一、概述

在开始学习Thread之前,我们先来了解一下 线程和进程之间的关系:

线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。 线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。

由上描述,可以得知线程作为cpu的基本调度单位,只有把多线程用好,才能充分利用cpu的多核资源。

本文基于JDK 8(也可以叫JDK 1.8)。

二、线程使用

2.1 启动线程

创建线程有四种方式:

  • 实现Runnable接口
  • 继承Thread类
  • 使用JDK 8 的Lambda
  • 使用Callable和Future

2.1.1 Runnable创建方式

public class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Thread thread = new Thread(new MyThread());
thread.start();

2.1.2 继承Thread创建方式

public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
MyThread thread = new MyThread();
thread.start();

以上代码有更简单的写法,如下:

Thread thread = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
thread.start();

2.1.3 Lambda创建方式

new Thread(()-> System.out.println(Thread.currentThread().getName())).start();

2.1.4 使用Callable和Future

看源码可以知道Thread的父类是Runnable是JDK1.0提供的,而Callable和Runnable类似,是JDK1.5提供的,弥补了调用线程没有返回值的情况,可以看做是Runnable的一个补充,下面看看Callable的实现。

public class MyThread implements Callable<String> {

    @Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName();
}
}
Callable<String> callable = new MyThread();
FutureTask<String> ft = new FutureTask<>(callable);
new Thread(ft,"threadName").start();
System.out.println(ft.get());

2.1.5 run()和start()的区别

真正启动线程的是start()方法而不是run(),run()和普通的成员方法一样,可以重复使用,但不能启动一个新线程。

2.2 Thread的常用方法

Thread类方法

方法 说明
start() 启动线程
setName(String name) 设置线程名称
setPriority(int priority) 设置线程优先级,默认5,取值1-10
join(long millisec) 挂起线程xx毫秒,参数可以不传
interrupt() 终止线程
isAlive() 测试线程是否处于活动状态

Thread静态(static)方法

方法 说明
yield() 暂停当前正在执行的线程对象,并执行其他线程。
sleep(long millisec)/sleep(long millis, int nanos) 挂起线程xx秒,参数不可省略
currentThread() 返回对当前正在执行的线程对象的引用
holdsLock(Object x) 当前线程是否拥有锁

2.3 sleep()和wait()的区别

sleep为线程的方法,而wait为Object的方法,他们的功能相似,最大本质的区别是:sleep不释放锁,wait释放锁。

用法上的不同:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来终止线程;wait()可以用notify()/notifyAll()直接唤起。

重点: 测试wait和sleep释放锁的代码如下:

public class SynchronizedTest extends Thread {
int number = 10;
public synchronized void first(){
System.out.println("this is first!");
number = number+1;
}
public synchronized void secord() throws InterruptedException {
System.out.println("this is secord!!");
Thread.sleep(1000);
// this.wait(1000);
number = number*100;
}
@Override
public void run() {
first();
}
}
SynchronizedTest synchronizedTest = new SynchronizedTest();
synchronizedTest.start();
synchronizedTest.secord();
// 主线程稍等10毫秒
Thread.sleep(10);
System.out.println(synchronizedTest.number);

根据结果可以得知:

  • 执行sleep(1000)运行的结果是:1001
  • 执行wait(1000)运行的结果是:1100

总结: 使用 sleep(1000)不释放同步锁,执行的是10*100+1=1001,wait(1000)释放了锁,执行的顺序是(10+1)x100=1100,所以sleep不释放锁,wait释放锁。

三、线程状态

3.1 线程状态概览

线程状态:

  • NEW 尚未启动
  • RUNNABLE 正在执行中
  • BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
  • WAITING 永久等待状态
  • TIMED_WAITING 等待指定的时间重新被唤醒的状态
  • TERMINATED 执行完成

线程的状态可以使用getState()查看,更多状态详情,查看Thread源码,如下图:

3.2 线程的状态代码实现

3.2.1 NEW 尚未启动状态

Thread thread = new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 只声明不调用start()方法,得到的状态是NEW
System.out.println(thread.getState()); // NEW

