Java多线程(二) —— join、线程的状态与synchronized

news/2024/10/18 16:53:27 标签: java, 线程

join

我们可以通过使用join() 方法来进行线程等待的操作:

java">public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("t1 线程启动");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("t2 线程启动");
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Main 线程启动");
    }
}

在这里插入图片描述


在这里插入图片描述

上面前两个方法我们经常使用到,第一个方法在上面已经演示过了,第二个方法是设置了时间,也就是最多等待 x 毫秒就不会继续等待了,如果一个线程因为有些原因而始终处于阻塞状态的话,为了避免程序持续运行,我们会设置一下等待时间,避免整个程序都挂了。

后面的方法则是设置了 ns 级别,但是我们很少会用到,因为我们大部分人的电脑的操作系统都不是实时操作系统的,由于线程存在调度,也就是线程调度过来了并不会立即就会执行可能会晚几纳秒这样的,但是这种延迟我我们是可以接受的,所以最后一个方法我们几乎不会使用。


现在来处理一下谁等谁的问题,上面的代码中我们实在 main 方法里使用了 t1.join() 和 t2.join(),也就是 main 线程等 t1 和 t2 线程的结束,我们才执行 main 线程

也就是说在哪个线程里使用 xxx.join() ,意味着 这个线程 等 xxx

我们也可以让 t1 线程等 main 线程

java">public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread cur = Thread.currentThread();
        Thread t1 = new Thread(() -> {
            try {
                cur.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t1 线程启动");
        });

        t1.start();

        for (int i = 0; i < 3; i++) {
            System.out.println("Hello Main");
        }
    }
}

在这里插入图片描述

线程的状态

在这里插入图片描述

new: 表明线程对象被创建出来了

runnable:说明线程正在执行(执行状态)或者处于要执行的状态(就绪状态)

wating:表明线程正在等待某些事情,例如上面的 join() 方法,一个线程等待另一个线程的结束。

time_wating:表明线程正在等待某些事情,但是是由等待时间限制的,也就是说,这不是死等,时间一到,这个线程就会被执行,例如上面的 join(ms) 方法,一个线程等待另一个线程 x 毫秒。

blocked:说明线程此时处于阻塞状态,这个阻塞状态是由于没有获取到锁而陷入的阻塞状态,和上面提到的 wating 和 time_wating 是由区别的。

terminated:表明线程终止。可以是正常执行完的终止也可以是我们手动的终止。

上一篇文章中我们学过的 isAlive() 方法,可以认为是 线程处于不是NEW和TERMINATED的状态 都是活着的。

synchronized

引入

java">public class Demo3 {
    private static int count = 0;
    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();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count = " + count);
    }
}

大家来思考一下运行结果是什么?


在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

你会发现每一次运行的结果都是不一样的。


我们站在CPU 的角度来分析上面的代码,首先 count++ 是有三条指令,首先加载 count 的数值,然后进行 +1,最后保存 count 的数值。

由于线程的调度是随机的就会出现下面的情况,这里我举一个例子供大家参考:
在这里插入图片描述
在上面的调度中,t1 先加载 count = 0, 然后 t1 被调度走了,t2 带哦都过来了,此时 t2 也执行了 加载 count = 0; 然后 t2 被调度走了,此时 t1调度过来将 count + 1 = 1, 之后又被调度走了,t2 来了执行 add ,count + 1 = 1,然后进行 save 保存数据,此时 count 被修改为 1,然后 t1 调度过来了,进行 save 操作,这时候将 count 修改为 1,之后进行保存

此时你会发现,上面进行了两个 ++ 操作,但是 count 却变为了 1,这就是线程安全问题

我们再来分析一下,我们最后得到的count 的范围有可能为 10 w 吗?
答案是有可能的,但是概率极低,只要每一个线程的三条指令都是串行执行的话,就会得到正确的结果 10w

那count 有可能小于 5 w 吗?
有可能,假设一共有 5 次 ++ 操作,t1 线程执行 3 次,t2 线程执行 2 次:
在这里插入图片描述
5 次 ++ ,最后得到 2
10 w 次++ ,最后是可能得到小于 5 w 的结果的,但是这个概率极低

synchronized 的使用

为了避免上面的线程安全问题,我们可以进行加锁操作,Java 给我们提供了 synchronized 关键字,让我们可以对代码进行加锁操作:

在这里插入图片描述

java">public class Demo3 {
    private static int count = 0;
    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();

        System.out.println("count = " + count);
    }
}

在这里插入图片描述


java">synchronized(对象) {
    //需要加锁的代码
}

一般情况下,我们的 synchronized 的括号内部需要传入一个锁对象,这个所对象可以为任意对象,但是这里建议大家自己创建一个专门用来作锁的对象,专象专用

在Java中,如果两个及以上的线程对同一个对象都有加锁需求的话,当谁拥有这个对象的锁的时候,就可以进行sychronized 里面的操作了,如果没有拥有这个对象的锁,就会处于阻塞状态,直到获得这个对象的锁。

这里需要注意如果加锁的对象不是同一个的话,那么这些线程是不会发生互斥关系的
只有当两个及以上的线程对同一个对象加锁的时候,才会产生互斥(锁冲突 / 锁竞争)


synchronized 的其他用法:
可以修饰方法:

java">class Test {
    private static int count = 0;

    synchronized public void add() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                test.add();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                test.add();
            }
        });

        t1.start();
        t2.start();
        t1.join();;
        t2.join();

        System.out.println("count = " + test.getCount());
    }
}

在这里插入图片描述

你也可以在方法里面写 synchronized:

