Java多线程【状态与安全问题】

这篇具有很好参考价值的文章主要介绍了Java多线程【状态与安全问题】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🍎一.多线程状态

🍇1.1多线程的状态形式

线程状态 说明
NEW 安排了工作, 还未开始行动
RUNNABLE 可工作的. 又可以分成正在工作中和即将开始工作
BLOCKED 这几个都表示排队等着其他事情
WAITING 这几个都表示排队等着其他事情
TIMED_WAITING 这几个都表示排队等着其他事情
TERMINATED 工作完成了

1.NEW状态:安排了工作, 还未开始行动

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{

        });
        System.out.println(thread.getState());
    }
}

Java多线程【状态与安全问题】
2.RUNNABLE:可工作的. 又可以分成正在工作中和即将开始工作

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true){
                //这里面不能有人任务,这样才会显示是就绪状态
            }
        });
        thread.start();
        System.out.println(thread.getState());
    }
}

Java多线程【状态与安全问题】

3.TERMINATED:工作完成了
操作系统线程已经执行完毕,线程已经销毁了,但是Thread对象还存在,获取到的状态

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
                System.out.println("我滴任务完成啦!");

        });
        thread.start();
        Thread.sleep(500);
        System.out.println(thread.getState());

    }
}

Java多线程【状态与安全问题】

4.TIMED_WAITING:代码调用了sleep或者join(超时时间)

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("我在等一会");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(500);
        System.out.println(thread.getState());

    }
}

Java多线程【状态与安全问题】

BLOCKED,WAITING|这两个状态我们后续会将

🍇1.2状态转化分布图

Java多线程【状态与安全问题】

🍎二.多线程的安全问题

🍇2.1线程安全问题的概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线
程安全的。

如果多线程环境下代码运行的结果是不符合我们预期的,即在单线程环境应该的结果,则说这个程序是线
程不安全的。

🍇2.2线程不安全示例

假设我们设置一个count初始值为0,我们使用两个线程同时并且每条线程执行5万次,这样我们的预期count结果为10万,接下来我们来演示一下看看两个线程并发是否安全

public class Test {
   static class Count{
        public int count = 0;
        public void sum(){
            count++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Count count = new Count();
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count.sum();
            }
        });
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
               count.sum();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count.count);
    }
}

Java多线程【状态与安全问题】Java多线程【状态与安全问题】

Java多线程【状态与安全问题】

我们发现count执行的结果并不是10万,而是5万-10万之间的随机数,这是什么原因呢?接下来我来帮助大家了解一下为什么在对对同一数据进行并发线程不安全
原子性
什么是原子性?
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
一条 java 语句不一定是原子的,也不一定只是一条指令比如刚才我们看到的 n++,其实是由三步操作组成的:
1. 从内存把数据读到 CPU
2. 进行数据更新
3. 把数据写回到 CPU不保证原子性会给多线程带来什么问题如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

可见性
可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到.线程之间的共享变量存在 主内存 (Main Memory).每一个线程都有自己的 “工作内存” (Working Memory) .当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存.由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同一个共享变量的 “副本”. 此时修改线程1 的工作内存中的值, 线程2 的工作内存不一定会及时变化.
Java多线程【状态与安全问题】
情况一:
Java多线程【状态与安全问题】
情况二:
Java多线程【状态与安全问题】
情况三:

Java多线程【状态与安全问题】
情况四:

Java多线程【状态与安全问题】
重排序问题:
Java多线程【状态与安全问题】

🍇2.3解决线程不安全方案

这个时候我们应该怎么解决这个问题呢? 上锁!(synchronized)
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到
同一个对象 synchronized 就会阻塞等待.
进入 synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁
Java多线程【状态与安全问题】

