# study-java-thread **Repository Path**: tycaa/study-java-thread ## Basic Information - **Project Name**: study-java-thread - **Description**: 学习Java多线程的项目 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-01-11 - **Last Updated**: 2024-08-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 介绍 学习JAVA多线程的项目 ## Java多线程基础 ### 进程与线程 - 进程:即进行就是正在进行中的程序,程序执行的最小单元为进程,每个程序启动至少占用一个进程,但一个程序可以有多个进程。 - 线程:在进程里执行任务的最小单元,是具体执行任务的执行单元,一个进程里可有多个线程。 ### 线程的创建 线程的创建有两种方式 - **通过继承Tread类的方式进行创建** ```java public class Demo01 { public static void main(String[] args) { Thread t = new Demo01Thread(); t.start(); } } class Demo01Thread extends Thread { @Override public void run() { System.out.println("执行了线程"); } } ``` - **通过实现Runnable接口来创建线程** ```java public class Demo02 { public static void main(String[] args) { Thread t = new Thread(new Demo02Thread()); t.start(); } } class Demo02Thread implements Runnable { @Override public void run() { System.out.println("执行了线程"); } } ``` 总结:不管使用实现Thread类或者Runnable接口的方式都可以创建线程,但是使用继承Thread的方式有局限性,因为JAVA是单继承的,通过Runnable的方式则更加灵活,因为可以在实现线程接口的同时继承其它的业务类。 ### 线程的启动 线程的启动是调用线程类的start方法,必须要调用该方法才可以启动线程,直接调用run方法无法启动线程 ### 线程变量(概念) 自定义线程中,成员变量针对其他线程来说分为共享和不共享。 - **不共享:** ```java public class Demo03 { public static void main(String[] args) { Thread t = new Demo03Thread(); Thread t1 = new Demo03Thread(); t.start(); t1.start(); } } class Demo03Thread extends Thread { private int conut = 3; @Override public void run() { while(conut-- > 0) { System.out.println(Thread.currentThread().getName() + "--count:"+conut); } } } ``` 输出结果: ``` Thread-0--count:2 Thread-1--count:2 Thread-0--count:1 Thread-1--count:1 Thread-0--count:0 Thread-1--count:0 ``` 以不共享变量的方式变量是安全的,各个线程之间互不影响 - **共享变量:** ```java public class Demo04 { public static void main(String[] args) { Runnable r = new Demo04Thread(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); Thread t3 = new Thread(r); t1.start(); t2.start(); t3.start(); } } class Demo04Thread implements Runnable { private int conut = 6; @Override public void run() { while(conut-- > 0) { System.out.println(Thread.currentThread().getName() + "--count:"+conut); } } } ``` 输出结果: ``` Thread-1--count:4 Thread-2--count:3 Thread-0--count:4 Thread-2--count:1 Thread-1--count:2 Thread-0--count:0 ``` 在共享变量的线程中,该例子线程1和线程0同时读到了同样的数据,造成了数据的不安全,该问题叫做【非线程安全问题】,在下面的章节我们会讲到如何解决该问题 ### 线程常用方法 - **方法:Thread.currentThread();** 说明:该方法非常重要,在多线程执行过程中,可以获取到执行当前这段代码的活动的线程。 ```java public class Demo05 { public static void main(String[] args) { Thread t = new Demo05Thread(); t.start(); } } class Demo05Thread extends Thread { public Demo05Thread() { System.out.println("执行构造方法线程是:"+Thread.currentThread().getName()); } @Override public void run() { System.out.println("执行run方法线程是:"+Thread.currentThread().getName()); } } ``` 输出结果: ``` 执行构造方法线程是:main 执行run方法线程是:Thread-0 ``` 说明在上面程序代码执行过程中,构造方法是由main线程调用的,而run方法的具体逻辑是由Thread-0线程调用的。 - **方法:isAlive()** 说明:该方法用于判断线程是否处于存活状态,注意:在调用start()方法后在run()方法执行结束前线程都处于存活状态,不管在run方法执行过程中调用了stop()方法或者interrupt()都无法使其变成false ```java public class Demo06 { public static void main(String[] args) { Thread t = new Demo06Thread(); System.out.println("当前线程t的状态1:" + t.isAlive()); t.start(); System.out.println("当前线程t的状态2:" + t.isAlive()); } } class Demo06Thread extends Thread { public Demo06Thread() { System.out.println("执行构造方法线程是:" + Thread.currentThread().getName()); System.out.println("执行构造方法线程状态:" + Thread.currentThread().isAlive()); System.out.println("Demo06Thread线程状态:" + this.isAlive()); } @Override public void run() { System.out.println("执行run方法"); } } ``` 输出结果: ``` 执行构造方法线程是:main 执行构造方法线程状态:true Demo06Thread线程状态:false 当前线程t的状态1:false 当前线程t的状态2:true 执行run方法 ``` - **方法:sleep()** 说明:使当前线程休眠多少毫秒 - **方法:getId()** 说明:获取线程的唯一ID ### 停止线程的方法 停止线程在Java里面一共有三种常用的方法分别是:使用退出旗标、调用stop方法、调用interrupt方法,下面将分别介绍三种方式的使用方法。 - **使用退出旗标:** 推荐使用,该方法是退出线程最为安全的方法。 ```java public class Demo07 { public static void main(String[] args) throws InterruptedException { Demo07Thread t = new Demo07Thread(); t.start(); Thread.sleep(2);//睡眠2毫秒 t.stopThread(); } } class Demo07Thread extends Thread { private boolean flg = true; @Override public void run() { while (flg) { System.out.println("当前时间:"+System.currentTimeMillis()); } System.out.println("线程执行结束了"); } public void stopThread() { flg = false; } } ``` 输出结果: ``` 当前时间:1641902240509 线程执行结束了 ``` - **使用stop方法停止线程(不推荐使用)** ```java public class Demo08 { public static void main(String[] args) throws InterruptedException { User u = new User(); System.out.println("修改前user:"+u); Demo08Thread t = new Demo08Thread(u); t.start(); Thread.sleep(500);//在外面把线程以外结束掉 t.stop(); System.out.println("修改后user:"+u); } } class User { private String name = "a"; private String pwd = "aa"; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User [name=" + name + ", pwd=" + pwd + "]"; } } class Demo08Thread extends Thread { private User user; public Demo08Thread(User user) { this.user = user; } @Override public void run() { try { //先修改用户名,暂停五秒后再修改用户密码 user.setName("b"); Thread.sleep(5000); user.setPwd("bb"); } catch (InterruptedException e) { e.printStackTrace(); } catch(ThreadDeath e) { System.out.println("线程被意外停止了"); } System.out.println("线程执行结束了"); } } ``` 输出结果: ``` 修改前user:User [name=a, pwd=aa] 修改后user:User [name=b, pwd=aa] 线程被意外停止了 线程执行结束了 ``` 使用stop方法会马上停止线程,但是马上停止线程并抛出ThreadDeath错误会造成线程内部本身的业务逻辑遭到破坏,这也是该方法不建议使用的原因。 - **interrupt()方法停止线程** 严格来说interrupt()方法是中断线程,该方法如果只是单独调用不起任何作用,因为该方法只是给线程打上一个停止标记,并不会真正的去结束线程,必须要配合isInterrupted()或者Thread.interrupted()方法一起使用才会有作用。 - **isInterrupted()方法** 该方法一般是用于判断某个具体的线程实例是否被中断 ```java public class Demo09 { public static void main(String[] args) throws InterruptedException { Demo09Thread t = new Demo09Thread(); t.start(); Thread.sleep(50); t.interrupt(); System.out.println("t线程的interrupt状态:"+t.isInterrupted()); } } class Demo09Thread extends Thread { @Override public void run() { for (int i = 0; i < Integer.MAX_VALUE; i++) { if(this.isInterrupted()) { //在这里使用isInterrupted方法判断线程是否被终止 break; } System.out.println("当前i=" + i); } System.out.println("线程执行结束了"); } } ``` 输出结果: ``` ... 当前i=3884 当前i=3885 当前i=3886 当前i=3887 当前i=3888 当前i=3889 线程执行结束了 ``` - **Thread.interrupted()方法** Thread.interrupted()方法是用于判断当前活动线程是否被打上中断标记(也就是调用过interrupt()方法),需要注意的是该方法调用一次以后会重置线程的中断状态,第二次调用则会变为false ```java public class Demo10 { public static void main(String[] args) throws InterruptedException { Demo10Thread t = new Demo10Thread(); t.start(); Thread.sleep(50); t.interrupt(); } } class Demo10Thread extends Thread { @Override public void run() { for (int i = 0; i < Integer.MAX_VALUE; i++) { //因为在这里已经调用过一次interrupted()方法了,所以下面if判断的时候状态已经被重置为false,循环不会停止 //当然也有一定的概率刚刚执行过输出语句的时候程序终止了 进行判断的情况,但是理论就是这么个理论 System.out.println("interrupt状态:"+Thread.interrupted()); if(Thread.interrupted()) { break; } System.out.println("当前i=" + i); } System.out.println("线程执行结束了"); } } ``` 当线程中有sleep方法的时候,调用interrupt方法不管sleep方法有没有被执行到,都会立即抛出InterruptedException ```java public class Demo11 { public static void main(String[] args) throws InterruptedException { Demo11Thread t = new Demo11Thread(); t.start(); Thread.sleep(50); t.interrupt(); } } class Demo11Thread extends Thread { @Override public void run() { try { Thread.sleep(1000); System.out.println("执行了run方法"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程执行结束了"); } } ``` 输出结果: ```java java.lang.InterruptedException: sleep interrupted at java.base/java.lang.Thread.sleep(Native Method) at com.taoye.thread.Demo11Thread.run(Demo11.java:17) 线程执行结束了 ``` 通常在具体业务开发的时候,都会抛出异常让其他的线程能够捕获到 ```java public class Demo12 { public static void main(String[] args) throws InterruptedException { Demo12Thread t = new Demo12Thread(); t.start(); Thread.sleep(10); t.interrupt(); } } class Demo12Thread extends Thread { @Override public void run() { for (int i = 0; i < Integer.MAX_VALUE; i++) { if (this.isInterrupted()) { //线程被终止时抛出异常留给程序处理 throw new RuntimeException("线程被终止啦"); } System.out.println("当前i=" + i); } System.out.println("线程执行结束了"); } } ``` ### 暂停线程 - **使用suspend()方法暂停线程resume()方法恢复被暂停的线程** ```java public class Demo13 { public static void main(String[] args) throws InterruptedException { Demo13Thread t = new Demo13Thread(); t.start(); System.out.println("暂停前time:+"+System.currentTimeMillis()+";num:"+t.getNum()); t.suspend(); Thread.sleep(100); System.out.println("暂停后1time:+"+System.currentTimeMillis()+";num:"+t.getNum()); Thread.sleep(100); System.out.println("暂停后2time:+"+System.currentTimeMillis()+";num:"+t.getNum()); t.resume(); Thread.sleep(100); System.out.println("恢复后time:+"+System.currentTimeMillis()+";num:"+t.getNum()); } } class Demo13Thread extends Thread { private long num = 0; public long getNum() { return num; } @Override public void run() { while(true) { num++; } } } ``` - **不推荐使用该方法,是因为该方法有很多问题** - 问题1:容易造成死锁 ```java public class Demo14 { public static void main(String[] args) throws InterruptedException { Demo14Service t = new Demo14Service(); Thread a = new Thread(()->{ t.printString(); }); a.setName("A"); a.start(); Thread b = new Thread(()->{ t.printString(); }); b.setName("B"); b.start(); //如果这里唤醒被暂停的线程则可以继续执行 //Thread.sleep(1000); //a.resume(); } } /** * 假装这是个业务类 * @author Mr.Tao */ class Demo14Service{ public synchronized void printString() { System.out.println(Thread.currentThread().getName()+"假装执行了某个业务1"); if(Thread.currentThread().getName().equals("A")) { System.out.println("如果是A线程来我就不让它执行业务,且让它暂停"); Thread.currentThread().suspend(); //需要注意的是println方法也有同步锁,所以下面的代码也不会执行 System.out.println("如果是A线程来我就不让它执行业务,且让它暂停"); System.out.println("如果是A线程来我就不让它执行业务,且让它暂停"); } System.out.println(Thread.currentThread().getName()+"假装执行了某个业务2"); } } ``` 输出结果: ``` A假装执行了某个业务1 如果是A线程来我就不让它执行业务,且让它暂停 ``` 这里调用了业务类Demo14Service的方法printString但是这个方法是一个同步方法,只允许一个线程访问,在A线程进入该方法后暂停了,无法释放锁,就会造成该方法被锁定,且无法被其他的线程访问,造成死锁。 - 问题2:会造成公共对象的数据不一致问题 ```java public class Demo15 { public static void main(String[] args) throws InterruptedException { Demo15User u = new Demo15User(); System.out.println("修改前user:" + u); Thread a = new Thread(()->{ u.setName("A线程设置的名字"); Thread.currentThread().suspend(); u.setPwd("A线程设置的密码"); }); a.setName("A"); a.start(); Thread.sleep(10); System.out.println("A线程修改后user:" + u); Thread b = new Thread(()->{ //假装B线程要等待A的业务逻辑做完后执行的代码,结果A线程挂起了导致业务未执行完,下面的逻辑永远都不会成立 if(u.getPwd().equals("A线程设置的密码")) { u.setName("B线程设置的名字"); u.setPwd("B线程设置的密码"); } }); b.setName("B"); b.start(); Thread.sleep(10); System.out.println("B线程修改后user:" + u); } } class Demo15User { private String name = "a"; private String pwd = "aa"; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User [name=" + name + ", pwd=" + pwd + "]"; } } ``` 如上代码所示,原本B线程要等待A线程做完全部业务逻辑后才能执行特定的任务,但是因为A线程被暂停而导致了逻辑错误,无法执行以下的任务。 总结:不推荐使用暂停方法,除非特别熟练的情况下(有暂停就必须要唤醒),否则非常容易造成死锁和数据问题。 ### yield方法 该方法要结合线程的状态来看,是把当前CPU调度到的线程资源让给别的线程。简单来说就是在线程start以后,线程是处于等待被CPU调度的状态(ready),当CPU调度该线程以后,线程为running状态,yield方法就是把running状态改为ready状态,但是CPU下次有可能还是会调度到它,这是从操作系统的角度来说。在Java里面不明显,因为不管是running还是ready状态在Java里面都叫runnable。 ### 线程的状态(TODO) ### 线程的优先级 - 概念: - 在操作系统中,线程可以设置优先级,优先级高的线程可以得到更多的CPU资源,让线程调度器优先进行调用。在Java中,线程的优先级分为1-10个级别,取值也是1-10。需要注意的是:Java中线程的优先级具有继承性,会根据调用start()方法的线程进行取值。 - 方法: - setPriority(int priority); ```java public class Demo17 { public static void main(String[] args) { System.out.println("main线程的优先级为:"+Thread.currentThread().getPriority()); /* * 线程类提供了3个常量来对应优先级的取值 * Thread.MIN_PRIORITY = 1 代表优先级最小 * Thread.NORM_PRIORITY = 5 默认优先级,所有线程创建优先级都为5 * Thread.MAX_PRIORITY = 10 最大优先级 */ Thread.currentThread().setPriority(Thread.MAX_PRIORITY); System.out.println("main线程的优先级为:"+Thread.currentThread().getPriority()); Demo17Thread t = new Demo17Thread(); t.setName("A"); t.start(); } } class Demo17Thread extends Thread { @Override public void run() { System.out.println("A线程的优先级为:"+Thread.currentThread().getPriority()); } } ``` 输出结果: ``` main线程的优先级为:5 main线程的优先级为:10 A线程的优先级为:10 ``` 这里main线程启动了A线程,所以A线程的优先级与main线程一致。 ### 守护线程 - 概念 - 守护线程就是伴随用户线程存在的线程,当进程里面没有用户线程时,守护线程也会跟着销毁。 - 使用方法 - setDaemon(true) ```java public class Demo18 { public static void main(String[] args) throws InterruptedException { Demo18Thread t = new Demo18Thread(); t.setName("A"); t.setDaemon(true); t.start(); // Demo18Thread t2 = new Demo18Thread(); // t2.setName("B"); // t2.start(); Thread.sleep(3000); System.out.println("主线程结束了"); } } class Demo18Thread extends Thread { @Override public void run() { try { while (true) { System.out.println(this.getName() + "线程打印当前时间:" + System.currentTimeMillis()); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 上面例子中将A线程设置为守护线程,3000毫秒后主线程结束了,A线程也跟着销毁了,但如果放开B线程的注释,B线程为用户线程,主线程虽然结束了,但是进程里还有B这个用户线程存在,那守护线程也将继续执行。 ## 线程的同步(重要) ### 1.局部变量不存在线程安全问题 - 因为局部变量是私有变量,每次调用方法的时候都会被重新创建,所以即使在多线程环境下,也不存在脏读,即非线程安全问题。 ```java public class Demo19 { public static void main(String[] args) throws InterruptedException { Demo19Service service = new Demo19Service(); Demo19Thread t = new Demo19Thread(service); t.setName("A"); t.start(); Demo19Thread t2 = new Demo19Thread(service); t2.setName("B"); t2.start(); } } class Demo19Service { public void setNumber(String name) { //这个number是局部变量,每次调用该方法都会被重新初始化,不存在脏读 int number = 10; if(name.equals("A")) { //线程A先进来设置了以后 线程B立马修改number变量的值测试是否造成脏读 number = 100; System.out.println("线程A设置完成了,当前值为:"+number); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }else { number = 200; System.out.println("线程B设置完成了,当前值为:"+number); } System.out.println("线程"+name+"的值为:" + number); } } class Demo19Thread extends Thread { private Demo19Service service; public Demo19Thread(Demo19Service service) { this.service = service; } @Override public void run() { service.setNumber(getName()); } } ``` 输出结果: ``` 线程B设置完成了,当前值为:200 线程A设置完成了,当前值为:100 线程B的值为:200 线程A的值为:100 ``` 上面的代码演示了即使多个线程去调用同一个service,但是number是私有变量,所以也是线程安全的。 ### 2.成员变量非线程安全(脏读) - 成员变量不是线程安全的,会产生脏读问题 ```java public class Demo20 { public static void main(String[] args) throws InterruptedException { Demo20Service service = new Demo20Service(); Demo20Thread t = new Demo20Thread(service); t.setName("A"); t.start(); Demo20Thread t2 = new Demo20Thread(service); t2.setName("B"); t2.start(); } } class Demo20Service { //根据19的例子,将number设置为成员变量 private int number = 10; public void setNumber(String name) { if(name.equals("A")) { //线程A先进来设置了以后 线程B立马修改number变量的值测试是否造成脏读 number = 100; System.out.println("线程A设置完成了,当前值为:"+number); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } number = 200; System.out.println("线程B设置完成了,当前值为:"+number); } System.out.println("线程"+name+"的值为:" + number); } } class Demo20Thread extends Thread { private Demo20Service service; public Demo20Thread(Demo20Service service) { this.service = service; } @Override public void run() { service.setNumber(getName()); } } ``` 输出结果: ``` 线程A设置完成了,当前值为:100 线程B设置完成了,当前值为:200 线程B的值为:200 线程A的值为:200 ``` 这里的执行顺序是线程A先修改了number的值,此时number的值为100,然后线程A休息2秒不做任何操作,这时候线程B把number的值设置为了200,结果发现线程A里面的number的值也跟着变成200了,这就是脏读现象。 ### 3.Synchronized关键字的用法 #### 3.1 Synchronized同步方法 - 按照Demo20的例子,只需要在setNumber方法前面加上Synchronized关键字即可将该方法变成同步方法,解决脏读问题。 ```java public class Demo21 { public static void main(String[] args) throws InterruptedException { Demo21Service service = new Demo21Service(); Thread t = new Demo21Thread(service); t.setName("A"); t.start(); Thread t2 = new Demo21Thread(service); t2.setName("B"); t2.start(); } } class Demo21Service { //根据19的例子,将number设置为成员变量 private int number = 10; synchronized public void setNumber(String name) { if(name.equals("A")) { //线程A先进来设置了以后 线程B立马修改number变量的值测试是否造成脏读 number = 100; System.out.println("线程A设置完成了,当前值为:"+number); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } number = 200; System.out.println("线程B设置完成了,当前值为:"+number); } System.out.println("线程"+name+"的值为:" + number); } } class Demo21Thread extends Thread { private Demo21Service service; public Demo21Thread(Demo21Service service) { this.service = service; } @Override public void run() { service.setNumber(getName()); } } ``` 输出结果: ``` 线程A设置完成了,当前值为:100 线程A的值为:100 线程B设置完成了,当前值为:200 线程B的值为:200 ``` 该例子展示了加了Synchronized关键字后,该方法变成同步执行,先等A线程执行完以后,B线程再执行。 #### 3.2 Synchronized锁定对象 - 使用Synchronized关键字后,实际上锁定的并非某个方法,或者某个代码块,而是锁定的整个对象 ```java public class Demo22 { public static void main(String[] args) throws InterruptedException { Demo22Service service1 = new Demo22Service(); Demo22Service service2 = new Demo22Service(); Thread t = new Demo22Thread(service1); t.setName("A"); t.start(); Thread t2 = new Demo22Thread(service2); t2.setName("B"); t2.start(); } } class Demo22Service { private int number = 10; synchronized public void setNumber(String name) { if(name.equals("A")) { //线程A先进来设置了以后 线程B立马修改number变量的值测试是否造成脏读 number = 100; System.out.println("线程A设置完成了,当前值为:"+number); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } number = 200; System.out.println("线程B设置完成了,当前值为:"+number); } System.out.println("线程"+name+"的值为:" + number); } } class Demo22Thread extends Thread { private Demo22Service service; public Demo22Thread(Demo22Service service) { this.service = service; } @Override public void run() { service.setNumber(getName()); } } ``` 该例子展示了,当对象存在多个的时候,线程A和B分别使用的不同的对象,两者之间的同步锁并不能起到正确的效果。 而当一个对象里面存在多个Synchronized关键字修饰的方法时,它们也会按照顺序执行。 ```java public class Demo23 { public static void main(String[] args) throws InterruptedException { Demo23Service service = new Demo23Service(); Thread t = new Demo23ThreadA(service); t.setName("A"); t.start(); Thread t2 = new Demo23ThreadB(service); t2.setName("B"); t2.start(); } } class Demo23Service { synchronized public void fun1() { System.out.println(Thread.currentThread().getName()+"线程执行了fun1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("fun1结束"); } synchronized public void fun2() { System.out.println(Thread.currentThread().getName()+"线程执行了fun2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("fun2结束"); } } class Demo23ThreadA extends Thread { private Demo23Service service; public Demo23ThreadA(Demo23Service service) { this.service = service; } @Override public void run() { service.fun1(); } } class Demo23ThreadB extends Thread { private Demo23Service service; public Demo23ThreadB(Demo23Service service) { this.service = service; } @Override public void run() { service.fun2(); } } ``` 输出结果: ``` A线程执行了fun1 fun1结束 B线程执行了fun2 fun2结束 ``` #### 3.3 Synchronized自动释放锁 - 使用 Synchronized关键字修饰方法,在方法内部产生异常会自动释放锁,让其他的线程可以访问。 ```java package com.taoye.thread; public class Demo24 { public static void main(String[] args) throws InterruptedException { Demo24Service service = new Demo24Service(); Thread t = new Demo24Thread(service); t.setName("A"); t.start(); Thread t2 = new Demo24Thread(service); t2.setName("B"); t2.start(); } } class Demo24Service { synchronized public void fun() { if(Thread.currentThread().getName().equals("A")) { for (int i = 0; i < 100; i++) { System.out.println("线程A来访问fun了,i的值:"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(i == 2) { System.out.println("线程A抛出异常了"); throw new RuntimeException(); } } }else { System.out.println(Thread.currentThread().getName()+"线程来执行fun方法了"); } } } class Demo24Thread extends Thread { private Demo24Service service; public Demo24Thread(Demo24Service service) { this.service = service; } @Override public void run() { service.fun(); } } ``` 输出结果: ``` 线程A来访问fun了,i的值:0 线程A来访问fun了,i的值:1 线程A来访问fun了,i的值:2 线程A抛出异常了 Exception in thread "A" java.lang.RuntimeException at com.taoye.thread.Demo24Service.fun(Demo24.java:27) at com.taoye.thread.Demo24Thread.run(Demo24.java:44) B线程来执行fun方法了 ``` #### 3.4 Synchronized方法不能继承 - Synchronized方法不能继承,子类无法同时被锁定,如果在子类调用父类的方法,只有带Synchronized关键字的方法会被锁定。 ```java public class Demo25 { public static void main(String[] args) throws InterruptedException { Demo25ServiceChild service = new Demo25ServiceChild(); Thread t = new Demo25Thread(service); t.setName("A"); t.start(); Thread t2 = new Demo25Thread(service); t2.setName("B"); t2.start(); } } class Demo25Service { private int number = 10; public synchronized void setNumber(String name) { if(name.equals("A")) { //线程A先进来设置了以后 线程B立马修改number变量的值测试是否造成脏读 number = 100; System.out.println("线程A设置完成了,当前值为:"+number); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } number = 200; System.out.println("线程B设置完成了,当前值为:"+number); } System.out.println("线程"+name+"的值为:" + number); } } class Demo25ServiceChild extends Demo25Service { public void setNumber(String name) { System.out.println("子类调用了方法,线程:"+Thread.currentThread().getName()); super.setNumber(name); } } class Demo25Thread extends Thread { private Demo25ServiceChild service; public Demo25Thread(Demo25ServiceChild service) { this.service = service; } @Override public void run() { service.setNumber(getName()); } } ``` 输出结果: ``` 子类调用了方法,线程:B 子类调用了方法,线程:A 线程B设置完成了,当前值为:200 线程B的值为:200 线程A设置完成了,当前值为:100 线程A的值为:100 ``` 由此可见子类是几乎同时被A、B线程访问了,而带有synchronized关键字的父类方法,依然具有同步效果。如果想子类也持有锁,必须在子类方法上也加上synchronized关键字。 #### 3.5 Synchronized语句块 - 为什么要使用Synchronized语句块?其本质原因是因为Synchronized方法是串行的,比如addNumber方法加了Synchronized关键字,那么该方法只能同时被一个线程使用,那么耗时就是A线程+B线程执行方法的所有时间。 ```java public class Demo26 { public static void main(String[] args) throws InterruptedException { Demo26Service service = new Demo26Service(); Thread t = new Demo26ThreadA(service); t.setName("A"); t.start(); Thread t2 = new Demo26ThreadB(service); t2.setName("B"); t2.start(); Thread.sleep(8000); long start = Utils.start1 < Utils.start2 ? Utils.start1 : Utils.start2; long end = Utils.end1 > Utils.end2 ? Utils.end1 : Utils.end2; System.out.println("全部线程完成总耗时:" + (end - start) + "毫秒"); System.out.println("number的值为:" + service.getNumber()); } } class Utils { public static long start1; public static long end1; public static long start2; public static long end2; } class Demo26Service { private int number = 0; /** * 这里是模拟业务逻辑 */ public synchronized void addNumber() { try { System.out.println(Thread.currentThread().getName() + "线程开始操作了"); // 模拟需要同步的耗时操作,因为不同步number的值会造成脏读 for (int i = 0; i < 20; i++) { Thread.sleep(50); number += 10; } System.out.println(Thread.currentThread().getName() + "线程操作完了:值为:" + number); // 这是模拟另外一些不需要同步的耗时操作 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } public int getNumber() { return number; } } class Demo26ThreadA extends Thread { private Demo26Service service; public Demo26ThreadA(Demo26Service service) { this.service = service; } @Override public void run() { Utils.start1 = System.currentTimeMillis(); service.addNumber(); Utils.end1 = System.currentTimeMillis(); } } class Demo26ThreadB extends Thread { private Demo26Service service; public Demo26ThreadB(Demo26Service service) { this.service = service; } @Override public void run() { Utils.start2 = System.currentTimeMillis(); service.addNumber(); Utils.end2 = System.currentTimeMillis(); } } ``` 输出结果: ``` A线程开始操作了 A线程操作完了:值为:200 B线程开始操作了 B线程操作完了:值为:400 全部线程完成总耗时:6030毫秒 number的值为:400 ``` 这个例子我们模拟了同步的耗时语句累加number值,可以看到执行时间大概是循环的时间+其他业务逻辑处理的时间,由于该方法是串行的,所以耗时为6秒多一点。 在真实业务开发过程中,通常情况下我们只需要把需要保证线程安全的业务用Synchronized关键字包裹起来,而其他的地方并行处理来提升效率。 ```java public class Demo27 { public static void main(String[] args) throws InterruptedException { Demo27Service service = new Demo27Service(); Thread t = new Demo27ThreadA(service); t.setName("A"); t.start(); Thread t2 = new Demo27ThreadB(service); t2.setName("B"); t2.start(); Thread.sleep(8000); long start = Utils.start1 < Utils.start2 ? Utils.start1 : Utils.start2; long end = Utils.end1 > Utils.end2 ? Utils.end1 : Utils.end2; System.out.println("全部线程完成总耗时:" + (end - start) + "毫秒"); System.out.println("number的值为:" + service.getNumber()); } } class Demo27Service { private int number = 0; /** * 这里是模拟业务逻辑 */ public void addNumber() { try { System.out.println(Thread.currentThread().getName() + "线程开始操作了"); // 模拟需要同步的耗时操作,因为不同步number的值会造成脏读 synchronized (this) { //假如该业务方法只需要这一段代码需要同步执行 for (int i = 0; i < 20; i++) { Thread.sleep(50); number += 10; } System.out.println(Thread.currentThread().getName() + "线程操作完了:值为:" + number); } // 这是模拟另外一些不需要同步的耗时操作 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } public int getNumber() { return number; } } class Demo27ThreadA extends Thread { private Demo27Service service; public Demo27ThreadA(Demo27Service service) { this.service = service; } @Override public void run() { Utils.start1 = System.currentTimeMillis(); service.addNumber(); Utils.end1 = System.currentTimeMillis(); } } class Demo27ThreadB extends Thread { private Demo27Service service; public Demo27ThreadB(Demo27Service service) { this.service = service; } @Override public void run() { Utils.start2 = System.currentTimeMillis(); service.addNumber(); Utils.end2 = System.currentTimeMillis(); } } ``` 输出结果: ``` A线程开始操作了 B线程开始操作了 A线程操作完了:值为:200 B线程操作完了:值为:400 全部线程完成总耗时:4020毫秒 number的值为:400 ``` 在不影响正确逻辑的情况下,我们减小了synchronized关键字影响的范围,可以明显看出来效率大大提升了,所以在开发过程中,应尽量使用synchronized语句进行优化代码。 因为只有synchronized范围内的代码才是同步串行的,其他地方都是并行执行的,所以可以提高效率。 #### 3.6 Synchronized锁定不同对象 - 思考:如果一个service里面有2个方法,并且2方法是不同的逻辑,那当A线程调用fun1时,其他线程也不能调用fun2,这符合逻辑吗? - 显然这是不符合逻辑的,所以可以使用不同对象的锁来解决这个问题 ```java import cn.hutool.core.date.DateUtil; public class Demo28 { public static void main(String[] args) throws InterruptedException { Demo28Service service = new Demo28Service(); Thread t = new Demo28ThreadA(service); t.setName("A"); t.start(); Thread t1 = new Demo28ThreadA(service); t1.setName("B"); t1.start(); Thread t2 = new Demo28ThreadB(service); t2.setName("C"); t2.start(); Thread t3 = new Demo28ThreadB(service); t3.setName("D"); t3.start(); Thread.sleep(8000); } } class Demo28Service { private Object lock1 = new Object(); private Object lock2 = new Object(); /** * 这里是模拟业务逻辑1 */ public void fun1() { synchronized(lock1) { try { System.out.println(Thread.currentThread().getName()+"线程执行了fun1,当前时间:"+DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"线程fun1执行结束,当前时间:"+DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 这里是模拟业务逻辑2 */ public void fun2() { synchronized(lock2) { try { System.out.println(Thread.currentThread().getName()+"线程执行了fun2,当前时间:"+DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"线程fun2执行结束,当前时间:"+DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo28ThreadA extends Thread { private Demo28Service service; public Demo28ThreadA(Demo28Service service) { this.service = service; } @Override public void run() { service.fun1(); } } class Demo28ThreadB extends Thread { private Demo28Service service; public Demo28ThreadB(Demo28Service service) { this.service = service; } @Override public void run() { service.fun2(); } } ``` 输出结果: ``` A线程执行了fun1,当前时间:2022-01-25 22:47:33 C线程执行了fun2,当前时间:2022-01-25 22:47:33 A线程fun1执行结束,当前时间:2022-01-25 22:47:35 B线程执行了fun1,当前时间:2022-01-25 22:47:35 C线程fun2执行结束,当前时间:2022-01-25 22:47:35 D线程执行了fun2,当前时间:2022-01-25 22:47:35 B线程fun1执行结束,当前时间:2022-01-25 22:47:37 D线程fun2执行结束,当前时间:2022-01-25 22:47:37 ``` 以上代码可以看出A、B线程调用了同一个方法fun1,而C、D调用了同一个方法fun2,从执行结果可以看出来,A、C线程是并行执行的,且在A、C线程执行完成后C、D线程才开始执行,即A、C线程并行执行,A、B和C、D是串行执行的。这样就是解决同一个类当中有互不影响业务逻辑的方法都需要加锁的情况下,又不影响效率的方法。 #### 3.7 Synchronized语句块锁定逻辑 - 当synchronized语句块通过其他对象方式锁定的时候,需要注意的是,只有锁定线程访问的共同对象,才能使锁生效。例如下面的代码 ```java import java.util.ArrayList; import java.util.List; public class Demo29 { public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(); Thread t = new Demo29ThreadA(list); t.setName("A"); t.start(); Thread t1 = new Demo29ThreadB(list); t1.setName("B"); t1.start(); Thread.sleep(5000); System.out.println("list的大小:"+list.size()); } } class Demo29Service { private Object lock = new Object(); public void add(List list) { /** * 这里不管是使用this还是lock都无法锁定,因为不管是this对象代表的service实例, * 还是service实例里面的object对象,都是在线程里面new出来的,并非线程公共的对象 * 不是公共的对象是无法使用synchronized语句块来锁定对象的 */ synchronized(list) { try { if(list.size() < 1) { Thread.sleep(2000); list.add(1); } } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo29ThreadA extends Thread { private List list; public Demo29ThreadA(List list) { this.list = list; } @Override public void run() { //注意,在A、B线程里new了不同的service对象 Demo29Service service = new Demo29Service(); service.add(list); } } class Demo29ThreadB extends Thread { private List list; public Demo29ThreadB(List list) { this.list = list; } @Override public void run() { //注意,在A、B线程里new了不同的service对象 Demo29Service service = new Demo29Service(); service.add(list); } } ``` 输出结果: ``` list的大小:1 ``` 但是如果把锁定对象改为this或者lock,这里都无法保证线程安全,原因是因为锁定的对象不是多个线程访问的公共对象则无法形成同一把锁。注意看Demo29Service的add方法和A、B线程的run方法,只能是所有线程的公共对象才可以持有synchronized关键字的同一把锁,否则其实拿到的都不是同一把锁,自然也无法锁定成功。 #### 3.8 Synchronized类锁 - 使用synchronized关键字锁定静态方法的时候,实际上和使用synchronized锁定类的效果是一样的 ```java import cn.hutool.core.date.DateUtil; public class Demo30 { public static void main(String[] args) throws InterruptedException { Demo30Service service = new Demo30Service(); Thread t = new Demo30ThreadA(service); t.setName("A"); t.start(); Thread t1 = new Demo30ThreadB(); t1.setName("B"); t1.start(); } } class Demo30Service { /** * 这里是模拟业务逻辑1 */ public void fun1() { synchronized (Demo30Service.class) { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun1,当前时间:" + DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun1执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 这里是模拟业务逻辑2 */ public static synchronized void fun2() { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun2,当前时间:" + DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun2执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } class Demo30ThreadA extends Thread { Demo30Service service = new Demo30Service(); public Demo30ThreadA(Demo30Service service) { this.service = service; } @Override public void run() { //这里调用的不是静态方法 service.fun1(); } } class Demo30ThreadB extends Thread { @Override public void run() { Demo30Service.fun2(); } } ``` 输出结果: ``` A线程执行了fun1,当前时间:2022-01-26 23:24:04 A线程fun1执行结束,当前时间:2022-01-26 23:24:07 B线程执行了fun2,当前时间:2022-01-26 23:24:07 B线程fun2执行结束,当前时间:2022-01-26 23:24:09 ``` 由此可以看出,锁定的静态方法和锁定类是同一个锁,一样的可以起到效果,那么类锁跟对象锁有什么区别呢? ```java import cn.hutool.core.date.DateUtil; public class Demo31 { public static void main(String[] args) throws InterruptedException { Demo31Service service = new Demo31Service(); Thread t = new Demo31ThreadA(service); t.setName("A"); t.start(); Thread t1 = new Demo31ThreadB(); t1.setName("B"); t1.start(); } } class Demo31Service { /** * 这里非静态方法,锁定的是实例化的对象 */ public synchronized void fun1() { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun1,当前时间:" + DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun1执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 这里是静态方法,使用的是类锁 */ public static synchronized void fun2() { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun2,当前时间:" + DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun2执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } class Demo31ThreadA extends Thread { Demo31Service service = new Demo31Service(); public Demo31ThreadA(Demo31Service service) { this.service = service; } @Override public void run() { // 这里调用的不是静态方法 service.fun1(); } } class Demo31ThreadB extends Thread { @Override public void run() { Demo31Service.fun2(); } } ``` 输出结果: ``` A线程执行了fun1,当前时间:2022-01-26 23:27:57 B线程执行了fun2,当前时间:2022-01-26 23:27:57 B线程fun2执行结束,当前时间:2022-01-26 23:27:59 A线程fun1执行结束,当前时间:2022-01-26 23:27:59 ``` 可以看出类锁跟对象锁实际上并不是一个锁,类锁针对的是静态方法,对所有的静态方法都有效果,而对象锁只针对当前实例化出来的对象有效果。实际上不管是类锁还是对象锁,都是内置锁,只是类锁是抽象出来的一种说法,目的就是为了区分静态方法和普通成员方法的锁。 #### 3.9 Synchronized使用字符串作为锁 ```java package com.taoye.thread; import cn.hutool.core.date.DateUtil; public class Demo32 { public static void main(String[] args) throws InterruptedException { Demo32Service service = new Demo32Service(); Thread t = new Demo32ThreadA(service); t.setName("A"); t.start(); Thread t1 = new Demo32ThreadB(service); t1.setName("B"); t1.start(); } } class Demo32Service { /** * 这里使用字符串作为参数,用于使用锁 */ public void fun(String lock) { synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun1,当前时间:" + DateUtil.now()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun1执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo32ThreadA extends Thread { Demo32Service service; public Demo32ThreadA(Demo32Service service) { this.service = service; } @Override public void run() { // 这里调用的不是静态方法 service.fun("LOCK-A"); } } class Demo32ThreadB extends Thread { Demo32Service service; public Demo32ThreadB(Demo32Service service) { this.service = service; } @Override public void run() { service.fun("LOCK-A"); } } ``` 输出结果: ``` A线程执行了fun1,当前时间:2022-02-07 00:16:50 A线程fun1执行结束,当前时间:2022-02-07 00:16:53 B线程执行了fun1,当前时间:2022-02-07 00:16:53 B线程fun1执行结束,当前时间:2022-02-07 00:16:55 ``` 可以看到,我这里写了两个同样的字符串"LOCK-A",是可以正常锁定的,原因是因为采用字面值的方式创建一个字符串时JVM首先会去字符串池中查找是否存在"LOCK-A"这个对象,如果不存在,则在字符串常量池中创建"LOCK-A"这个对象,所以当线程B执行run方法时,常量池里面已经存在了"LOCK-A"这个对象,线程A和B的"LOCK-A"字符串都指向了同一个对象,所以可以正常锁定,但是这样的锁定是不安全的,如果在线程里面改变了这个字符串的值,则锁定就失效了。看看下面的例子: ```java package com.taoye.thread; import cn.hutool.core.date.DateUtil; public class Demo33 { public static void main(String[] args) throws InterruptedException { Demo33Service service = new Demo33Service(); Thread t = new Demo33ThreadA(service); t.setName("A"); t.start(); Thread.sleep(500); Thread t1 = new Demo33ThreadB(service); t1.setName("B"); t1.start(); } } class Demo33Service { private String lock = "LOCK-A"; /** * 这里使用字符串作为参数,用于使用锁 */ public void fun() { synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun1,当前时间:" + DateUtil.now()); //在这里A线程进来的时候,修改了锁定对象的值 if(Thread.currentThread().getName().equals("A")) { lock = "LOCK-B"; } Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun1执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo33ThreadA extends Thread { Demo33Service service; public Demo33ThreadA(Demo33Service service) { this.service = service; } @Override public void run() { // 这里调用的不是静态方法 service.fun(); } } class Demo33ThreadB extends Thread { Demo33Service service; public Demo33ThreadB(Demo33Service service) { this.service = service; } @Override public void run() { service.fun(); } } ``` 输出结果: ``` A线程执行了fun1,当前时间:2022-02-07 00:39:19 B线程执行了fun1,当前时间:2022-02-07 00:39:19 A线程fun1执行结束,当前时间:2022-02-07 00:39:21 B线程fun1执行结束,当前时间:2022-02-07 00:39:21 ``` 这里可以看出来,线程A和B几乎是并行执行的,这里的执行顺序是,A线程进入了同步代码块当中,把字符串的值改为了"LOCK-B",这里相当于在常量池重新创建了对象,这时候的lock引用所指向的"LOCK-B"和"LOCK-A"已经不是同一个对象了,所以当线程B启动的时候,实际上锁定的是"LOCK-B"这个对象,这就导致了锁失效,所以在实际使用过程中,使用String当锁定对象是不安全的。 #### 3.10 Synchronized使用自定义对象锁 ```java package com.taoye.thread; import cn.hutool.core.date.DateUtil; public class Demo34 { public static void main(String[] args) throws InterruptedException { Demo34User user = new Demo34User(); user.setUserName("AA-name"); user.setUserPass("AA-pass"); Demo34Service service = new Demo34Service(user); Thread t = new Demo34ThreadA(service); t.setName("A"); t.start(); Thread.sleep(500); Thread t1 = new Demo34ThreadB(service); t1.setName("B"); t1.start(); } } /** * 自定义user对象,用来作为锁 * @author Mr.Tao * */ class Demo34User { private String userName; private String userPass; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPass() { return userPass; } public void setUserPass(String userPass) { this.userPass = userPass; } } class Demo34Service { private Demo34User user; public Demo34Service(Demo34User user) { this.user = user; } public void fun() { /** * 这里使用自定义对象作为同步代码块的锁 */ synchronized (user) { try { System.out.println(Thread.currentThread().getName() + "线程执行了fun1,当前时间:" + DateUtil.now()); //在这里A线程进来的时候,修改了锁定对象的值 if(Thread.currentThread().getName().equals("A")) { // user = new Demo34User(); 如果在这里重新new了user对象,会导致锁失效 //只改变对象里面的属性,不影响锁 user.setUserName("BB-name"); user.setUserPass("BB-pass"); } Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "线程fun1执行结束,当前时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo34ThreadA extends Thread { Demo34Service service; public Demo34ThreadA(Demo34Service service) { this.service = service; } @Override public void run() { // 这里调用的不是静态方法 service.fun(); } } class Demo34ThreadB extends Thread { Demo34Service service; public Demo34ThreadB(Demo34Service service) { this.service = service; } @Override public void run() { service.fun(); } } ``` 输出结果: ``` A线程执行了fun1,当前时间:2022-02-07 00:41:44 A线程fun1执行结束,当前时间:2022-02-07 00:41:46 B线程执行了fun1,当前时间:2022-02-07 00:41:46 B线程fun1执行结束,当前时间:2022-02-07 00:41:48 ``` 在使用自定义类作为锁时,改变类的属性不会影响锁,只有改变类的指向才会导致锁失效,实际上这个原理跟使用String作为锁时原理相同。 #### Synchornized总结 - synchronized关键字是锁定的对象,不管是synchronized方法还是synchronized语句块锁定的都是整个对象,并非某段代码或者某个方法,只要是synchronized影响的地方且在同一个对象中,执行顺序都是串行的。 - synchronized同步代码块不推荐使用String对象来做对象锁。 ### 4.volatile关键字 - 在Java当中volatile关键字有两层意思 1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 解释:线程在启动以后,为了加快线程的执行效率,会把在公共堆栈中的变量(这里是service)都复制到一份到线程自己私有的堆栈中,不管是值的读取和更改都是在私有的堆栈当中操作,再到某个特定的时机同步到公共堆栈中,而这个时机是不可控的,一旦出现这种情况,线程将无法停止。 ```java public class Demo35 { public static void main(String[] args) throws InterruptedException { Demo35Service service = new Demo35Service(); service.setFlg(true); Thread t = new Demo35ThreadA(service); t.start(); //预计在3秒后停止线程 Thread.sleep(3000); service.setFlg(false); } } class Demo35Service { private boolean flg; public boolean isFlg() { return flg; } public void setFlg(boolean flg) { System.out.println(Thread.currentThread().getName()+"线程设置了flg:"+flg); this.flg = flg; } /** * 模拟业务方法 */ public void fun() { } } class Demo35ThreadA extends Thread { private Demo35Service service; //修改为下列方式,即可正确停止线程 // private volatile Demo35Service service; public Demo35ThreadA(Demo35Service service) { this.service = service; } @Override public void run() { while (service.isFlg()) { //这里假装调用某个业务方法 service.fun(); } } } ``` 上面的例子main线程计划在3秒后停止线程t,而实际运行过程中并不能正确的停止t线程,因为main线程更新的flg值是在公共堆栈中的,而t线程判断的值一直是在t线程的私有堆栈中的,t线程并不知道main线程更新了flg值,所以没有刷新,取到的一直是true,导致了线程无法停止。修改方法很简单,只需要按照代码当中注释的,加上volatile关键字就可以了。volatile关键字会让jvm强制从公共堆栈获取service的值。 2. 禁止进行指令重排序(TODO)。 ## 线程的互相通信 ### 1.使用轮询进行通信 - 概念:简单来讲就是线程间的互相控制 - 案例:首先我们设想一个场景,A线程每秒钟往List读取1条数据,B线程在当A线程数据读取了5条数据时结束,应该如何做? ```java import java.util.ArrayList; import java.util.List; public class Demo37 { public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(); Thread t = new Demo37ThreadA(list); t.setName("A"); t.start(); Thread t1 = new Demo37ThreadB(list); t1.setName("B"); t1.start(); } } class Demo37ThreadA extends Thread { private List list; public Demo37ThreadA(List list) { this.list = list; } @Override public void run() { for (int i = 0; i < 10; i++) { list.add(i); System.out.println("添加了数字:"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo37ThreadB extends Thread { private volatile List list; public Demo37ThreadB(List list) { this.list = list; } @Override public void run() { while(true) { if(list.size() == 5) { break; } } System.out.println("B线程结束"); } } ``` 输出结果: ``` 添加了数字:0 添加了数字:1 添加了数字:2 添加了数字:3 添加了数字:4 B线程结束 添加了数字:5 添加了数字:6 添加了数字:7 添加了数字:8 添加了数字:9 ``` 可以看到B线程利用while循环一直轮询list的大小可以正常完成该功能,但是这种方式有很大的弊端,因为无限循环的次数很快,可能在一秒钟内要循环很多次,造成CPU资源的浪费,但是如果给B线程设置了间隔时间,可能又无法在第一时间内停止掉B线程。所以这种方式虽然可以完成功能,但是不可取。 ### 2.wait/notify机制 #### 2.1. 基本使用 - 概念:【wait/notify机制】即等待通知机制,wait/notify方法是搭配使用的,且必须在同步代码块或者同步方法里面才能使用,因为wait/notify方法是Object类的方法,必须要拿到对象锁才能执行,且notify唤醒的wait线程必须是同一个锁所锁定的线程。 - 案例:还是以A线程每秒钟往List读取1条数据,B线程在当A线程数据读取了5条数据时结束这个案例为例子,修改上面的代码如下: ```java import java.util.ArrayList; import java.util.List; public class Demo38 { public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(); Thread t1 = new Demo38ThreadB(list); t1.setName("B"); t1.start(); Thread t = new Demo38ThreadA(list); t.setName("A"); t.start(); } } class Demo38ThreadA extends Thread { private List list; public Demo38ThreadA(List list) { this.list = list; } @Override public void run() { synchronized(list) { try { for (int i = 0; i < 10; i++) { list.add(i); System.out.println("添加了数字:" + i); if (list.size() == 5) { System.out.println("A线程已经添加了5个元素,通知B线程结束"); list.notify(); } Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo38ThreadB extends Thread { private volatile List list; public Demo38ThreadB(List list) { this.list = list; } @Override public void run() { try { synchronized (list) { System.out.println("我是线程B,我等A线程通知我结束哦~"); list.wait(); System.out.println("我是线程B,我现在结束运行了。"); } } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 输出结果: ``` 我是线程B,我等A线程通知我结束哦~ 添加了数字:0 添加了数字:1 添加了数字:2 添加了数字:3 添加了数字:4 A线程已经添加了5个元素,通知B线程结束 添加了数字:5 添加了数字:6 添加了数字:7 添加了数字:8 添加了数字:9 我是线程B,我现在结束运行了。 ``` 在这个案例中有几个需要注意的几个点: 1.wait/notify方法需要共用同一把锁,且必须在同步方法中执行。 2.wait方法会释放锁,notify方法不会释放锁。这一条非常重要。这里为什么要先启动B线程,因为A和B线程公用了同一把锁(list对象),如果先启动A线程,那A线程会先执行同步代码块里面的内容,等A线程的同步代码块内容执行完后才会进入B线程同步代码块当中的wait,这个时候已经没有线程可以唤醒B线程了,这种情况叫【通知过早】。但是如果先启动B线程,在执行到wait方法的时候会释放锁,这样A线程拿到锁以后执行添加值操作,添加到5个后再通知唤醒B线程,才可以正常运行。且运行结果可以看出来,即使添加了5个元素后发出了唤醒通知,B线程依然没有立即执行,而是等A线程执行完毕后释放了锁才可以继续执行。 #### 2.2 等待中调用interrupt()方法 ```java import java.util.ArrayList; import java.util.List; /** * 线程在等待中被调用了interrupt()方法 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo39 { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t = new Demo39ThreadA(lock); t.setName("A"); t.start(); Thread.sleep(2000); t.interrupt(); } } class Demo39ThreadA extends Thread { private Object lock; public Demo39ThreadA(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { try { System.out.println("我是线程A,现在开始进入等待状态了"); lock.wait(); System.out.println("我是线程A,现在等待结束了"); } catch (InterruptedException e) { System.out.println("我是线程A,现在被意外终止等待了"); e.printStackTrace(); } } } } ``` 输出结果: ``` 我是线程A,现在开始进入等待状态了 我是线程A,现在被意外终止等待了 java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.taoye.thread.Demo39ThreadA.run(Demo39.java:34) ``` 线程在等待中如果遇到了interrupt()会立即结束等待状态。 #### 2.3 只唤醒一个线程或所有线程 ```java /** * 使用notify唤醒一个线程或者使用notifyAll唤醒多个线程 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo40 { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t = new Demo40ThreadA(lock); t.setName("A"); t.start(); Thread t2 = new Demo40ThreadA(lock); t2.setName("B"); t2.start(); Thread t3 = new Demo40ThreadA(lock); t3.setName("C"); t3.start(); Thread.sleep(2000); Thread t4 = new Demo40ThreadB(lock); t4.setName("唤醒线程"); t4.start(); } } class Demo40ThreadA extends Thread { private Object lock; public Demo40ThreadA(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { try { System.out.println("我是线程" + Thread.currentThread().getName() + ",现在开始进入等待状态了"); lock.wait(); System.out.println("我是线程" + Thread.currentThread().getName() + ",现在等待结束了"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo40ThreadB extends Thread { private Object lock; public Demo40ThreadB(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { //调用一次notify()方法可随机唤醒一个等待中的线程 //lock.notify(); //唤醒全部线程要使用notifyAll()方法 lock.notifyAll(); } } } ``` 输出结果: ``` 我是线程A,现在开始进入等待状态了 我是线程C,现在开始进入等待状态了 我是线程B,现在开始进入等待状态了 我是线程B,现在等待结束了 我是线程C,现在等待结束了 我是线程A,现在等待结束了 ``` #### 2.4 设置等待时间 wait方法可以设置等待时间,设置了等待时间以后,线程会自动唤醒。 ```java import cn.hutool.core.date.DateUtil; /** * 设置wait等待时间 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo41 { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t = new Demo41ThreadA(lock); t.setName("A"); t.start(); Thread.sleep(5000); Thread t2 = new Demo41ThreadB(lock); t2.setName("唤醒线程"); t2.start(); } } class Demo41ThreadA extends Thread { private Object lock; public Demo41ThreadA(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { try { System.out.println("我是线程" + Thread.currentThread().getName() + ",现在开始等待3秒,时间:" + DateUtil.now()); lock.wait(3000); System.out.println("我是线程" + Thread.currentThread().getName() + ",现在等待结束了,时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo41ThreadB extends Thread { private Object lock; public Demo41ThreadB(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("我是唤醒线程,现在开始唤醒,时间:" + DateUtil.now()); lock.notify(); System.out.println("我是唤醒线程,唤醒完成,时间:" + DateUtil.now()); } } } ``` 输出结果: ``` 我是线程A,现在开始等待3秒,时间:2022-04-04 11:48:31 我是线程A,现在等待结束了,时间:2022-04-04 11:48:34 我是唤醒线程,现在开始唤醒,时间:2022-04-04 11:48:36 我是唤醒线程,唤醒完成,时间:2022-04-04 11:48:36 ``` 该例子可以看出来3秒钟以后线程A自动唤醒了,开始执行下面的代码,而并没有等到唤醒线程调用notify方法来进行唤醒。 #### 2.5 解决通知过早问题 在2.1章节说过,如果先唤醒后等待,这样等待中的线程就没有可以唤醒它的线程了,将会一直处于等待状态,这种情况叫【通知过早】,要解决这个问题只需要保证需要等待的线程一定要先执行即可,如果先唤醒了则不去执行等待。 ```java import cn.hutool.core.date.DateUtil; /** * 解决通知过早的问题 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo42 { public static void main(String[] args) throws InterruptedException { Demo42Service lock = new Demo42Service(); Thread t = new Demo42ThreadA(lock); t.setName("A"); Thread t2 = new Demo42ThreadB(lock); t2.setName("唤醒线程"); //执行通知过早逻辑 t2.start(); Thread.sleep(100); t.start(); } } class Demo42Service { /** * 这里模拟业务逻辑如果没有处理完成则不允许等待 */ private boolean canWait = true; public boolean isCanWait() { return canWait; } public void setCanWait(boolean canWait) { this.canWait = canWait; } } class Demo42ThreadA extends Thread { private Demo42Service lock; public Demo42ThreadA(Demo42Service lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { try { //判断是否可以进入等待 if (lock.isCanWait()) { System.out.println("我是线程" + Thread.currentThread().getName() + ",现在开始等待,时间:" + DateUtil.now()); lock.wait(); System.out.println("我是线程" + Thread.currentThread().getName() + ",现在等待结束了,时间:" + DateUtil.now()); } } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo42ThreadB extends Thread { private Demo42Service lock; public Demo42ThreadB(Demo42Service lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("我是唤醒线程,现在开始唤醒,时间:" + DateUtil.now()); //唤醒线程业务处理异常,A线程不用等待 lock.setCanWait(false); lock.notify(); System.out.println("我是唤醒线程,唤醒完成,时间:" + DateUtil.now()); } } } ``` #### 2.6 单生产消费者模型 ##### 2.6.1 单生产者和单消费者 ```java import java.util.ArrayList; import java.util.List; /** * 生产消费者模式 *

