一, 概述
在开始学习 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
来源: https://www.cnblogs.com/vipstone/p/9762360.html