public class Test {
   static class Count{
        public int count = 0;
        //对方法上锁
        synchronized public void sum(){
            count++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Count count = new Count();
        Thread thread1 = new Thread(()->{
            //对该线程上锁
            synchronized (count) {
                for (int i = 0; i < 50000; i++) {
                    count.sum();
                }
            }
        });
        Thread thread2 = new Thread(()->{
            //对该线程上锁
            synchronized (count) {
                for (int i = 0; i < 50000; i++) {
                    count.sum();
                }
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count.count);
    }
}

Java多线程【状态与安全问题】

🍇2.4Java 标准库中线程安全类与不安全类

Java 标准库中线程不安全类: 使用了一些锁机制来控制

  1. Vector (不推荐使用)
  2. HashTable (不推荐使用)
  3. ConcurrentHashMap
  4. StringBuffer(StringBuffer 的核心方法都带有 synchronized )
    特例:String:还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的

Java 标准库中线程不安全类: 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施

  1. .ArrayList
  2. LinkedList
  3. TreeMap
  4. HashSet
  5. TreeSet
  6. StringBuilder

🍎三.多线程常见关键字

🍇3.1synchronized(锁)

🎈3.1.1互斥

synchronized的底层是使用操作系统的mutex lock实现的.

我们在介绍线程安全问题解决时说过 synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.
进入 synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁

可以粗略理解成, 每个对象在内存中存储的时候, 都存有一块内存表示当前的 “锁定” 状态(类似于厕所的 “有人/无人”).
如果当前是 “无人” 状态, 那么就可以使用, 使用时需要设为 “有人” 状态.
如果当前是 “有人” 状态, 那么其他人无法使用, 只能排队

Java多线程【状态与安全问题】
理解 “阻塞等待”
针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试进行加锁, 就加不上了, 就会阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的线程, 再来获取到这个锁.

注意:
上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 “唤醒”. 这
也就是操作系统线程调度的一部分工作.
假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B
和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能
获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.
注意:synchronized是非公平锁

🎈3.1.2刷新内存

synchronized 的工作过程:

  1. 获得互斥锁
  2. 从主内存拷贝变量的最新副本到工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁
    所以 synchronized 也能保证内存可见性. 具体代码参见后面 volatile 部分.

🎈3.1.3可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题
理解 “把自己锁死”:
一个线程没有释放锁, 然后又尝试再次加锁.

// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待.
lock();

按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第
二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无
法进行解锁操作. 这时候就会 死锁
Java 中的 synchronized 是 可重入锁, 因此没有上面的问题.

🎈3.1.4synchronized使用案例

1.直接修饰普通方法: 锁的 SynchronizedDemo 对象

public class SynchronizedDemo {
public synchronized void methond() {
}
}

2. 修饰静态方法: 锁的 SynchronizedDemo 类的对象

public class SynchronizedDemo {
public synchronized static void method() {
}
}

3. 修饰代码块: 明确指定锁哪个对象

public class SynchronizedDemo {
public void method() {
}
}

4.锁类对象

public class SynchronizedDemo {
public void method() {
}
}

🍇3.2volatile(可见性)

🎈3.2.1volatile与synchronized区别

关键字 相同作用 不同作用
synchronized 可以保证内容可见性 能够保证线程执行的原子性
volatile 保证线程的可见性 并不能保证线程执行的原子性

🎈3.2.2volatile使用的场景

我峨嵋你来执行一个停止线程的代码程序

public class Test2 {
    private static int isQuit;
    private static Object okject = new Object();
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        Thread thread = new Thread(()->{
            while(isQuit==0){
            
            }
            System.out.println("该线程执行结束");
        });
         thread.start();
        System.out.println("输入一个不为0的的数停止该线程");
        isQuit = scanner.nextInt();
        System.out.println("mian结束运行");

    }
}

Java多线程【状态与安全问题】
我们发现main结束时并没有执行到Thread内的输出"该线程结束",这是因为在在编译器执行时,因为每次快速执行线程的while循环时会被编译器优化掉while判断isQuit忽视这个值导致线程一直在执行并没有在输入非0时的结束操作而停止该线程结束
那我们应该怎样去做呢?
没错就是要用到我们刚刚认识到的volatile关键字(synchronized也可以)

volatile优化版本:

public class Test2 {
    //volatile 在需要在循环判断的isQuit中加入volatile来防止编译器优化忽略判断isQuit
    volatile private static int isQuit;
    private static Object okject = new Object();
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        Thread thread = new Thread(()->{
            while(isQuit==0){

            }
            System.out.println("该线程执行结束");
        });
        thread.start();
        System.out.println("输入一个不为0的的数停止该线程");
        isQuit = scanner.nextInt();
        System.out.println("mian结束运行");

    }
}

Java多线程【状态与安全问题】

synchronized优化版本:

public class Test2 {
    private static int isQuit;
    private static final Object okject = new Object();
    public static void main(String[] args) throws InterruptedException {
        Scanner scanner = new Scanner(System.in);

        Thread thread = new Thread(()->{
            synchronized (okject) {
                while (isQuit == 0) {

                }
                System.out.println("该线程执行结束");
            }
        });
        thread.start();
        System.out.println("输入一个不为0的的数停止该线程");
        isQuit = scanner.nextInt();
        System.out.println("mian结束运行");

    }
}

Java多线程【状态与安全问题】
我们发现该程序并没有结束
这也是编译器优化的问题,编译器的优化不仅可以产生内存可见性问题,也会产生重排序问题,就是保证在原有的逻辑下,调整代码的执行顺序,从而提高线程的运行效率从而引起线程的不安全,这个问题我们了解即可.

🍇3.3wait丶notify(等待丶唤醒)

🎈3.3.1wait

wait 做的事情:
使当前执行代码的线程进行等待. (把线程放到等待队列中)释放当前的锁满足一定条件时被唤醒, 重新尝试获取这个锁

注意:
wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常

wait 结束等待的条件:
其他线程调用该对象的 notify 方法.wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

代码示例: 观察wait()方法搭配synchronized使用

public class Testified {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        Thread thread = new Thread(()->{
            synchronized (o){

            }
           //执行一些任务
        });
        thread.start();
        System.out.println("开始等待");
        thread.wait();
        System.out.println("等待结束");
    }
}

