您现在的位置是:首页 > 编程 > 

Java多线程第二篇

2025-07-27 23:47:18
Java多线程第二篇 一.线程的常用方法1.线程终止1.1通过成员对线程进行终止 变量创建需要以static修饰并且变量成员需要以final修饰或者非final修饰(不变的常量)才能进入到run方法中,但是如果你想终止的话,你一定要修改它的值,所以我们必须要定义一个外部成员。代码语言:javascript代码运行次数:0运行复制public class Demo7 { //这里的成员默认f

Java多线程第二篇

一.线程的常用方法

1.线程终止

1.1通过成员对线程进行终止

变量创建需要以static修饰并且变量成员需要以final修饰或者非final修饰(不变的常量)才能进入到run方法中,但是如果你想终止的话,你一定要修改它的值,所以我们必须要定义一个外部成员。

代码语言:javascript代码运行次数:0运行复制
public class Demo7 {
    //这里的成员默认false
    private static boolean isQuit;
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            //不为true进入循环
           while(!isQuit){
               println("Thread is Running");
               try {
                   //thread线程睡眠1000
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
           //这里跳出循环后打印
            println("Thread closing");
        });
        thread.start();
        //这里进入start后线程和主线程同时运行,程序继续往下走
        //这里sleep睡眠5000ms为条件,达成条件后继续往下执行
        Thread.sleep(5000);
        isQuit=true;
        //这里睡眠2000在执行下面代码,这时候打印为TERMIATED
            Thread.sleep(2000);
        println(thread.getState());
    }
}

1.2通过Thread本地方法终止

通过上述我们明白如果我们创建,可以看到缺点。 1.需要手动创建变量。 2.当线程内部在sleep的时候,主线程修改为ture,这时候结束了循环,但是新的线程内部无法及时的做出响应。 这时候我们就要通过Java中的本地方法来进行线程的中断。

代码语言:javascript代码运行次数:0运行复制
public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
          //这里的currentThread方法可以获得当前线程的实例,但是无法直接写成thread,这时候我们的thread还没有构造完成。
            //isInterrupted是标志位置,判断线程是否结束。
            //isInterrupted 默认为false
           while(!().isInterrupted()){
               println("Thread is running");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            println("结束线程");
        });
        thread.start();
        Thread.sleep(000);
        //这里修改为true,循环会结束?
        thread.interrupt();
        Thread.sleep(2000);
        println("Thread exit");
    }
}

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

  • 以下修改的部分
代码语言:javascript代码运行次数:0运行复制
 while (!().isInterrupted()) {
                println("Thread is working");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException();
                    //或者直接通过上述代码的打印异常增加一个break,break前可以增加一些想要添加的代码。
                    //break;
                }
            }
        });

2.线程的等待join

join 是实现线程等待的效果,在主线程调用thread.join(),此时是主线程等待thread线程先结束。 让一个线程,等待另一个线程执行结束,在继续执行,本质上就是控制线程结束的顺序的方式。

2.1不增加参数
代码语言:javascript代码运行次数:0运行复制
public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread =new Thread(()->{
           while(!().isInterrupted()){
               println("Thread is running:1");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   break;
               }
           }
        });
        thread.start();
            Thread.sleep(000);
        thread.interrupt();
        //这里的join就是阻断,让其执行完成后在继续执行其他线程
            thread.join();
        for(int i=0;i<;i++){
            println("Thread is running:2");
            Thread.sleep(1000);
        }
    }
}

2.2增加参数

当增加参数后我们会限定join的等待时长,如果超过该等待时长则继续执行

代码语言:javascript代码运行次数:0运行复制
public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          for (int i=0;i<10;i++) {
                println("Thread is running:1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException();
                }
            }
        });
        thread.start();
//           这里等待thread执行完成则主线程开始执行
//            如果join带参数后,则是需要等待的时间,如果等待时间过长我们就需要将主线程继续进行
        thread.join(000);
        for(int i=0;i<;i++){
            println("Thread is running:2");
        }
    }
}

2.线程的休眠时间

线程的sleep是有误差的,因为线程的调度开销也是需要时间,所以可能不是很准确,比较随机。

