Java多线程第二篇-线程的常用方法和线程安全

[复制链接]
发表于 2025-11-23 17:57:30 | 显示全部楼层 |阅读模式
一.线程的常用方法

1.线程制止

1.1通过成员对线程举行制止

   变量创建必要以static修饰而且变量成员必要以final修饰大概非final修饰(稳固的常量)才气进入到run方法中,但是假如你想制止的话,你肯定要修改它的值,以是我们必须要界说一个外部成员。
  1. public class Demo7 {
  2.     //这里的成员默认false
  3.     private static boolean isQuit;
  4.     public static void main(String[] args) throws InterruptedException {
  5.         Thread thread=new Thread(()->{
  6.             //不为true进入循环
  7.            while(!isQuit){
  8.                System.out.println("Thread is Running");
  9.                try {
  10.                    //thread线程睡眠1000
  11.                    Thread.sleep(1000);
  12.                } catch (InterruptedException e) {
  13.                    throw new RuntimeException(e);
  14.                }
  15.            }
  16.            //这里跳出循环后打印
  17.             System.out.println("Thread closing");
  18.         });
  19.         thread.start();
  20.         //这里进入start后线程和主线程同时运行,程序继续往下走
  21.         //这里sleep睡眠5000ms为条件,达成条件后继续往下执行
  22.         Thread.sleep(5000);
  23.         isQuit=true;
  24.         //这里睡眠2000在执行下面代码,这时候打印为TERMINATED
  25.             Thread.sleep(2000);
  26.         System.out.println(thread.getState());
  27.     }
  28. }
复制代码

1.2通过Thread当地方法制止

   通过上述我们明确假如我们创建,可以看到缺点。
1.必要手动创建变量。
2.当线程内部在sleep的时间,主线程修改为ture,这时间竣事了循环,但是新的线程内部无法及时的做出相应。
这时间我们就要通过Java中的当地方法来举行线程的制止。
  1. public class Demo7 {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread thread=new Thread(()->{
  4.           //这里的currentThread方法可以获得当前线程的实例,但是无法直接写成thread,这时候我们的thread还没有构造完成。
  5.             //isInterrupted是标志位置,判断线程是否结束。
  6.             //isInterrupted 默认为false
  7.            while(!Thread.currentThread().isInterrupted()){
  8.                System.out.println("Thread is running");
  9.                try {
  10.                    Thread.sleep(1000);
  11.                } catch (InterruptedException e) {
  12.                    e.printStackTrace();
  13.                }
  14.            }
  15.             System.out.println("结束线程");
  16.         });
  17.         thread.start();
  18.         Thread.sleep(3000);
  19.         //这里修改为true,循环会结束?
  20.         thread.interrupt();
  21.         Thread.sleep(2000);
  22.         System.out.println("Thread exit");
  23.     }
  24. }
复制代码

   这里我们可以看到线程照旧在实行,为什么呢?
正常来说sleep会进入休眠,此处给到interrupt改为true之后就可以使用sleep内部触发一个非常,提前被唤醒,扫除该实例的标志位。
这里扫除该实例标志位后假如不使用break大概throw出非常则会继承实行,可以将选择权限交给开辟者举行选择,我们可以通过break来跳出大概直接抛出。

  

  • 以下修改的部分
  1. while (!Thread.currentThread().isInterrupted()) {
  2.                 System.out.println("Thread is working");
  3.                 try {
  4.                     Thread.sleep(1000);
  5.                 } catch (InterruptedException e) {
  6.                     throw new RuntimeException();
  7.                     //或者直接通过上述代码的打印异常增加一个break,break前可以增加一些想要添加的代码。
  8.                     //break;
  9.                 }
  10.             }
  11.         });
复制代码

2.线程的期待join

   join 是实现线程期待的结果,在主线程调用thread.join(),此时是主线程期待thread线程先竣事。