Java多线程【状态与安全问题】
抛出错误这是因为我们需要搭配notify来进行等待和唤醒

🎈3.3.2notify

notify 方法是唤醒等待的线程
方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁

代码示例: 使用notify()方法唤醒线程
我们来设置一个通过线程2来等待后被线程1唤醒的操作

class Tack{
    public int i ;
    public void tack(int i){
        System.out.println("第"+i+"任务完成了!");
    }
}
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Object ob = new Object();
        Thread thread1 = new Thread(()->{
           synchronized (ob){
               //开始执行并且执行完成后将wait唤醒
               System.out.println("notify开始执行了!");
               ob.notify();
               Tack tack1 = new Tack();
               System.out.println("开始执行第一个任务!");
               tack1.tack(1);
               
           }
        });
        Thread thread2 = new Thread(()->{
            synchronized (ob){
                Tack tack2 = new Tack();
                System.out.println("开始执行第二个任务!");
                System.out.println("wait开始执行!");
                try {
                    //开始等待,等待被notify唤醒
                    ob.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tack2.tack(2);
            }
        });
        //先开始执行线程2
        thread2.start();
        //线阻塞0.1s
        Thread.sleep(100);
        //开始执行线程1
        thread1.start();
    }
}

Java多线程【状态与安全问题】

🍎四.总结

🍇4.1线程不安全情况总结(重点)

1.线程是抢占行执行,线程充满了随机性[这就是线程不安全的万恶之源!!!]
2.多线程对同一个变量进行修改操作
3.针对的变量操作不是原子性的
4.内存可见性被编译器优化
5.重拍序

这样问题所围绕的解决方法就是加锁!!在进行搭配volatile,wait,notify来进行解决问题和优化
Java多线程【状态与安全问题】文章来源地址https://www.toymoban.com/news/detail-407143.html