3.2.2 RUNNABLE 运行状态

Thread thread = new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
thread.start();
System.out.println(thread.getState()); // RUNNABLE

3.2.3 BLOCKED 阻塞状态

使用synchronized同步阻塞实现,代码如下:

public class MyCounter {
int counter;
public synchronized void increase() {
counter++;
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
MyCounter myCounter = new MyCounter();
// 线程1调用同步线程,模拟阻塞
new Thread(()-> myCounter.increase()).start();
// 线程2继续调用同步阻塞方法
Thread thread = new Thread(()-> myCounter.increase());
thread.start(); // 让主线程等10毫秒
Thread.currentThread().sleep(10);
// 打印线程2,为阻塞状态:BLOCKED
System.out.println(thread.getState());

3.2.4 WAITING 永久等待状态

public class MyThread extends Thread{
@Override
public void run() {
synchronized (MyThread.class){
try {
MyThread.class.wait();
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread thread = new Thread(new MyThread());
thread.start();
// 主线程挂起200毫秒,等thread执行完成
Thread.sleep(200);
// 输出WAITING,线程thread一直处于被挂起状态
System.out.println(thread.getState());

唤醒线程: 可使用 notify/notifyAll 方法,代码如下:

synchronized (MyThread.class) {
MyThread.class.notify();
}

使线程WAITING的方法:

  • Object的wait() 不设置超时时间
  • Thread.join()不设置超时时间
  • LockSupport的park()

查看Thread源码可以知道Thread的join方法,底层使用的是Object的wait实现的,如下图:

注意: 查看Object的源码可知wait(),不传递参数,等同于wait(0),设置的“0”不是立即执行,而是无限的等待,不执行,如下图:

3.2.5 TIMED_WAITING 超时等待状态

TIMED_WAITING状态,只需要给wait设置上时间即可,代码如下:

public class MyThread extends Thread{
@Override
public void run() {
synchronized (MyThread.class){
try {
MyThread.class.wait(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

调用代码还是一样的,如下:

Thread thread = new Thread(new MyThread());
thread.start();
// 主线程挂起200毫秒,等thread执行完成
Thread.sleep(200);
// 输出TIMED_WAITING
System.out.println(thread.getState());
synchronized (MyThread.class) {
MyThread.class.notify();
}

3.2.6 TERMINATED 完成状态

Thread thread = new Thread(()-> System.out.println(Thread.currentThread().getName()));
thread.start();
// 让主线程等10毫秒
Thread.currentThread().sleep(10);
System.out.println(thread.getState());

四、死锁

根据前面的知识,我们知道使用sleep的时候是不释放锁的,所以利用这个特性我们可以很轻易的写出死锁的代码,具体的流程如图(图片来源于杨晓峰老师文章):

代码如下:

static  Object object1 = new Object();
static Object object2 = new Object(); public static void main(String[] args) { Thread thread = new Thread(){
@Override
public void run() {
synchronized (object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println(Thread.currentThread().getName());
}
}
}
}; Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (object2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1){
System.out.println(Thread.currentThread().getName());
}
}
}
}; thread.start();
thread2.start();

运行上面的代码,程序会处于无限等待之中。

五、总结

根据上面的内容,我们已经系统的学习Thread的使用了,然而学而不思则罔,最后留一个思考题:根据本文介绍的知识,怎么能避免死锁?(哈哈,卖个关子,根据文章的知识点组合可以得出答案)

源码下载:https://github.com/vipstone/java-core-example.git


推荐部分

本人最近看了前Oracle首席工程师杨晓峰的课程,也是第四部分引用的流程图的主人,感觉很不错,推荐给你,一起来系统的学习Java核心吧。

参考文档

https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html

Java提高班(一)Thread详解的更多相关文章

  1. 【java提高】(19)---BigDecimal详解和精度问题

    BigDecimal详解和精度问题 一.背景 在实际开发中,对于 不需要任何准确计算精度的属性可以直接使用float或double,但是如果需要精确计算结果,则必须使用BigDecimal,例如价格. ...

  2. Java网络编程和NIO详解8:浅析mmap和Direct Buffer

    Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...

  3. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  4. Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

    Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系 ...

  5. Java网络编程和NIO详解3:IO模型与Java网络编程模型

    Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...

  6. Java中的main()方法详解

    在Java中,main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main,方法必须是 ...

  7. 使用Java操作文本文件的方法详解

    使用Java操作文本文件的方法详解 摘要: 最初java是不支持对文本文件的处理的,为了弥补这个缺憾而引入了Reader和Writer两个类 最初java是不支持对文本文件的处理的,为了弥补这个缺憾而 ...

  8. Java面试题04-final关键字详解

    Java面试题04-final关键字详解 本篇博客将会讨论java中final关键字的含义,以及final用在什么地方,感觉看书总会有一些模糊,而且解释的不是很清楚,在此做个总结,以备准备面试的时候查 ...

  9. java 关键字final static native详解

    java 关键字native static final详解 一.final 根据程序上下文环境,Java关键字final有"这是无法改变的"或者"终态的"含义, ...

  10. (转)Java并发包基石-AQS详解

    背景:之前在研究多线程的时候,模模糊糊知道AQS这个东西,但是对于其内部是如何实现,以及具体应用不是很理解,还自认为多线程已经学习的很到位了,贻笑大方. Java并发包基石-AQS详解Java并发包( ...

随机推荐

  1. Python使用ProtoBuffer

    Python使用ProtoBuffer Protocol Buffers,是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储.通信协议等方面. 就可读性而言感 ...

  2. Tomcat6,7,8的日志切割

    使用的日志切割工具cronolog(yum就可以了) 确定好路径后,开始配置 Tomcat6 Tomcat6/bin/catalina.sh 292-317行(修改两处) 修改之后为下面的内容 # t ...

  3. golang二进制bit位的常用操作

    golang作为一热门的兼顾性能 效率的热门语言,相信很多人都知道,在编程语言排行榜上一直都是很亮眼,作为一门强类型语言,二进制位的操作肯定是避免不了的,数据的最小的单位也就是位,尤其是网络中封包.拆 ...

  4. C++ 初步

    c++新特性: 1. 初始化方法: 复制初始化 int x=1024; 直接初始化 int x (1024); 2.变量随用随定义 3.增加bool数据类型 输入输出: cout<<x; ...

  5. promise的理解

    为什么会有promise,他的作用是什么? promise主要是为了解决js中多个异步回调难以维护和控制的问题. 什么是promise? 从图中,我们可以看出,Promise是一个函数,这个函数上有在 ...

  6. Centos7 编译安装 Nginx PHP Mariadb Memcached 扩展 ZendOpcache扩展 (实测 笔记 Centos 7.3 + Openssl 1.1.0e + Mariadb 10.1.22 + Nginx 1.12.0 + PHP 7.1.4 + Laravel 5.4 )

    环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G,双网卡) 系统版本:CentOS-7-x86_64-Minimal-1611.iso 安装步骤: 1.准备 1.0 查看硬 ...

  7. 你不知道的JS之作用域和闭包(二)词法作用域

    原文:你不知道的js系列 词法作用域(Lexical Scope) Lex time 一个标准的编译器的第一个阶段就是分词(token化) 词法作用域就是在词法分析时定义的作用域.换句话说,词法作用域 ...

  8. Oracle 闪回 找回数据

    使用闪回技术,实现基于磁盘上闪回恢复区的自动备份与还原. 一.恢复表对象 1.创建学生表 create table STUDENT ( idno INTEGER, name VARCHAR2(30), ...

  9. COCOMOII的使用说明

    if(airline.equals("欧美")) { result="所有座位都有食物供应,每个座位都可以播放电影"; } if(airline.equals( ...

  10. 浅拷贝 &&&深拷贝 实现

    1.浅拷贝 //1.直接赋值给一个变量 //浅拷贝 //2.Object.assign() //浅拷贝 let obj4={} let obj5={money:50000} obj4.__proto_ ...