目录
1.线程等待join(共有3个方法重载)
1.1.public void join()
1.2.public void join(long millis)
2.线程终止
2.1.通过自定义标记符来中断(常用)
2.2.调用 interrupt() 方法来中断(常用,最推荐使用)
2.2.1.Thread.interrupted()
2.2.2.Thread.currentThread().isInterrupted()
--->PS:(常见面试题)interrupted VS isInterrupted
2.3.使用stop停止线程(过期方法,不推荐使用)
3.yield让出CPU的执行权
4.获取当前线程
5.休眠当前线程
5.1.使用sleep休眠
5.2.使用TimeUnit休眠
PS:(线程练习题1)在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程中累加两个子线程的结果
PS:(线程练习题2)实现多线程数组求和
1.线程等待join(共有3个方法重载)
有时我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。这时就需要⼀个⽅法明确等待线程的结束。
相比isAlive()的优势:①写法更优雅;②使用wait()进行阻塞等待,会使用更小的资源来完成等待的工作。
1.1.public void join()
/**
* 关于join示例
*/
public class ThreadByJoin {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
//1.张三开始上班
System.out.println("1.张三开始上班");
//2.张三正在上班
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.张三下班
System.out.println("3.张三下班");
});
t1.start();
//等待线程t1执行完之后,再执行后面的代码
t1.join();
Thread t2 = new Thread(() -> {
//1.李四开始上班
System.out.println("1.李四开始上班");
//2.李四正在上班
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.李四下班
System.out.println("3.李四下班");
});
t2.start();
}
}
查看源码:
1.2.public void join(long millis)
import java.time.LocalDateTime;
public class ThreadByJoin {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("1.张三开始上班" + LocalDateTime.now());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3.张三下班" + LocalDateTime.now());
});
t1.start();
t1.join(500);
Thread t2 = new Thread(() -> {
System.out.println("1.李四开始上班" + LocalDateTime.now());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3.李四下班" + LocalDateTime.now());
});
t2.start();
}
}
查看源码:
2.线程终止
李四⼀旦进到⼯作状态,就会按照⾏动指南上的步骤去进⾏⼯作,不完成是不会结束的。但有时需要增加⼀些机制,例如⽼板突然来电话了,说转账的对⽅是个骗⼦,需要赶紧停⽌转账,那张三该如何通知李四停⽌呢?这就涉及到我们的停⽌线程的⽅式了。
2.1.通过自定义标记符来中断(常用)
/**
* 使用自定义标识符终止线程
*/
public class ThreadInterrupt {
//1.声明一个自定义标识符(一定要加volatile(易挥发的,不稳定的)关键字)
private volatile static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(!flag) {
System.out.println("正在转账......");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("啊?差点误了大事!");
});
thread.start();
Thread.sleep(3000);
//终止线程
System.out.println("有内鬼,终止交易!");
flag = true;
}
}
但其问题在于:线程中断地不够及时。因为线程在执行过程中,无法调用 while(!isInterrupt) 来判断线程是否为终止状态,它只能在下一轮运行时判断是否要终止当前线程。
class InterruptFlag { // 自定义的中断标识符 private static volatile boolean isInterrupt = false; public static void main(String[] args) throws InterruptedException { // 创建可中断的线程实例 Thread thread = new Thread(() -> { while (!isInterrupt) { // 如果 isInterrupt=true 则停止线程 System.out.println("thread 执行步骤1:线程即将进入休眠状态"); try { // 休眠 1s Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread 执行步骤2:线程执行了任务"); } }); thread.start(); // 启动线程 // 休眠 100ms,等待 thread 线程运行起来 Thread.sleep(100); System.out.println("主线程:试图终止线程 thread"); // 修改中断标识符,中断线程 isInterrupt = true; } }
期望的是:线程执行了步骤 1 之后,收到中断线程的指令,然后就不要再执行步骤 2 了,但上述执行结果看出,使用自定义中断标识符无法实现预期结果。
2.2.调用 interrupt() 方法来中断(常用,最推荐使用)
使用 interrupt 方法可以给执行任务的线程,发送一个中断线程的指令,它并不直接中断线程,而是发送一个中断线程的信号,把是否正在中断线程的主动权交给代码编写者。相比于自定义中断标识符,它能更及时的接收到中断指令。
public static void main(String[] args) throws InterruptedException { // 创建可中断的线程实例 Thread thread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { System.out.println("thread 执行步骤1:线程即将进入休眠状态"); try { // 休眠 1s Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("thread 线程接收到中断指令,执行中断操作"); // 中断当前线程的任务执行 break; } System.out.println("thread 执行步骤2:线程执行了任务"); } }); thread.start(); // 启动线程 // 休眠 100ms,等待 thread 线程运行起来 Thread.sleep(100); System.out.println("主线程:试图终止线程 thread"); // 修改中断标识符,中断线程 thread.interrupt(); }
从执行结果看出,线程在接收到中断指令之后,立即中断了线程。
interrupt()(执行终止)需要配合 Thread.interrupted()(判断终止)或 Thread.currentThread().isInterrupted()(判断终止)来使用。
使⽤ Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位。
Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记。
2.2.1.Thread.interrupted()
/**
* 使用interrupt终止线程
*/
public class ThreadInterrupt2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(!Thread.interrupted()) { //Thread.interrupted()判断线程是否被终止,如果线程被终止,返回true;否则返回false。
System.out.println("正在转账......");
}
System.out.println("啊?险些误了大事!");
});
//启动线程
thread.start();
Thread.sleep(100);
//终止线程
thread.interrupt();
System.out.println("有内鬼,终止交易!");
}
}
2.2.2.Thread.currentThread().isInterrupted()
public class ThreadInterrupt2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) { //得到当前的线程,判断其是否是终止状态,如果是终止状态,返回true,否则返回false。
System.out.println("正在转账......");
}
System.out.println("啊?险些误了大事!");
});
thread.start();
Thread.sleep(100);
thread.interrupt();
System.out.println("有内鬼,终止交易!");
}
}
--->PS:(常见面试题)interrupted VS isInterrupted
二者执行结果一样。区别如下:
①interrupted属于静态方法,所有程序都可以直接调用的全局方法;而isInterrupted属于某个实例的私有方法。
②interrupted在使用完之后会重置中断标识符为初始状态false;而isInterrupted在使用完之后不会重置中断标识符。
推荐使用isInterrupted,不建议用interrupted是因为它是全局的,用完后状态会自动回滚,加剧了程序的复杂性和理解上的难度。
public class ThreadInterrupt2 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { } System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); System.out.println(); System.out.println("interrupted:" + Thread.interrupted()); System.out.println("interrupted:" + Thread.interrupted()); System.out.println("interrupted:" + Thread.interrupted()); } }); t.start(); Thread.sleep(100); t.interrupt(); } }
标识符默认为false,表示线程未被终止。
public class ThreadInterrupt2 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { @Override public void run() { while (!Thread.interrupted()) { } System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); System.out.println(); System.out.println("interrupted:" + Thread.interrupted()); System.out.println("interrupted:" + Thread.interrupted()); System.out.println("interrupted:" + Thread.interrupted()); } }); t.start(); Thread.sleep(100); t.interrupt(); } }
2.3.使用stop停止线程(过期方法,不推荐使用)
stop 方法是被 @Deprecated 修饰的过期方法,并且在其源码注释的第一句话就说明了 stop 方法为非安全的方法:因为 stop 方法会直接停止线程,这样就没有给线程足够的时间来处理停止前的保存工作,就会造成数据不完整的问题。在最新版本 Java 中,此方法已经被直接移除了,所以强烈不建议使用。
小结——停止线程的 3 种方法:
- 自定义中断标识符,此方法的缺点是不能及时响应中断请求;
- 使用 interrupt,此方法是发送一个中断信号给线程,它可以及时响应中断,也是最推荐使用的方法;
- stop 方法,过期的不推荐使用。
3.yield让出CPU的执行权
- static静态方法。
- native表示yield是原生的方法,是JVM里的一个方法,调用C/C++里的yield方法,不是Java的方法。
yield不改变线程的状态,但是会重新去排队,而排队之后选择谁是不确定的。
yield方法会让出CPU执行权,让线程调度器重新随机调度线程,是有一定几率再一次调度到出让CPU执行权的线程上,这一次它就会接着刚才的继续往后执行到线程的方法了,不会再让一次执行权,因为yield已经被执行过。
/**
* yield方法演示(让出CPU的执行权)
*/
public class ThreadYield {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
//得到当前线程
Thread cThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
//让出CPU执行权
Thread.yield();
System.out.println("执行线程:" + cThread.getName());
}
}, "张三");
t1.start();
//创建并启动线程
new Thread(() -> {
//在一个main方法中可以起2个相同的cThread线程名字,是因为()->{}相当于有2个匿名内部类,都是在自己的作用域里,两个变量cThread是互相看不到的,彼此是隔离的
Thread cThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
System.out.println("执行线程:" + cThread.getName());
}
},"李四").start();
}
}
4.获取当前线程
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
5.休眠当前线程
5.1.使用sleep休眠
public class ThreadSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
System.out.println("我接受到了终止执行的通知");
// e.printStackTrace();
}
});
thread.start();
Thread.sleep(1000);
System.out.println("终止子线程thread");
thread.interrupt();
}
}
缺点:在休眠较长时间时,还需要自己手动计算时间,麻烦、易错。
5.2.使用TimeUnit休眠
import java.util.concurrent.TimeUnit;
TimeUnit.DAYS.sleep(1);//天(实际用的少)
TimeUnit.HOURS.sleep(1);//⼩时
TimeUnit.MINUTES.sleep(1);//分
TimeUnit.SECONDS.sleep(1);//秒
TimeUnit.MILLISECONDS.sleep(1000);//毫秒
TimeUnit.MICROSECONDS.sleep(1000);//微妙
TimeUnit.NANOSECONDS.sleep(1000);//纳秒
查看源码(实现原理-套了个壳):
PS:(线程练习题1)在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程中累加两个子线程的结果
实现思路①:使用主线程把两个普通子线程(无返回值)生成的随机数值保存起来,等两个子线程执行完之后进行累加操作。考察如何把线程内部的变量赋值给全局变量。
JVM规定:在一个线程中修改另一个线程中的私有变量是不安全的。
在一个线程中可以打印另一个线程中的属性,但不能修改。
解决方案:将num1和num2提成公共的全局变量:加static关键字并将其放在类中main方法外。
import java.util.Random; /** * 在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程累计两个子线程的结果 */ public class ThreadDemo14 { static int num1 = 0; static int num2 = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { num1 = new Random().nextInt(10); }); t1.start(); Thread t2 = new Thread(() -> { num2 = new Random().nextInt(10); }); t2.start(); t1.join(); t2.join(); System.out.println("最终的结果:" + (num1 + num2)); } }
实现思路②:两个子线程使用有返回值的线程调用,执行完将生成的随机数值返回给主线程,主线程把值进行累加。
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadDemo15 { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { int num = new Random().nextInt(10); return num; } }); Thread t1 = new Thread(futureTask); Thread t2 = new Thread(futureTask); t1.start(); t2.start(); int num1 = futureTask.get(); int num2 = futureTask.get(); System.out.println("最终的结果:" + (num1 + num2)); } }
注:
- 普通线程(无返回值)在打印最终结果前一定要加join()等待线程执行完;
- 而带返回值的线程有get(),get方法需要得到线程的返回值,当线程执行完才能有返回值,所以不用加join()。
PS:(线程练习题2)实现多线程数组求和
实现思路有2种(同上),此处使用思路2:
- 给定一个很长的数组 (长度 1000w), 通过随机数的方式生成 1-100 之间的整数.
- 实现代码, 能够创建两个线程, 对这个数组的所有元素求和.
- 其中线程1 计算偶数下标元素的和, 线程2 计算奇数下标元素的和.
- 最终再汇总两个和, 进行相加.
- 记录程序的执行时间.
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * 实现多线程大数组相加 */ public class ThreadDemo16 { public static void main(String[] args) throws ExecutionException, InterruptedException { //记录开始执行时间 long stime = System.currentTimeMillis(); //返回的是一个13位的时间戳-毫秒数 int[] arrs = new int[10000000]; //1.使用随机数初始化数组 Random random = new Random(); for (int i = 0; i < arrs.length; i++) { arrs[i] = (random.nextInt(100) + 1); } //2.创建两个线程执行相加操作 //2.1.返回偶数之和 FutureTask<Integer> task1 = new FutureTask<Integer>(new Callable<Integer>() { @Override public Integer call() throws Exception { int temp = 0; for (int i = 0; i < arrs.length; i = i + 2) { temp += arrs[i]; } System.out.println("线程1:" + temp); return temp; } }); Thread t1 = new Thread(task1); t1.start(); //2.2.返回奇数之和 FutureTask<Integer> task2 = new FutureTask<Integer>(new Callable<Integer>() { @Override public Integer call() throws Exception { int temp = 0; for (int i = 1; i < arrs.length; i = i + 2) { temp += arrs[i]; } System.out.println("线程2:" + temp); return temp; } }); Thread t2 = new Thread(task2); t2.start(); int sum = task1.get() + task2.get(); System.out.println("最终结果:" + sum); //记录执行完成时间 long etime = System.currentTimeMillis(); //返回的是一个13位的时间戳 //程序执行时间 = 执行完成时间 - 开始执行时间 System.out.println("程序执行时间:" + (etime - stime) + "ms"); } }