目录
线程的概念
Java中多线程应用
继承Thread类
实现Runnable接口
实现Callable接口
线程的生命周期
线程的基本操作及原理
Thread.join的使用及原理
Thread.sleep的作用
问题
Thread.sleep的工作流程
wait和notify的使用
wait
notify
例子
生产者消费者模式
Thread.interrupted和Thread.interrupt
为什么Thread.stop不推荐使用?
通过共享变量来终止线程
interrupt 方法
Interrupted方法
总结
线程的概念
是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一个进程可以有很多线程,每条线程并行执行不同的任务。
为什么会有线程?
-
在多核CPU中,利用多线程可以实现真正意义上的并行执行
-
在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务 被阻塞,将会引起不依赖该任务的任务也被阻塞。通过对不同任务创 建不同的线程去处理,可以提升程序处理的实时性
-
线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快
为什么要用多线程?
-
异步执行(避免阻塞)
-
利用多CPU资源实现真正意义行的并行执行
Java中多线程应用
继承Thread类
既然线程启动时会去调用 run 方法,那么我们只要重写 Thread 类的 run 方法也是可以定义出我们的线程类的。
public class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadDemo thread = new ThreadDemo();
thread.start();
}
}
执行结果: 当前线程:Thread-0
实现Runnable接口
public class RunnableDemo implements Runnable {
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
}
}
执行结果: 当前线程:Thread-0
实现Callable接口
import java.util.concurrent.*;
public class CallableDemo implements Callable<String> {
public String call() throws Exception {
System.out.println("当前线程:" + Thread.currentThread().getName());
Thread.sleep(10000); // 等待sleep执行完后才会返回“hello ly”
return "hello ly";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(new CallableDemo());
// future.get() 是一个阻塞方法
System.out.println(Thread.currentThread().getName() + " - " + future.get());
}
}
执行结果: 当前线程:pool-1-thread-1
main - hello ly
线程的生命周期
Java线程从创建到销毁,可能会经历一下6个状态:
-
NEW:初始状态,线程被构建,但是还没有调用start方法
-
RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为”运行中”
-
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使 用权,阻塞也分为几种情况
-
WAITING: 等待状态
-
TIME_WAITING:超时等待状态,超时以后自动返回
-
TERMINATED:终止状态,表示当前线程执行完毕
import java.util.concurrent.TimeUnit;
public class ThreadStatusDemo {
public static void main(String[] args) {
// TIME_WAITING
new Thread(()->{
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Time_Wating_Demo").start();
// WAITING
new Thread(()->{
while (true) {
synchronized (ThreadStatusDemo.class) {
try {
ThreadStatusDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "Wating").start();
new Thread(new BlockedDemo(), "Blocked-Demo-01").start();
new Thread(new BlockedDemo(), "Blocked-Demo-02").start();
}
static class BlockedDemo extends Thread {
@Override
public void run() {
synchronized (BlockedDemo.class) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行ThreadStatusDemo.main
后,如果在Terminal
下执行jps
命令无显示,如下图所示:
产生原因:是因为 jps
命令 没有权限 读取 hsperfdata_用户名的文件夹
, 这个文件夹运行java
程序时产生的。
解决办法:
-
找到
hsperfdata_用户名的文件夹
添加读写权限,例如本机路径C:\Users\admin\AppData\Local\Temp\hsperfdata_admin
; -
右击-->属性-->安全-->高级;
-
选中当前登录用户,点击【更改权限】;
-
选中当前登录用户,点击【编辑】;
-
勾选【完全控制】,点击【确定】。
线程的基本操作及原理
Thread.join的使用及原理
public class ThreadJoinDemo {
private static int x = 0;
private static int i = 0;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
i = 1;
x = 2;
});
Thread t2 = new Thread(()->{
i = x + 2;
});
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("result : " + i);
}
}
执行结果可能是:4
多执行几次结果有可能是:1
由于线程的执行顺序是不确定的,如何保证线程的执行顺序呢?
我们在t1.start();
和t2.start();
之间加入t1.join();
就能保证了。
......
t1.start();
t1.join(); // t1线程的执行结果对于t2可见(t1线程一定会比t2线程优先执行)
t2.start();
......
Thread.sleep的作用
使线程暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断。
import java.text.SimpleDateFormat;
public class ThreadSleepDemo extends Thread {
public static void main(String[] args) {
new ThreadSleepDemo().start();
}
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long dt1 = System.currentTimeMillis();
System.out.println("begin: " + dt1 + " | " + sdf.format(dt1));
try {
Thread.sleep(3000);
long dt2 = System.currentTimeMillis();
System.out.println("end: " + dt2 + " | " + sdf.format(dt2));
long dt = dt2 - dt1;
System.out.println("dt2 - dt1 = " + dt);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
begin: 1650361034229 | 2022-04-19 17:37:14
end: 1650361037243 | 2022-04-19 17:37:17
dt2 - dt1 = 3014
问题
-
假设现在是
2022-04-19 12:00:00.000
,如果调用Thread.Sleep(1000)
,那么在2022-04-19 12:00:01.000
的时候,这个线程会不会被唤醒? -
Thread.Sleep(0)
的意义Thread.sleep(0)
并非是真的让线程挂起0毫秒,意义在于调用Thread.sleep(0)
的当前线程确实被冻结了一下,让其他线程有机会优先执行,Thread.sleep(0)
是使你的线程暂时放弃cpu
,也是释放一些未使用的时间片给其他线程或者进程使用,就相当于一个让位动作
Thread.sleep的工作流程
-
挂起线程并修改其运行状态
-
用sleep()提供的参数来设置一个定时器。
-
当时间结束,定时器会触发,内核收到中断后修改线程的运行状态。
例如线程会被标志为就绪而进入就绪队列等待调度
wait和notify的使用
wait
-
wait(),当前线程进入 无限等待状态,必须被唤醒才能继续执行,调用后会释放锁对象
-
wait(long timeout),wait(long timeout,int nanos),当前线程进入等待状态,可以被提前唤醒,但在指定时间后会自动唤醒
notify
-
notify(), 随机唤醒一个在锁对象上调用wait的线程
-
notifyAll(),唤醒 全部在锁对象上调用wait的线程
例子
public class TestDemo {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread() {
@Override
public void run() {
// 保证正等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj) {
System.out.println("告诉老板要的包子的种类和数量");
// 调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("包子已经准备好了,开吃!");
}
}
}.start();
// 创建一个老板线程(生产者)
new Thread() {
@Override
public void run() {
// 花了5秒钟准备包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("老板5秒钟之后准备好包子,告知顾客,可以吃包子了");
// 准备好包子之后,调用notify方法,唤醒顾客吃包子
obj.notify();
}
}
}.start();
}
}
执行结果:
告诉老板要的包子的种类和数量
老板5秒钟之后准备好包子,告知顾客,可以吃包子了
包子已经准备好了,开吃!
生产者消费者模式
-
生产者
Producer.java
源码
import java.util.Queue;
public class Producer implements Runnable {
private Queue<String> bags;
private int size;
public Producer(Queue<String> bags, int size) {
this.bags = bags;
this.size = size;
}
public void run() {
int i = 0;
while (true) {
i++;
synchronized (bags) {
while (bags.size() == size) {
System.out.println("bags 满了");
try {
bags.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产者 - 生产:bag " + i);
bags.add("bag " + i);
// 唤醒处于阻塞状态下的消费者
bags.notifyAll();
}
}
}
}
消费者Consumer.java
源码
import java.util.Queue;
public class Consumer implements Runnable {
private Queue<String> bags;
private int size;
public Consumer(Queue<String> bags, int size) {
this.bags = bags;
this.size = size;
}
public void run() {
while (true) {
synchronized (bags) {
while (bags.isEmpty()) {
System.out.println("bags 为空。");
try {
bags.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String bag = bags.remove();
System.out.println("消费者消费:" + bag);
// 唤醒处于阻塞状态下的生产者
bags.notifyAll();
}
}
}
}
-
测试
WaitNotifyDemo.java
源码
import java.util.LinkedList;
import java.util.Queue;
public class WaitNotifyDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<String>();
int size = 10;
Producer producer = new Producer(queue, size);
Consumer consumer = new Consumer(queue, size);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
Thread.interrupted和Thread.interrupt
为什么Thread.stop
不推荐使用?
因为它本质上是不安全的。停止线程会导致它解锁所有已锁定的监视器。(当ThreadDeath异常在堆栈中传播时,监视器被解锁。)如果之前由这些监视器保护的对象中的任何一个处于不一致状态,则其他线程现在可以以不一致的状态查看这些对象。据称这些物体被 损坏。当线程操作受损对象时,可能导致任意行为。这种行为可能微妙且难以检测,或者可能会发音。与其他未经检查的异常不同,可以 ThreadDeath静默地杀死线程; 因此,用户没有警告他的程序可能被损坏。腐败现象可能会在实际损害发生后随时出现,甚至可能在未来数小时甚至数天。
通过共享变量来终止线程
import java.util.concurrent.TimeUnit;
public class StopDemo {
static volatile boolean bStop = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
TimeUnit.SECONDS.sleep(2000);
bStop = true;
}
static class StopThread implements Runnable {
public void run() {
while(!bStop) {
System.out.println("持续运行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
interrupt 方法
当其他线程通过调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
while(!Thread.currentThread().isInterrupted()) {
System.out.println("持续执行......");
}
});
thread.start();
Thread.sleep(1);
thread.interrupt();
System.out.println("线程中断了...");
}
}
-
什么情况下会抛出InterruptedException异常?
当一个线程处于阻塞状态下(例如休眠)的情况下,调用了该线程的interrupt()方法,则会出现InterruptedException。
-
如何处理InterruptedException?
-
不要生吞此异常;
-
如果可以处理此异常:完成清理工作之后退出;
-
不处理此异常,不继续执行任务:重新抛出;
-
不处理此异常,继续执行任务:捕捉到异常之后恢复中断标记(交由后续程序检查中断)。
-
Interrupted方法
public class MyThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i = " + (i + 1));
}
}
public static void main(String[] args) {
MyThreadDemo thread = new MyThreadDemo();
thread.start();
thread.interrupt();
System.out.println("第一次调用thread.isInterrupted() : " + thread.isInterrupted());
System.out.println("第二次调用thread.isInterrupted() : " + thread.isInterrupted());
System.out.println("thread是否存活 : " + thread.isAlive());
}
}
运行结果: 第一次调用thread.isInterrupted() : true
第二次调用thread.isInterrupted() : true
thread是否存活 : true
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
从结果可以看出调用interrupt()
方法后,线程仍在继续运行,并未停止,但已经给线程设置了中断标志,两个isInterrupted()
方法都会输出true,也说明isInterrupted()
方法并不会清除中断状态。
下面我们把代码修改一下,多加两行调用interrupted()
方法:
public class MyThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i = " + (i + 1));
}
}
public static void main(String[] args) {
MyThreadDemo thread = new MyThreadDemo();
thread.start();
thread.interrupt();
System.out.println("第一次调用thread.isInterrupted() : " + thread.isInterrupted());
System.out.println("第二次调用thread.isInterrupted() : " + thread.isInterrupted());
System.out.println("第一次调用threadinterrupted() : " + thread.interrupted());
System.out.println("第二次调用thread.interrupted() : " + thread.interrupted());
System.out.println("thread是否存活 : " + thread.isAlive());
}
}
运行结果: 第一次调用thread.isInterrupted() : true
i = 1
第二次调用thread.isInterrupted() : true
i = 2
第一次调用threadinterrupted() : false
第二次调用thread.interrupted() : false
i = 3
thread是否存活 : true
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
从输出结果看,可能会有疑惑,为什么后面两个interrupted
方法输出的都是false,而不是预料中的一个true一个false?
注意!!!这是一个坑!!!上面说到,interrupted()
方法测试的是当前线程是否被中断
这里当前线程是main线程,而thread.interrupt()
中断的是thread线程。
所以当前线程main从未被中断过,尽管interrupted()
方法是以thread.interrupted()
的形式被调用,但它检测的仍然是main线程而不是检测thread线程,所以thread.interrupted()
在这里相当于main.interrupted()
。对于这点,下面我们再修改进行测试。
Thread.currentThread()函数可以获取当前线程,下面代码中获取的是main线程
public static void main(String[] args) {
Thread.currentThread().interrupt();
System.out.println("第一次调用Thread.currentThread().interrupt():"
+Thread.currentThread().isInterrupted());
System.out.println("第一次调用thread.interrupted():"
+Thread.currentThread().interrupted());
System.out.println("第二次调用thread.interrupted():"
+Thread.currentThread().interrupted());
}
运行结果: 第一次调用Thread.currentThread().interrupt():true
第一次调用thread.interrupted():true
第二次调用thread.interrupted():false
结果证明猜想是正确的。
若果想要是实现调用interrupt()方法真正的终止线程,则可以在线程的run方法中做处理即可,比如直接跳出run()方法使线程结束,视具体情况而定,下面是一个例子。
public class MyThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("i = " + (i + 1));
if(this.isInterrupted()) {
System.out.println("通过this.isInterrupted()检测到中断");
System.out.println("第一个interrupted()" + this.interrupted());;
System.out.println("第二个interrupted()" + this.interrupted());
break;
}
}
System.out.println("因为检测到中断,所以跳出循环,线程到这里结束,因为后面没有内容了。");
}
public static void main(String[] args) throws InterruptedException {
MyThreadDemo myThread = new MyThreadDemo();
myThread.start();
myThread.interrupt();
// sleep等待一秒,等myThread运行完
Thread.currentThread().sleep(1000);
System.out.println("myThread线程是否存活:" + myThread.isAlive());
}
}
运行结果: i = 1
通过this.isInterrupted()检测到中断
第一个interrupted()true
第二个interrupted()false
因为检测到中断,所以跳出循环,线程到这里结束,因为后面没有内容了。
myThread线程是否存活:false
总结
-
interrupt() 是给线程设置中断标志
-
interrupted() 是检测中断并清除中断状态
-
isInterrupted() 只检测中断。
-
还有一点就是
interrupted()作用于当前线程
,interrupt()
和isInterrupted()
作用于此线程,即代码中调用此方法的实例所代表的线程。