Java并发编程(一)Thread详解

26次阅读

共计 6006 个字符,预计需要花费 16 分钟才能阅读完成。

一、概述
在开始学习 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/j…

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

参考文档
https://docs.oracle.com/javas…

正文完
 0