代码语言:javascript代码运行次数:0运行复制
  public static void main(String[] args) throws InterruptedException {
       
        long start = ();
        Thread.sleep(1000);
        long end = ();
        println("开始和结束的时差(以ms为单位):"+(end-start));
    }

二.线程的其他状态

线程状态

说明

EW

THREAD的对象已经存在,start方法还没调用,没有创建新的线程

TERMIATED

THREAD的对象仍然存在,内核中的线程已经销毁

RUABLE

就绪状态(线程已经在cpu上执行了/线程正在排队等待在cpu执行)

TIMED_WAITIG

阻塞状态,由于sleep这种固定时间的方式产生的阻塞

WAITIG

阻塞状态,由于wait这种不固定时间的方式产生的阻塞

BLOCKED

阻塞状态,由于锁竞争导致的阻塞


  • 1.EW状态
代码语言:javascript代码运行次数:0运行复制
   public static void main(String[] args)throws InterruptedException {
            Thread thread=new Thread(()->{
            });
            //在调用start之前获取的状态,此时是EW状态
            println(thread.getState());
        }

  • 2.TERMIATED状态
代码语言:javascript代码运行次数:0运行复制
 public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
        });
        thread.start();
        thread.join();
        //这里start开始创建运行线程,当线程结束后(因为join对其阻塞)这时候线程销毁状态为TERMIATED
        println(thread.getState());
    }

  • . RUABLE状态
代码语言:javascript代码运行次数:0运行复制
public static void main(String[] args) {
        Thread thread=new Thread(()->{
        });
        thread.start();
        //这里start已经创建好线程正在就绪,是RUABLE状态
        println(thread.getState());
    }

  • 4.TIMED_WAITIG状态
代码语言:javascript代码运行次数:0运行复制
   public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
           while (true){
               try {
                   Thread.sleep(800);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        thread.start();
        while(true){
            //通过thread线程的进行sleep阻塞的状态sleep是TIMED_WAITIG状态
            Thread.sleep(1000);
            println(thread.getState());
        }
    }

三.线程安全

线程安全是什么? 线程安全是当一段代码在单个线程中跑的时候,不会出现问题。 但是如果放到了多个线程中则出现问题(bug),我们把这种叫做“线程的安全问题”或者是“线程不安全”。


.1多线程执行

假设定义一个成员变量count默认为0,通过两个线程将count存储至十万数值。

代码语言:javascript代码运行次数:0运行复制
 private static int count;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            for(int i=0;i<50000;i++){
                count++;
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<50000;i++){
                count++;
            }
        });
        t1.start();
        //如果t1和t2start同时执行,没有join的约束则会出现bug
        t1.join();
        //线程两个如果并发开始进行则出现bug,我们的实际结果输出十万
        t2.start();
        t2.join();
        println(count);
    }

这里的count每次自增的操作本质是分成三步进行(站在cpu的角度通过三个指令进行实现)⬇️ 1.load: 将数据从内存中,读到cpu寄存器中。 2.add: 将寄存器的数据进行+1自增。 .save: 把寄存器中的数据,保存到内存中。

如果多个线程执行上述的代码,因为线程的调度顺序是“随机”的,因在count++中会执行次而且三个指令的cpu指令顺序不同,就会导致在有些调度顺序下,并发程度高,上述逻辑就会出现问题。

通过上述可以意识到,在多线程中,在随机调度的时候,多个线程之间存在诸多可能的先后顺序,我们必须要在保证所有可能的情况下,代码是正确的。

.2线程加锁

使用线程加锁来避免解决线程的冲突。 synchronized 在使用的时候,要搭配一个代码块{} 进入{就会加锁 出了}就会解锁,这里synchronized()中需要表示一个用来加锁的对象,这个对象是谁不重要,重要的是通过这个对象来区分两个线程是否在竞争同一块锁。 在已经加锁的状态中,另一个线程尝试同样的加锁,就会产生“锁冲突/锁竞争”,后一个线程就会阻塞等待,一直到前一个线程解锁为止。