让一个线程,期待另一个线程实行竣事,在继承实行,本质上就是控制线程竣事的序次的方式。
  2.1不增长参数

  1. public class Demo7 {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread thread =new Thread(()->{
  4.            while(!Thread.currentThread().isInterrupted()){
  5.                System.out.println("Thread is running:1");
  6.                try {
  7.                    Thread.sleep(1000);
  8.                } catch (InterruptedException e) {
  9.                    break;
  10.                }
  11.            }
  12.         });
  13.         thread.start();
  14.             Thread.sleep(3000);
  15.         thread.interrupt();
  16.         //这里的join就是阻断,让其执行完成后在继续执行其他线程
  17.             thread.join();
  18.         for(int i=0;i<3;i++){
  19.             System.out.println("Thread is running:2");
  20.             Thread.sleep(1000);
  21.         }
  22.     }
  23. }
复制代码

2.2增长参数

   当增长参数后我们会限定join的期待时长,假如凌驾该期待时长则继承实行
  1. public class Demo7 {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread thread = new Thread(() -> {
  4.           for (int i=0;i<10;i++) {
  5.                 System.out.println("Thread is running:1");
  6.                 try {
  7.                     Thread.sleep(1000);
  8.                 } catch (InterruptedException e) {
  9.                     throw new RuntimeException();
  10.                 }
  11.             }
  12.         });
  13.         thread.start();
  14. //           这里等待thread执行完成则主线程开始执行
  15. //            如果join带参数后,则是需要等待的时间,如果等待时间过长我们就需要将主线程继续进行
  16.         thread.join(3000);
  17.         for(int i=0;i<3;i++){
  18.             System.out.println("Thread is running:2");
  19.         }
  20.     }
  21. }
复制代码

2.3线程的休眠时间

   线程的sleep是有毛病的,由于线程的调治开销也是必要时间,以是大概不是很正确,比力随机。
  1.   public static void main(String[] args) throws InterruptedException {
  2.       
  3.         long start = System.currentTimeMillis();
  4.         Thread.sleep(1000);
  5.         long end = System.currentTimeMillis();
  6.         System.out.println("开始和结束的时差(以ms为单位):"+(end-start));
  7.     }
复制代码


二.线程的其他状态

线程状态分析NEWTHREAD的对象已经存在,start方法还没调用,没有创建新的线程TERMINATEDTHREAD的对象仍然存在,内核中的线程已经烧毁RUNNABLE停当状态(线程已经在cpu上实行了/线程正在列队期待在cpu实行)TIMED_WAITING壅闭状态,由于sleep这种固定时间的方式产生的壅闭WAITING壅闭状态,由于wait这种不固定时间的方式产生的壅闭BLOCKED壅闭状态,由于锁竞争导致的壅闭


  • 1.NEW状态
  1.    public static void main(String[] args)throws InterruptedException {
  2.             Thread thread=new Thread(()->{
  3.             });
  4.             //在调用start之前获取的状态,此时是NEW状态
  5.             System.out.println(thread.getState());
  6.         }
复制代码



  • 2.TERMINATED状态
  1. public static void main(String[] args) throws InterruptedException {
  2.         Thread thread=new Thread(()->{
  3.         });
  4.         thread.start();
  5.         thread.join();
  6.         //这里start开始创建运行线程,当线程结束后(因为join对其阻塞)这时候线程销毁状态为TERMINATED
  7.         System.out.println(thread.getState());
  8.     }
复制代码



  • 3. RUNNABLE状态
  1. public static void main(String[] args) {
  2.         Thread thread=new Thread(()->{
  3.         });
  4.         thread.start();
  5.         //这里start已经创建好线程正在就绪,是RUNNABLE状态
  6.         System.out.println(thread.getState());
  7.     }
复制代码



  • 4.TIMED_WAITING状态
  1.    public static void main(String[] args) throws InterruptedException {
  2.         Thread thread=new Thread(()->{
  3.            while (true){
  4.                try {
  5.                    Thread.sleep(800);
  6.                } catch (InterruptedException e) {
  7.                    throw new RuntimeException(e);
  8.                }
  9.            }
  10.         });
  11.         thread.start();
  12.         while(true){
  13.             //通过thread线程的进行sleep阻塞的状态sleep是TIMED_WAITING状态
  14.             Thread.sleep(1000);
  15.             System.out.println(thread.getState());
  16.         }
  17.     }