java">    public void add() {
        synchronized(this) {
            count++;
        }
    }

括号里面记得写 this,表示当前加锁的对象

实质上这种写法其实和上面直接对类的方法加 synchronized 是一样的,都是对 this 加锁


当然也可以对进行方法使用 synchronized ,这样就是对类对象进行加锁

java">class Counter {
    private static int count = 0;

    synchronized public static void add() {
        count++;
    }

    public static int getCount() {
        return count;
    }
}

public class Deno5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                Counter.add();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                Counter.add();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count =" + Counter.getCount());
    }
}

在这里插入图片描述

synchronized 的可重入性

java">public class Demo1 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            synchronized(locker) {
                synchronized (locker) {
                    System.out.println("t1 线程启动");
                }
            }
        });

        t1.start();
    }
}

我们看一下上面的代码,你会发现一个线程内部对一个对象进行了两次加锁,我们回到锁的性质来看,当一个对象被加锁之后,其他线程要想获得这个锁就必须阻塞等待,那么在这个线程里,按照理论来说,这个线程会陷入死锁状态,第一次已经对 locker 加过锁了,第二次就应该是无法获得 locker 这个锁对象了,但是事实并非如此:

在这里插入图片描述

线程依然能正常执行,这是因为Java 的synchronized 带有可重入特性,简单来说,就是在同一个线程里,可以对已经获得的锁对象进行重复加锁,这样就避免因为一个线程对同一个对象重复加锁产生的死锁问题了。

在可重入下,什么时候解锁?
当然是第一个synchronized 的最后一个括号出来就会自动解锁,因为两个synchronized 之间可能存在代码,加锁加锁就一次性锁住

如何实现可重入锁?
可重入特性是在同一个线程对同一个锁对象进行加锁,那么我们只要在锁内部记录当前是哪个线程持有这把锁,在后续加锁的时候,就进行判定。
使用计数器来统计括号,遇到左括号就++, 遇到右括号就–,等到 count == 0 的时候就直接解锁

不要认为 JVM 分不出是不是 synchronized 的括号,在 .java 编译成 .class 字节码文件的时候,不同的括号都有不同的含义,对应的字节码是不一样的。


http://www.niftyadmin.cn/n/5711425.html

相关文章

LeetCode 二分算法 咒语和药水的成功对数

咒语和药水的成功对数 给你两个正整数数组 spells 和 potions &#xff0c;长度分别为 n 和 m &#xff0c;其中 spells[i] 表示第 i 个咒语的能量强度&#xff0c;potions[j] 表示第 j 瓶药水的能量强度。 同时给你一个整数 success 。一个咒语和药水的能量强度 相乘 如果 大于…

基于51单片机的大棚环境检测系统设计

温室大棚环境监测系统设计&#xff1a;基于51单片机的智能化解决方案 引言 随着现代农业技术的发展&#xff0c;温室大棚种植已成为提高农作物产量和质量的重要手段。为了更好地控制温室环境&#xff0c;提高作物生长效率&#xff0c;环境监测系统成为了温室管理中不可或缺的…

Spring AI Alibaba: 支持国产大模型的Spring ai框架

Spring AI &#xff1a;java做ai应用的最好选择 过去&#xff0c;Java在AI应用开发方面缺乏一个高效且易于集成的框架&#xff0c;这限制了开发者快速构建和部署智能应用程序的能力。 Spring AI正是为解决这一问题而生&#xff0c;它提供了一套统一的接口&#xff0c;使得AI功…

中国科学院大学与美团发布首个交互式驾驶世界模型数据集DrivingDojo:推进交互式与知识丰富的驾驶世界模型

中国科学院大学与美团发布首个交互式驾驶世界模型数据集DrivingDojo&#xff1a;推进交互式与知识丰富的驾驶世界模型 Abstract 驾驶世界模型因其对复杂物理动态的建模能力而受到越来越多的关注。然而&#xff0c;由于现有驾驶数据集中的视频多样性有限&#xff0c;其卓越的建…

tavily - 简单使用

github &#xff1a;https://github.com/tavily-ai/tavily-python 安装 pip install tavily-python获取 API Key https://app.tavily.com/home 使用 from tavily import TavilyClientTAVILY_API_KEY tvly-3iO5Ek...3hbCNl# Step 1. Instantiating your TavilyClient tavily…

centors7升级GLIBC2.18

错误来源&#xff1a;找不到GLIBC2.18&#xff0c;因为glibc的版本是2.17 网上大多教程方法&#xff0c;反正我是行不通&#xff1a; 方法1&#xff1a;更新源&#xff0c;然后使用yum安装更新 方法2&#xff1a;下载源码&#xff0c;configrue&#xff0c;make执行 wget h…

宠物空气净化器选哪款?希喂、霍尼韦尔、安德迈真实测评!

自从家里养了猫&#xff0c;我的生活就多了不少乐趣&#xff0c;但也多了不少烦恼。最大的烦恼就是猫毛满天飞&#xff0c;弄得地板、衣服都是猫毛&#xff0c;甚至水杯里都能见到猫毛的踪迹。渐渐地&#xff0c;我的鼻子和喉咙都开始不舒服&#xff0c;医生给我开了先药&#…

银发产业动态:阿里、华为、京东健康、平安健康均有布局

整理 | 李梦媛 一周银发产业大事件速览 10月18日 星期五 养老服务 民政部发布《2023年度国家老龄事业发展公报》市场监管总局发布系列适老化国家标准扬州出台全国首部优待老年人地方性法规中信保诚人寿联合得到&#xff0c;共探养老综合解决方案饿了么携手北京西城&#xf…