代码语言:javascript代码运行次数:0运行复制
public class Test {
    private static int count;
    public static void main(String[] args) throws InterruptedException {
    //这里是上锁,前提是线程公用一个锁!!
        Object locker =new Object();
        Thread t1=new Thread(()->{
            for(int i=0;i<50000;i++){
                synchronized (locker){
                    count++;
                }
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<50000;i++){
                synchronized(locker){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        println(count);
    }
}

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

.2.1死锁

在多个线程中,如果是有把锁(嵌套锁),无论是否可重入,都会进入死锁状态。

代码语言:javascript代码运行次数:0运行复制
public class Test {
   private static final Object locker1=new Object();
   private static final Object locker2=new Object();
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            synchronized(locker1){
                println("hello t1:1");
                synchronized (locker2){
                    println("hello t1:2");
                }
            }
        });

        Thread t2=new Thread(()->{
            synchronized(locker2){
                println("hello t2:2");
                synchronized (locker1){
                    println("hello t2:1");
                }
            }
        });
        t1.start();
        t2.start();
    }
}
.2.2哲学家进餐

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

.2.并行算法

这里如果哲学家在吃饭和思考的状态下切换,这里如果哲学家吃饭和思考都是任意时间段,如果每个哲学家饿的时间和思考的时间不固定,则即可交替执行。

这里如果一个哲学家想要拿这个叉子,而另一个哲学家也想拿,这时候则为死锁,两个哲学家都吃不到面。

.2.4死锁的达成条件

1.互斥使用:(锁的基本特性)当一个线程中持有着一把锁,另一个线程也想要获取锁,这时候就要阻塞等待。 2.不可抢占:(锁的基本特性)当锁已经被线程1拿到之后,线程2只能等线程1主动释放掉,不能强占。 .请求保持:(代码的结构)一个线程中如果获取多把锁(当获取第一把锁之后在获取第二把锁,在获取的过程中,锁1不会被释放。) 4.循环等待/环路等待:等待的依赖关系,形成循环。 如果有两把锁设a、b,线程1获取到锁a,然后线程2也获取到锁b,这时候线程1嵌套获取b锁,线程2嵌套获取a锁,则循环等待锁释放,进不去出不来。 例如:你此时在车库,打不开车库门,这时候车钥匙在家,你这时候给你你老婆打电话,让他回家帮你拿钥匙,而你老婆这时候说家的钥匙我落到车库啦~ 。这时候就陷入一种嵌套循环,形成死锁。 ⬆️以上为死锁的四个条件,尽量避免锁嵌套结构!


.volatile关键字

  • 保证内存的可见性。
  • 禁止指令重排序。

计算机在运行过程中需要访问数据,这些数据会放在内存中(定义一个变量时,变量就是在内存中)。 cpu在使用这个变量时,就会把这些内存中的数据读取出来,放到cpu的寄存器中参与运算(load读取内存值放到寄存器中)。 寄存器读取速度>内存读取速度>硬盘读取运算。 为了解决上述的问题,这时候编译器可能对代码进行优化,把一些本来要读取内存的操作,优化成读取寄存器,减少读取内存的次数来提高代码的整体效率

代码语言:javascript代码运行次数:0运行复制
public class Test {
    private  static int isQuit=0;
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
                //循环没有内容,但是也会循环许多次
            while(isQuit==0){
            }
            println("isQuit");
        });
        thread1.start();

        Thread thread2=new Thread(()->{
            //用户通过第二个线程输入值不为0,则循环会结束?
            Scanner scanner=new Scanner(System.in);
            isQuit=();
        });
        thread2.start();
    }
}

这时候一个线程读和一个线程写,仍然在执行,此时出现bug(引起线程安全问题),此时就是因为内存可见性引起。 1.load读取内存中的isQuit值放入寄存器中。 2.通过cmp指令比较寄存器中的值是否为0,绝对是否要继续循环。 此时会发生大量的循环,大量load和cmp操作,这时候编译器/JVM发现load出来的结果isQuit都是相同的,并且load读取内存到寄存器中很花费时间(1次load的时间相当于上万次的cmp)。 此时编译器则为了更方便的操作,只有第一次循环时读取内存器,后续不在读取内存,而是直接从寄存器中取isQuit的值,此时内存中无法读取数据编译器却没有进行读取,而是一直在寄存器中执行,此时就出现了bug。

  • 方案1