* 本例子模拟从文件读入数据,A线程模拟读入文件,读入100条由B线程写入数据库 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo43 { public static void main(String[] args) throws InterruptedException { Demo43Service service = new Demo43Service(); Thread t = new Demo43Provider(service); t.setName("生产者-A"); Thread t2 = new Demo43Consumer(service); t2.setName("消费者-A"); t.start(); t2.start(); } } class Demo43Service { /** * 模拟读入数据的容器 */ private List list = new ArrayList<>(); public List getList() { return list; } public void read() { for (int i = 0; i < 100; i++) { list.add(i); } } public void clear() { list.clear(); } } /** * 提供者线程,用于提供消费数据 */ class Demo43Provider extends Thread { private Demo43Service service; public Demo43Provider(Demo43Service service) { this.service = service; } @Override public void run() { synchronized (service) { try { while (true) { //如果容器里面还有数据没有消费,则等待消费者消费 if (service.getList().size() > 0) { System.out.println(Thread.currentThread().getName() + "开始等待消费 "); service.wait(); } else { //如果里面没有数据,则生产数据,并唤醒消费者,让消费者消费 System.out.println(Thread.currentThread().getName() + "开始生产数据 "); service.read(); service.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 消费者线程,模拟拿到生产者生产的数据并存到数据库 */ class Demo43Consumer extends Thread { private Demo43Service service; public Demo43Consumer(Demo43Service service) { this.service = service; } @Override public void run() { synchronized (service) { try { while (true) { //如果容器里面没有可以消费的数据,则等待生产者生产 if (service.getList().size() == 0) { System.out.println(Thread.currentThread().getName() + "开始等待生产 "); service.wait(); } else { //如果里面有数据,则模拟保存到数据库,并清空,然后让生产者继续生产数据 System.out.println(Thread.currentThread().getName() + "开始消费数据,一共消费:" + service.getList().size() + "条数据到数据库 "); service.clear(); service.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 输出结果: ``` 消费者-A开始等待生产 生产者-A开始生产数据 生产者-A开始等待消费 消费者-A开始消费数据,一共消费:100条数据到数据库 消费者-A开始等待生产 生产者-A开始生产数据 生产者-A开始等待消费 消费者-A开始消费数据,一共消费:100条数据到数据库 消费者-A开始等待生产 生产者-A开始生产数据 生产者-A开始等待消费 消费者-A开始消费数据,一共消费:100条数据到数据库 消费者-A开始等待生产 生产者-A开始生产数据 生产者-A开始等待消费 消费者-A开始消费数据,一共消费:100条数据到数据库 消费者-A开始等待生产 生产者-A开始生产数据 生产者-A开始等待消费 消费者-A开始消费数据,一共消费:100条数据到数据库 ...... ``` ##### 2.6.2 多生产者和多消费者 ```java import java.util.ArrayList; import java.util.List; /** * 生产消费者模式 *

* 本例子模拟从文件读入数据,A线程模拟读入文件,读入100条由B线程写入数据库 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo44 { public static void main(String[] args) throws InterruptedException { Demo44Service service = new Demo44Service(); int size = 2; for (int i = 0; i < size; i++) { char name = (char) ('A' + i); Thread t = new Demo44Provider(service); t.setName("生产者-" + name); Thread t2 = new Demo44Consumer(service); t2.setName("消费者-" + name); t.start(); t2.start(); } } } class Demo44Service { /** * 模拟读入数据的容器 */ private List list = new ArrayList<>(); public List getList() { return list; } public void read() { for (int i = 0; i < 100; i++) { list.add(i); } } public void clear() { list.clear(); } } /** * 提供者线程,用于提供消费数据 */ class Demo44Provider extends Thread { private Demo44Service service; public Demo44Provider(Demo44Service service) { this.service = service; } @Override public void run() { while (true) { synchronized (service) { try { //如果容器里面还有数据没有消费,则等待消费者消费 if (service.getList().size() > 0) { System.out.println(Thread.currentThread().getName() + "开始等待消费 "); service.wait(); } else { //如果里面没有数据,则生产数据,并唤醒消费者,让消费者消费 System.out.println(Thread.currentThread().getName() + "开始生产数据 "); service.read(); service.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } /** * 消费者线程,模拟拿到生产者生产的数据并存到数据库 */ class Demo44Consumer extends Thread { private Demo44Service service; public Demo44Consumer(Demo44Service service) { this.service = service; } @Override public void run() { while (true) { synchronized (service) { try { //如果容器里面没有可以消费的数据,则等待生产者生产 if (service.getList().size() == 0) { System.out.println(Thread.currentThread().getName() + "开始等待生产 "); service.wait(); } else { //如果里面有数据,则模拟保存到数据库,并清空,然后让生产者继续生产数据 System.out.println(Thread.currentThread().getName() + "开始消费数据,一共消费:" + service.getList().size() + "条数据到数据库 "); service.clear(); service.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } ``` 注意,在多生产消费者的情况下,要使用notifyAll()方法唤醒所有的线程,不然会造成wait假死的问题。因为notify()方法一次只能唤醒1个线程,假如唤醒的顺序是 消费者B->消费者A->生产者A->生产者B则没有线程去唤醒了,会造成假死。 ### 3.join方法 #### 3.1 基本使用 ```java /** * Join方法的基本使用 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo45 { public static void main(String[] args) throws InterruptedException { Demo45Thread t = new Demo45Thread(); t.setName("A"); t.start(); t.join(); System.out.println("main线程结束了"); } } /** * 提供者线程,用于提供消费数据 */ class Demo45Thread extends Thread { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "开始启动 "); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "结束了 "); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 输出结果: ``` A开始启动 A结束了 main线程结束了 ``` 没有使用join方法的时候,应该是主线程先结束,然后才是子线程结束,使用了join方法以后相当于是把线程拿到当前线程来执行,等待线程里面的内容执行完成后继续执行当前线程后面的内容。 #### 3.2 join方法和sleep方法的区别 ```java import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.RandomUtil; /** * join方法和sleep方法的区别 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo46 { public static void main(String[] args) throws InterruptedException { Demo46ThreadA t = new Demo46ThreadA(); t.setName("A"); Thread t2 = new Demo46ThreadB(t); t2.setName("B"); Thread t3 = new Demo46ThreadC(t); t3.setName("C"); t2.start(); //为了预防t3先拿到锁执行完毕,先睡眠一下确保t2先执行 Thread.sleep(200); t3.start(); } } class Demo46ThreadA extends Thread { @Override public void run() { try { System.out.println("我是线程" + Thread.currentThread().getName() + ",开始时间:" + DateUtil.now()); Thread.sleep(2000); System.out.println("我是线程" + Thread.currentThread().getName() + ",结束时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 这里不是静态方法,使用的锁跟对象锁是同一个锁 */ synchronized public void fun() { try { Thread.sleep(2000); System.out.println("fun方法执行时间:" + DateUtil.now()); } catch (InterruptedException e) { e.printStackTrace(); } } } class Demo46ThreadB extends Thread { private Demo46ThreadA t; public Demo46ThreadB(Demo46ThreadA t) { this.t = t; } @Override public void run() { synchronized (t) { try { System.out.println("我是线程" + Thread.currentThread().getName() + ",开始时间:" + DateUtil.now()); t.start(); //使用sleep方法会阻塞 //Thread.sleep(3000); //使用join方法会释放锁 t.join(); System.out.println("我是线程" + Thread.currentThread().getName() + ",开始执行耗时操作" + DateUtil.now()); //模拟耗时操作 for (int i = 0; i < Integer.MAX_VALUE; i++) { String s = new String(); String s1 = RandomUtil.randomString(9); } System.out.println("我是线程" + Thread.currentThread().getName() + ",结束时间:" + DateUtil.now()); } catch (Exception e) { e.printStackTrace(); } } } } class Demo46ThreadC extends Thread { private Demo46ThreadA t; public Demo46ThreadC(Demo46ThreadA t) { this.t = t; } @Override public void run() { System.out.println("我是线程" + Thread.currentThread().getName() + ",开始时间:" + DateUtil.now()); t.fun(); System.out.println("我是线程" + Thread.currentThread().getName() + ",结束时间:" + DateUtil.now()); } } ``` 输出结果: ``` 我是线程B,开始时间:2022-04-04 22:08:40 我是线程A,开始时间:2022-04-04 22:08:40 我是线程C,开始时间:2022-04-04 22:08:40 我是线程A,结束时间:2022-04-04 22:08:42 fun方法执行时间:2022-04-04 22:08:42 我是线程C,结束时间:2022-04-04 22:08:42 我是线程B,开始执行耗时操作2022-04-04 22:08:42 我是线程B,结束时间:2022-04-04 22:12:20 ``` sleep方法会阻塞线程,会一直持有锁,而join方法底层是调用的wait方法,会释放掉锁。 #### 3.3 join方法和interrupt方法 ```java import cn.hutool.core.util.RandomUtil; /** * join方法遇到了interrupt方法 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo47 { public static void main(String[] args) throws InterruptedException { Demo47ThreadB t2 = new Demo47ThreadB(); Thread t3 = new Demo47ThreadC(t2); t2.start(); t3.start(); } } class Demo47ThreadA extends Thread { @Override public void run() { //模拟耗时操作 System.out.println("A开始耗时操作了"); for (int i = 0; i < Integer.MAX_VALUE; i++) { String s = new String(); String s1 = RandomUtil.randomString(9); } System.out.println("A结束耗时操作了"); } } class Demo47ThreadB extends Thread { @Override public void run() { try { Demo47ThreadA t = new Demo47ThreadA(); t.start(); t.join(); System.out.println("B正常结束了"); } catch (InterruptedException e) { System.out.println("B异常结束了"); e.printStackTrace(); } } } class Demo47ThreadC extends Thread { private Demo47ThreadB t; public Demo47ThreadC(Demo47ThreadB t) { this.t = t; } @Override public void run() { t.interrupt(); } } ``` 输出结果: ``` B异常结束了 A开始耗时操作了 java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Thread.join(Thread.java:1245) at java.lang.Thread.join(Thread.java:1319) at com.taoye.thread.Demo47ThreadB.run(Demo47.java:44) ``` 可以看到C线程中断了B线程,B线程异常结束了,但是A线程不受影响仍然在执行,所以interrupt方法只中断调用的线程,而其他线程不受影响。 ### 4.ThreadLocal的使用 ThreadLocal类主要用于来做数据隔离的,如果一个service当中希望有一个值只能被当前线程读写,就可以使用ThreadLocal类进行包装。 ```java /** * ThreadLocal的使用 * * @author : Mr.Tao * @version : 1.0 * @date : 2022/4/4 10:31 **/ public class Demo48 { public static void main(String[] args) throws InterruptedException { Demo48Service service = new Demo48Service(); Demo48ThreadA t1 = new Demo48ThreadA(service); Demo48ThreadB t2 = new Demo48ThreadB(service); t1.start(); t2.start(); } } class Demo48Service { private String v1; private ThreadLocal v2 = new ThreadLocal<>(); public String getV1() { return v1; } public void setV1(String v1) { this.v1 = v1; } public String getV2() { return v2.get(); } public void setV2(String str) { v2.set(str); } } class Demo48ThreadA extends Thread { private Demo48Service service; public Demo48ThreadA(Demo48Service service) { this.service = service; } @Override public void run() { try { service.setV1("A线程修改的V1"); Thread.sleep(500); service.setV2("A线程修改的V2"); System.out.println("A线程结果:v1=" + service.getV1() + ",v2=" + service.getV2()); } catch (InterruptedException e) { e.printStackTrace(); } } } class Demo48ThreadB extends Thread { private Demo48Service service; public Demo48ThreadB(Demo48Service service) { this.service = service; } @Override public void run() { try { Thread.sleep(200); service.setV1("B线程修改的V1"); service.setV2("B线程修改的V2"); System.out.println("B线程结果:v1=" + service.getV1() + ",v2=" + service.getV2()); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 输出结果: ``` B线程结果:v1=B线程修改的V1,v2=B线程修改的V2 A线程结果:v1=B线程修改的V1,v2=A线程修改的V2 ``` 由此可见如果非ThreadLocal的变量是所有线程共同变量,大家都可以修改读取的,但是如果是ThreadLocal变量,则是属于每个线程独立的变量,每个线程读取到的是每个线程独立的值。 ## 线程的锁(TODO)