复制代码

三.线程安全

   线程安满是什么?
线程安满是当一段代码在单个线程中跑的时间,不会出现题目。
但是假如放到了多个线程中则出现题目(bug),我们把这种叫做“线程的安全题目”大概是“线程不安全”。
  
3.1多线程实行

   假设界说一个成员变量count默以为0,通过两个线程将count存储至十万数值。
  1. private static int count;
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread t1=new Thread(()->{
  4.             for(int i=0;i<50000;i++){
  5.                 count++;
  6.             }
  7.         });
  8.         Thread t2=new Thread(()->{
  9.             for(int i=0;i<50000;i++){
  10.                 count++;
  11.             }
  12.         });
  13.         t1.start();
  14.         //如果t1和t2start同时执行,没有join的约束则会出现bug
  15.         t1.join();
  16.         //线程两个如果并发开始进行则出现bug,我们的实际结果输出十万
  17.         t2.start();
  18.         t2.join();
  19.         System.out.println(count);
  20.     }
复制代码
  这里的count每次自增的使用本质是分成三步举行(站在cpu的角度通过三个指令举行实现)⬇️
1.load: 将数据从内存中,读到cpu寄存器中。
2.add: 将寄存器的数据举行+1自增。
3.save: 把寄存器中的数据,生存到内存中。


  假如多个线程实行上述的代码,由于线程的调治序次是“随机”的,因在count++中会实行N次而且三个指令的cpu指令序次差别,就会导致在有些调治序次下,并发水平高,上述逻辑就会出现题目。
   通过上述可以意识到,在多线程中,在随机调治的时间,多个线程之间存在诸多大概的先后序次,我们必须要在包管全部大概的情况下,代码是准确的。
  
3.2线程加锁

   使用线程加锁来制止办理线程的辩说。
synchronized 在使用的时间,要搭配一个代码块{} 进入{就会加锁 出了}就会解锁,这里synchronized()中必要表现一个用来加锁的对象,这个对象是谁不紧张,紧张的是通过这个对象来区分两个线程是否在竞争同一块锁。
在已经加锁的状态中,另一个线程实行同样的加锁,就会产生“锁辩说/锁竞争”,后一个线程就会壅闭期待,不绝到前一个线程解锁为止。
  1. public class Test {
  2.     private static int count;
  3.     public static void main(String[] args) throws InterruptedException {
  4.     //这里是上锁,前提是线程公用一个锁!!
  5.         Object locker =new Object();
  6.         Thread t1=new Thread(()->{
  7.             for(int i=0;i<50000;i++){
  8.                 synchronized (locker){
  9.                     count++;
  10.                 }
  11.             }
  12.         });
  13.         Thread t2=new Thread(()->{
  14.             for(int i=0;i<50000;i++){
  15.                 synchronized(locker){
  16.                     count++;
  17.                 }
  18.             }
  19.         });
  20.         t1.start();
  21.         t2.start();
  22.         t1.join();
  23.         t2.join();
  24.         System.out.println(count);
  25.     }
  26. }
复制代码

   synchronized紧张的特性,在Java中针对一个对象加多个锁是可重入的。
所谓的可重入锁,指的是一个线程,一把锁,加锁两次,会出现死锁,就是“不可重入“,反之,就是”可重入“。
  3.2.1死锁

在多个线程中,假如是有N把锁(嵌套锁),无论是否可重入,都会进入死锁状态。
  1. public class Test {
  2.    private static final Object locker1=new Object();
  3.    private static final Object locker2=new Object();
  4.     public static void main(String[] args) {
  5.         Thread t1=new Thread(()->{
  6.             synchronized(locker1){
  7.                 System.out.println("hello t1:1");
  8.                 synchronized (locker2){
  9.                     System.out.println("hello t1:2");
  10.                 }
  11.             }
  12.         });
  13.         Thread t2=new Thread(()->{
  14.             synchronized(locker2){
  15.                 System.out.println("hello t2:2");
  16.                 synchronized (locker1){
  17.                     System.out.println("hello t2:1");
  18.                 }
  19.             }
  20.         });
  21.         t1.start();
  22.         t2.start();
  23.     }
  24. }
