多线程并发编程之美
等待线程执行终止的 join 方法
执行的代码
public class MyThreadThree {
public static void main(String[] args) throws InterruptedException {
Thread thread_a=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("child thread_a start run");
});
Thread thread_b=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("child thread_b start run");
});
thread_a.start();
thread_b.start();
System.out.println("wait all child thread run over");
thread_a.join();
thread_b.join();
System.out.println("all child thread run over");
}
}
最终的执行结果:
wait all child thread run over all child thread run over thread_a start run thread_b start run
Process finished with exit code 0
主线程 main 在调用了子线程 thread_a 与 thread_b 的 join 方法之后,当前的主线程 main 处于阻塞的状态,直到线程 thread_a 与 thread_b 执行完毕后,才会继续执行当前的主线程 main 。
如果我注释掉线程 a 与线程 b 的两个方法,那么执行的结果是什么样的呢?没错,all child thread run over 这句主线程打印的话会在线程 a 与线程 b 输出之前执行(线程 a 与线程 b处于休眠状态)
public class MyThreadThree {
public static void main(String[] args) throws InterruptedException {
Thread thread_a=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_a start run");
});
Thread thread_b=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_b start run");
});
thread_a.start();
thread_b.start();
System.out.println("wait all child thread run over");
// thread_a.join();
// thread_b.join();
System.out.println("all child thread run over");
}
}
wait all child thread run over all child thread run over thread_a start run thread_b start run
Process finished with exit code 0
那么 join 方法的作用其实就变得很明显了,就是为了阻塞当前的线程,直到子线程的任务执行完毕。
我们可以将上面的第一部分的代码做一个小小的改动
public class MyThreadThree {
public static void main(String[] args) throws InterruptedException {
Thread thread_a=new Thread(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_a start run");
});
Thread thread_b=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_b start run");
});
thread_a.start();
thread_b.start();
System.out.println("wait all child thread run over");
thread_a.join();
thread_b.join();
System.out.println("all child thread run over");
}
}
这样保证了线程打印的执行输出顺序为 main -> thread_a -> thread_b -> main
wait all child thread run over thread_b start run thread_a start run all child thread run over
Process finished with exit code 0
既然 join 可以阻塞当前的线程直到,调用 join 方法的线程执行完毕并返回后,才继续执行当前的线程,那么我们可以通过 join 来控制,线程 a 与线程 b 的执行是顺序。
我们对以上的代码做一稍微的修改即可。
public class MyThreadThree {
public static void main(String[] args) throws InterruptedException {
Thread thread_a=new Thread(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_a start run");
});
Thread thread_b=new Thread(()->{
try {
//阻塞当前的线程 让其线程 a 先执行完毕
thread_a.join();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_b start run");
});
thread_a.start();
thread_b.start();
System.out.println("wait all child thread run over");
thread_a.join();
thread_b.join();
System.out.println("all child thread run over");
}
}
执行的结果为:
wait all child thread run over thread_a start run thread_b start run all child thread run over
Process finished with exit code 0
可以看到线程 b 在执行到 thread_a.join()
的时候,线程 b 处于停滞阻塞的状态,等待线程 a 执行完毕,后线程的 b 才继续执行。
以上的两段程序执行所占用的时间也是不同的,第一段程序执行所占用的最大时间大致为 2 s,第二段程序执行完毕所占用的最大时间大致为 2 + 1 = 3 s。
【思考】
那么在多线程中提供这样的 join 阻塞当前调用线程的方法作用是什么呢?
在 《Java并发编程之美》-1.4小节[等待线程执行终止的 join 方法]中是这样解释的:
好赖,就这样咯.
让线程睡眠的 sleep() 方法
执行下面的代码会发生什么?
- sleep() 会让当前的线程进入睡眠的状态,该状态下的线程不参与 CPU 时间片的调度,但该线程拥有监视器资源,锁资源还是持有不让出的,当到达了指定的唤醒时间后,线程重新进入就绪状态,然后继续参与 CPU 时间片的调度.
public class ThreadFour {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread_a= new Thread(()->{
lock.lock();
try {
System.out.println("thread_a is sleep");
Thread.sleep(1000);
System.out.println("thread_a is awaked");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
Thread thread_b=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println("thread_b is sleep");
Thread.sleep(1000);
System.out.println("thread_b is awaked");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
thread_a.start();
thread_b.start();
}
}
thread_a is sleep thread_a is awaked thread_b is sleep thread_b is awaked
Process finished with exit code 0
如果在当前的线程处于睡眠的状态时,其他线程调用了当前线程的 interrupt() 方法,则会导致当前的线程抛出 InterruptedException 的中断异常.
CPU 让出执行的权的 yield 方法
public class YieldTest implements Runnable {
YieldTest() {
Thread thread = new Thread(this);
thread.start();
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (i % 5 == 0) {
System.out.println(Thread.currentThread() + "yield CPU ... ");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread() + "over");
}
}
class test {
public static void main(String[] args) {
new YieldTest();
new YieldTest();
new YieldTest();
}
}
public class YieldTest implements Runnable {
YieldTest() {
Thread thread = new Thread(this);
thread.start();
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (i % 5 == 0) {
System.out.println(Thread.currentThread() + "yield CPU ... ");
Thread.yield();
}
}
System.out.println(Thread.currentThread() + "over");
}
}
class test {
public static void main(String[] args) {
new YieldTest();
new YieldTest();
new YieldTest();
}
}
对比这两段代码可以发现什么?
第一段的代码在调用了 sleep(1) 方法后进入阻塞的状态,并且等待阻塞时间过期然后唤醒。(这种的情况出现的次数最多)
Thread[Thread-2,5,main]yield CPU … Thread[Thread-2,5,main]over Thread[Thread-0,5,main]yield CPU … Thread[Thread-1,5,main]yield CPU … Thread[Thread-0,5,main]over Thread[Thread-1,5,main]over
Process finished with exit code 0
第二段代码在调用 yield() 方法使当前线程让出 CPU 的执行时间片,线程调度器会从线程的队列中优先选出一个优先级别最高的线程进行执行,当前这个期间也有可能调用到刚刚让出 CPU 执行的时间片的线程。
所以第二段的代码片段在执行之后的结果是这样的:
*Thread[Thread-2,5,main]yield CPU … **Thread[Thread-1,5,main]yield CPU … **Thread[Thread-0,5,main]yield CPU … *Thread[Thread-1,5,main]over Thread[Thread-0,5,main]over Thread[Thread-2,5,main]over
Process finished with exit code 0
- sleep()方法暂停当前线程后,会给其他线程执行机会,不区分其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。(优先级区分)
- sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程被yield()方法暂停之后,立即再次获得处理器资源被执行。(运行状态区分)
- sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。(抛出异常区分)
线程的中断
public class MyThreadFired {
public static void main(String[] args) throws InterruptedException {
Thread thread_a = new Thread(() -> {
//获取当前的线程的中断标志并且重置
while (!Thread.currentThread().interrupted()){
}
//获取当前线程的中断标志
System.out.println("thread isInterrupted: " + Thread.currentThread().isInterrupted());
});
thread_a.start();
//设置线程的中断标志
thread_a.interrupt();
Thread.sleep(1000);
thread_a.join();
System.out.println("main thread is over");
}
}
执行结果:
thread isInterrupted: false main thread is over
Process finished with exit code 0
线程死锁
资源占用导致线程死锁
public class MyThreadLock {
private static Object obj1=new Object();
private static Object obj2=new Object();
public static void main(String[] args) {
Thread thread_a= new Thread(()->{
synchronized (obj1){
System.out.println(Thread.currentThread() + "get obj1 Lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println(Thread.currentThread() + "get obj2 Lock");
}
}
});
Thread thread_b=new Thread(()->{
synchronized (obj2){
System.out.println(Thread.currentThread()+"get obj2 Lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println(Thread.currentThread() + "get obj1 Lock");
}
}
});
thread_a.start();
thread_b.start();
}
}
Thread[Thread-0,5,main]get obj1 Lock Thread[Thread-1,5,main]get obj2 Lock
ThreadLocal 实现原理
先看两组代码
public class MyThreadLocalTest {
static void print(String str) {
//获取本地变量中的值
System.out.println(str+":"+threadLocal.get());
//清除本地变量中的值
// threadLocal.remove();
}
//创建本地变量
static ThreadLocal<String> threadLocal=new ThreadLocal<>();
public static void main(String[] args) {
Thread threadOne=new Thread(()->{
//线程 One 获取本地线程变量中的值
System.out.println("threadOne threadLocal vault "+threadLocal.get());
//设置本地线程变量中的值
threadLocal.set("卡卡罗特来咯!!!");
threadLocal.set("卡卡罗特来咯!!!与他的儿子悟饭");
//调用打印函数
print("threadOne thread ");
//再次打印本地线程变量中的值
System.out.println("threadOne two threadLocal vault "+threadLocal.get());
});
Thread threadTwo=new Thread(()->{
//获取本地线程变量中的值
System.out.println("threadTwo threadLocal vault "+threadLocal.get());
//设置本地线程变量值
threadLocal.set("骄傲的贝吉塔!!!");
print("threadTwo thread");
System.out.println("threadTwo two threadLocal vault "+threadLocal.get());
});
threadOne.start();
threadTwo.start();
}
}
threadTwo threadLocal vault null threadOne threadLocal vault null *threadTwo thread:骄傲的贝吉塔!!! **threadTwo two threadLocal vault 骄傲的贝吉塔!!! *threadOne thread :卡卡罗特来咯!!!与他的儿子悟饭 threadOne two threadLocal vault 卡卡罗特来咯!!!与他的儿子悟饭
Process finished with exit code 0
public class MyThreadLocalTest {
static void print(String str) {
//获取本地变量中的值
System.out.println(str+":"+threadLocal.get());
//清除本地变量中的值
threadLocal.remove();
}
//创建本地变量
static ThreadLocal<String> threadLocal=new ThreadLocal<>();
public static void main(String[] args) {
Thread threadOne=new Thread(()->{
//线程 One 获取本地线程变量中的值
System.out.println("threadOne threadLocal vault "+threadLocal.get());
//设置本地线程变量中的值
threadLocal.set("卡卡罗特来咯!!!");
threadLocal.set("卡卡罗特来咯!!!与他的儿子悟饭");
//调用打印函数
print("threadOne thread ");
//再次打印本地线程变量中的值
System.out.println("threadOne two threadLocal vault "+threadLocal.get());
});
Thread threadTwo=new Thread(()->{
//获取本地线程变量中的值
System.out.println("threadTwo threadLocal vault "+threadLocal.get());
//设置本地线程变量值
threadLocal.set("骄傲的贝吉塔!!!");
print("threadTwo thread");
System.out.println("threadTwo two threadLocal vault "+threadLocal.get());
});
threadOne.start();
threadTwo.start();
}
}
输出结果:
threadTwo threadLocal vault null *threadTwo thread:骄傲的贝吉塔!!! *threadTwo two threadLocal vault null threadOne threadLocal vault null threadOne thread :卡卡罗特来咯!!!与他的儿子悟饭 threadOne two threadLocal vault null
Process finished with exit code 0
代码分析:
两组代码都是调用了本地线程变量 threadLocal 并设置了不同的值。其区别在于第一段代码在调用 print 方法时执行了threadLocal.remove()
第二组代码中未执行,所以在第二组代码中,对于每一个线程而言,最后的第二次输出的本地线程变量中都是有其固定的值存在的。
问题:
- 设计 ThreadLocal 本地线程变量的目的与什么?
- 两个线程调用的是同一个 ThreadLocal 对象的引用都执行的 set 方法,其对于没一个线程而言是怎么保证其内部在调用 get 方法获取本地变量的时候不会发生混乱的呢?
首先第一个问题很好解决:
先了解一点,多线程环境下在操作同一个数据的时候最容易出现的问题是什么,对,就是在同一时间段内多个线程并发执行最终导致唯一的数据对象产生混乱,为了解决这一个问题,在多线程环境下引入了线程锁机制,保证一个数据对象在同一时间点只有一个线程去操作,保证数据的唯一性。但这样同样的也有一个问题的存在,锁机制简单的理解就是将之前的程序的执行由并行切换成了串行,这样如果并发量如果较高的情况下就会导致,程序的执行效率大幅度的降低。
其实对于这一问题 JDK 内部就提供一种解决方案,就是 ThreadLocal 线程本地变量。
通过字面意思就可以很好的理解,线程本地变量,就是对于每一个线程都有一个属于自己的存储在内存中的本地变量,也就是每一个线程内部的该变量都是独立的。
这里举个例子:
public class MyThreadLocalTestTwo {
//构造一个公有变量
private static String str = "";
public static void main(String[] args) throws InterruptedException {
//创建线程 A
Thread thread_A = new Thread(() -> {
if (str.equals("")) {
//线程 A 对变量 str 进行修改
str = "卡卡罗特";
System.out.println(Thread.currentThread() + "===>" + str);
}
}, "thread_A");
//创建线程 B
Thread thread_B = new Thread(() -> {
if (str.equals("")) {
//线程 B 对变量 str 进行修改
str = "特兰克斯";
System.out.println(Thread.currentThread() + "===>" + str);
}
}, "thread_B");
thread_A.start();
thread_B.start();
}
}
执行以上代码输出的结果是什么样呢?可能会出现以下结果:
Thread[thread_A,5,main]===>特兰克斯 Thread[thread_B,5,main]===>特兰克斯
Process finished with exit code 0
Thread[thread_B,5,main]===>特兰克斯
Process finished with exit code 0
Thread[thread_A,5,main]===>卡卡罗特
Process finished with exit code 0
这个与我们期望的理想输出的值存在差异,我们理想期望输出的为两个线程同时执行
线程 A 输出 卡卡罗特 ,线程 B 输出 特兰克斯
出现以上的结果就是应该共享变量在线程的并发情况下出现的混乱,我们可以使用 ThreadLocal 线程局部变量来处理
public class MyThreadLocalTestTwo {
//构造一个公有变量
private static String str = "";
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
//创建线程 A
Thread thread_A = new Thread(() -> {
threadLocal.set(str);
if (threadLocal.get().equals("")) {
//线程 A 对变量 str 进行修改
threadLocal.set("卡卡罗特");
System.out.println(Thread.currentThread() + "===>" + threadLocal.get());
}
}, "thread_A");
//创建线程 B
Thread thread_B = new Thread(() -> {
threadLocal.set(str);
if (threadLocal.get().equals("")) {
//线程 A 对变量 str 进行修改
threadLocal.set("特兰克斯");
System.out.println(Thread.currentThread() + "===>" + threadLocal.get());
}
}, "thread_B");
thread_A.start();
thread_B.start();
}
}
Thread[thread_B,5,main]===>特兰克斯 Thread[thread_A,5,main]===>卡卡罗特
Process finished with exit code 0