上述方案则因在寄存器中读取快,但是算的不准。 而volatile关键词来解决此方案,而volatile则是让JVM继续读取内存中的数据然后通过寄存器在逐个比较,解决此方案。

代码语言:javascript代码运行次数:0运行复制
public class Test {
    private volatile static int isQuit=0;
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
            while(isQuit==0){
                //循环没有内容,但是也会循环许多次
            }
            println("isQuit");
        });
        thread1.start();
        Thread thread2=new Thread(()->{
            //用户通过第二个线程输入值不为0,则循环会结束?
            Scanner scanner=new Scanner(System.in);
            isQuit=();
        });
        thread2.start();
    }
}
  • 方案2

我们可以减少读取的次数,通过sleep睡眠来限定load的读取次数,此时就不会出发内存可见性问题,但是什么时候代码会被JVM优化,我们人是不清楚的,只有机器清除,所以我们尽量使用volatile更保险一些,此方法知道即可。

代码语言:javascript代码运行次数:0运行复制
public class Test {
    private  static int isQuit=0;
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
            while(isQuit==0){
                //循环没有内容,但是也会循环许多次
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            println("isQuit");
        });
        thread1.start();

        Thread thread2=new Thread(()->{
            //用户通过第二个线程输入值不为0,则循环会结束?
            Scanner scanner=new Scanner(System.in);
            isQuit=();
        });
        thread2.start();
    }

.4 wait和notify

wait和notify是用来协调多个线程中的执行顺序,本身多个线程的执行顺序是随机的,很多的时候,需要通过一些手段来对线程进行干预,前面说过join也是一种干预,但只是影响了线程的结束的先后顺序,很多时候,我们希望线程不要结束,也要进行干预形成先后顺序。

wait(不带参数): 让指定的线程进入阻塞状态,且无notify则处于一直阻塞状态,代码无法向下执行。 wait(带参数):指定线程的时间,超出时间则向下继续执行代码。 notify: 唤醒对应阻塞状态的线程。 notifyAll:唤醒所有处于阻塞状态的线程。

代码语言:javascript代码运行次数:0运行复制
public class Test {
    public static void main(String[] args) {
        Object obj=new Object();
        Thread t1=new Thread(()->{
            synchronized(obj){
                try {
                    println("wait等待1");
                    obj.wait();
                    println("wait1结束");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t=new Thread(()->{
            synchronized(obj){
                try {
                    println("wait等待2");
                    obj.wait();
                    println("wait2结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2=new Thread(()->{
            try {
                Thread.sleep(000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (obj){
                ();
                println("通知");
            }
        });
        t1.start();
        t2.start();
        t.start();
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2024-12-2,如有侵权请联系 cloudcommunity@tencent 删除多线程内存线程线程安全java

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1220414.html

相关标签:无
上传时间: 2025-07-25 14:42:42
留言与评论(共有 15 条评论)
本站网友 吞金是什么意思
27分钟前 发表
形成死锁
本站网友 叶舒
2分钟前 发表
另一个线程也想要获取锁
本站网友 杭州动漫学校
28分钟前 发表
让他回家帮你拿钥匙
本站网友 金鱼精
26分钟前 发表
我们希望线程不要结束
本站网友 光刻工艺
9分钟前 发表
让他回家帮你拿钥匙
本站网友 连江租房网
12分钟前 发表
但是如果你想终止的话
本站网友 安阳电影院
27分钟前 发表
需要通过一些手段来对线程进行干预
本站网友 嘉佑
24分钟前 发表
需要通过一些手段来对线程进行干预
本站网友 360桌面壁纸
27分钟前 发表
很多的时候
本站网友 photoshop视频教程下载
3分钟前 发表
也要进行干预形成先后顺序
本站网友 杭州不孕不育
14分钟前 发表
只有第一次循环时读取内存器
本站网友 陶瓷洁具品牌
15分钟前 发表
为了解决上述的问题
本站网友 冒险岛sf发布
17分钟前 发表
wait(带参数):指定线程的时间
本站网友 北京今日空气质量
26分钟前 发表
需要通过一些手段来对线程进行干预