复制代码

3.2.2哲学家进餐

   这里的锁可以以下列来举例,哲学家进餐。
注意:这里的叉子相当于筷子。
这里每个哲学家都有一份面而且左右两边都有一副叉子,每一个哲学家必须使用两副叉子才气举行用饭,叉子在同一时间只能被同一个人使用
,假如哲学家没有拿到两副叉子,则无法吃面。
这里哲学家的状态只有两种:一种是吃面,一种是思索(期待)。

  

3.2.3并行算法

   这里假如哲学家在用饭和思索的状态下切换,这里假如哲学家用饭和思索都是恣意时间段,假如每个哲学家饿的时间和思索的时间不固定,则即可瓜代实行。
    这里假如一个哲学家想要拿这个叉子,而另一个哲学家也想拿,这时间则为死锁,两个哲学家都吃不到面。
  3.2.4死锁的告竣条件

    1.互斥使用:(锁的根本特性)当一个线程中持有着一把锁,另一个线程也想要获取锁,这时间就要壅闭期待。
2.不可抢占:(锁的根本特性)当锁已经被线程1拿到之后,线程2只能等线程1自动开释掉,不能强占。
3.哀求保持:(代码的结构)一个线程中假如获取多把锁(当获取第一把锁之后在获取第二把锁,在获取的过程中,锁1不会被开释。)
4.循环期待/环路期待:期待的依靠关系,形成循环。
假如有两把锁设a、b,线程1获取到锁a,然后线程2也获取到锁b,这时间线程1嵌套获取b锁,线程2嵌套获取a锁,则循环期待锁开释,进不去出不来。
比方:你此时在车库,打不开车库门,这时间车钥匙在家,你这时间给你你妻子打电话,让他回家帮你拿钥匙,而你妻子这时间说家的钥匙我落到车库啦~ 。这时间就陷入一种嵌套循环,形成死锁。
⬆️以上为死锁的四个条件,只管制止锁嵌套结构!

  
3.3volatile关键字



  • 包管内存的可见性。
  • 克制指令重排序。
   盘算机在运行过程中必要访问数据,这些数据会放在内存中(界说一个变量时,变量就是在内存中)。
cpu在使用这个变量时,就会把这些内存中的数据读取出来,放到cpu的寄存器中加入运算(load读取内存值放到寄存器中)。
寄存器读取速率>内存读取速率>硬盘读取运算。
为了办理上述的题目,这时间编译器大概对代码举行优化,把一些原来要读取内存的使用,优化成读取寄存器,镌汰读取内存的次数来进步代码的团体服从
  1. public class Test {
  2.     private  static int isQuit=0;
  3.     public static void main(String[] args) {
  4.         Thread thread1=new Thread(()->{
  5.                 //循环没有内容,但是也会循环许多次
  6.             while(isQuit==0){
  7.             }
  8.             System.out.println("isQuit");
  9.         });
  10.         thread1.start();
  11.         Thread thread2=new Thread(()->{
  12.             //用户通过第二个线程输入值不为0,则循环会结束?
  13.             Scanner scanner=new Scanner(System.in);
  14.             isQuit=scanner.nextInt();
  15.         });
  16.         thread2.start();
  17.     }
  18. }
  19.   
复制代码
  这时间一个线程读和一个线程写,仍然在实行,此时出现bug(引起线程安全题目),此时就是由于内存可见性引起。
1.load读取内存中的isQuit值放入寄存器中。
2.通过cmp指令比力寄存器中的值是否为0,绝对是否要继承循环。
此时会发生大量的循环,大量load和cmp使用,这时间编译器/JVM发现load出来的结果isQuit都是雷同的,而且load读取内存到寄存器中很耗费时间(1次load的时间相当于上万次的cmp)。
此时编译器则为了更方便的使用,只有第一次循环时读取内存器,后续不在读取内存,而是直接从寄存器中取isQuit的值,此时内存中无法读取数据编译器却没有举行读取,而是不绝在寄存器中实行,此时就出现了bug。


  




  • 方案1
   上述方案则因在寄存器中读取快,但是算的禁绝。