到了这里,关于Java多线程【状态与安全问题】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java基础之线程安全问题以及线程安全集合类

    当多个线程同时访问同一个临界资源时,原子操作可能被破坏,会导致数据丢失, 就会触发线程安全问题 临界资源: 被多个线程同时访问的对象 原子操作: 线程访问临界资源的过程中不可更改和缺失的操作 互斥锁 每个对象都默认拥有互斥锁, 该锁默认不开启. 当开启互斥锁之后

    2024年01月18日
    浏览(15)
  • Java中的多线程——线程安全问题

    作者:~小明学编程   文章专栏:JavaEE 格言:热爱编程的,终将被编程所厚爱。 目录 多线程所带来的不安全问题 什么是线程安全 线程不安全的原因 修改共享数据 修改操作不是原子的 内存可见性对线程的影响 指令重排序 解决线程不安全的问题 synchronized 互斥 刷新内

    2024年02月03日
    浏览(40)
  • 【Java】线程安全问题

    在之前的文章中,已经介绍了关于线程的基础知识。 我的主页: 🍆🍆🍆爱吃南瓜的北瓜 欢迎各位大佬来到我的主页进行指点 一同进步!!! 我们创建两个线程t1和t2,对静态变量count执行++操作各50000次。 我们的预期结果是100000。但是当两个线程分别执行++操作时最后的结果

    2024年04月10日
    浏览(16)
  • java 线程安全问题 三种线程同步方案 线程通信(了解)

    线程安全问题指的是,多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。 下面代码演示上述问题,先定义一个共享的账户类: 在定义一个取钱的线程类 最后,再写一个测试类,在测试类中创建两个线程对象 某个执行结果: 为了解决前面的线程安全问题,

    2024年02月09日
    浏览(18)
  • java线程安全问题及解决

    当我们使用多个线程访问 同一资源 (可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程 只有读操作 ,那么不会发生线程安全问题。但是如果多个线程中对资源有 读和写 的操作,就容易出现线程安全问题。 案例: 火车站要卖票,我们模拟火车站的卖票

    2024年02月15日
    浏览(14)
  • Java多线程基础-8:单例模式及其线程安全问题

    单例模式是经典的设计模式之一。什么是设计模式?代码的设计模式类似于棋谱,棋谱就是一些下棋的固定套路,是前人总结出来的一些固定的打法。依照棋谱来下棋,不说能下得非常好,但至少是有迹可循,不会下得很糟糕。代码的设计模式也是一样。 设计模式,就是软件

    2024年02月05日
    浏览(20)
  • 【Java|多线程与高并发】线程安全问题以及synchronized使用实例

    Java多线程环境下,多个线程同时访问共享资源时可能出现的数据竞争和不一致的情况。 线程安全一直都是一个令人头疼的问题.为了解决这个问题,Java为我们提供了很多方式. synchronized、ReentrantLock类等。 使用线程安全的数据结构,例如ConcurrentHashMap、ConcurrentLinkedQueue等

    2024年02月09日
    浏览(18)
  • Java中SimpleDateFormat的线程安全性问题

    在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但不同的方法获取到的时间格式不尽相同,这时就需要一种格式化工具,把时间显示成我们需要的格式,最常用的方法就是使用SImpleDateFormat类。这是一个看上去功能比较简单的类,但使用不当,也

    2024年01月25日
    浏览(21)
  • JavaEE:多线程(2):线程状态,线程安全

    目录 线程状态 线程安全 线程不安全 加锁 互斥性 可重入  死锁 死锁的解决方法  Java标准库中线程安全类 内存可见性引起的线程安全问题 等待和通知机制 线程饿死 wait notify 就绪:线程随时可以去CPU上执行,也包含在CPU上执行的线程 阻塞:这个线程暂时不方便去CPU上执行

    2024年01月23日
    浏览(18)
  • 针对java中list.parallelStream()的多线程数据安全问题我们采用什么方法最好呢?

    当使用List.parallelStream()方法进行多线程处理时,可能会涉及到数据安全问题。下面是一些常见的方法来处理parallelStream()的多线程数据安全问题: 1. 使用线程安全的集合:Java中提供了线程安全的集合类,如CopyOnWriteArrayList和synchronizedList等。可以将原始的List转换为线程安全的集

    2024年02月10日
    浏览(17)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包