而volatile关键词来办理此方案,而volatile则是让JVM继承读取内存中的数据然后通过寄存器在逐个比力,办理此方案。
  1. public class Test {
  2.     private volatile static int isQuit=0;
  3.     public static void main(String[] args) {
  4.         Thread thread1=new Thread(()->{
  5.             while(isQuit==0){
  6.                 //循环没有内容,但是也会循环许多次
  7.             }
  8.             System.out.println("isQuit");
  9.         });
  10.         thread1.start();
  11.         Thread thread2=new Thread(()->{
  12.             //用户通过第二个线程输入值不为0,则循环会结束?
  13.             Scanner scanner=new Scanner(System.in);
  14.             isQuit=scanner.nextInt();
  15.         });
  16.         thread2.start();
  17.     }
  18. }
复制代码




  • 方案2
   我们可以镌汰读取的次数,通过sleep就寝来限定load的读取次数,此时就不会出发内存可见性题目,但是什么时间代码会被JVM优化,我们人是不清楚的,只有呆板扫除,以是我们只管使用volatile更保险一些,此方法知道即可。
  1. public class Test {
  2.     private  static int isQuit=0;
  3.     public static void main(String[] args) {
  4.         Thread thread1=new Thread(()->{
  5.             while(isQuit==0){
  6.                 //循环没有内容,但是也会循环许多次
  7.                 try {
  8.                     Thread.sleep(1000);
  9.                 } catch (InterruptedException e) {
  10.                     throw new RuntimeException(e);
  11.                 }
  12.             }
  13.             System.out.println("isQuit");
  14.         });
  15.         thread1.start();
  16.         Thread thread2=new Thread(()->{
  17.             //用户通过第二个线程输入值不为0,则循环会结束?
  18.             Scanner scanner=new Scanner(System.in);
  19.             isQuit=scanner.nextInt();
  20.         });
  21.         thread2.start();
  22.     }
复制代码

3.4 wait和notify

   wait和notify是用来调和多个线程中的实行序次,本身多个线程的实行序次是随机的,很多的时间,必要通过一些本领来对线程举行干预,前面说过join也是一种干预,但只是影响了线程的竣事的先后序次,很多时间,我们渴望线程不要竣事,也要举行干预形成先后序次。
    wait(不带参数): 让指定的线程进入壅闭状态,且无notify则处于不绝壅闭状态,代码无法向下实行。
wait(带参数):指定线程的时间,超出时间则向下继承实行代码。
notify: 唤醒对应壅闭状态的线程。
notifyAll:唤醒全部处于壅闭状态的线程。
  1. public class Test {
  2.     public static void main(String[] args) {
  3.         Object obj=new Object();
  4.         Thread t1=new Thread(()->{
  5.             synchronized(obj){
  6.                 try {
  7.                     System.out.println("wait等待1");
  8.                     obj.wait();
  9.                     System.out.println("wait1结束");
  10.                 } catch (InterruptedException e) {
  11.                     throw new RuntimeException(e);
  12.                 }
  13.             }
  14.         });
  15.         Thread t3=new Thread(()->{
  16.             synchronized(obj){
  17.                 try {
  18.                     System.out.println("wait等待2");
  19.                     obj.wait();
  20.                     System.out.println("wait2结束");
  21.                 } catch (InterruptedException e) {
  22.                     e.printStackTrace();
  23.                 }
  24.             }
  25.         });
  26.         Thread t2=new Thread(()->{
  27.             try {
  28.                 Thread.sleep(3000);
  29.             } catch (InterruptedException e) {
  30.                 throw new RuntimeException(e);
  31.             }
  32.             synchronized (obj){
  33.                 obj.notifyAll();
  34.                 System.out.println("通知");
  35.             }
  36.         });
  37.         t1.start();
  38.         t2.start();
  39.         t3.start();
  40.     }
  41. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表