线程安全问题 synchronized (原子性 (可见性 防止指令重排(volatile)))

文章讨论了多线程环境下线程安全的重要性,通过一个示例展示了在并发情况下,共享资源可能导致的计数不准确问题。提出了原子性、可见性和防止指令重排作为线程安全的三个要求。解决方案包括使用不同对象避免共享、使用`synchronized`关键字保证同步,以及静态方法上的锁机制。
摘要由CSDN通过智能技术生成

线程安全问题

由于多线程的(并行)引入有一些共享的代码块(资源)会出现不准确问题;
被总结为一下原因:不能一气呵成的执行完,共想变量没有可见性,在执行很多指令是,可能说出错。

线程安全必须满足一下要求
1,原子性
2,可见性
3,防止指令重派

用一个实例来说明问题所在
让两个线程各加5000;

import org.w3c.dom.css.Counter;

public class ThreadUnsafeCount {
    public static class Counter{
        int count=0;
        void increase(){
            count++;
        }
    }
    public static void main(String [] args) throws InterruptedException {

        Counter counter=new Counter();
        Thread thread01=new Thread(()->{
            for(int i=0;i<50000;i++){
                counter.increase();
            }
        });
        Thread thread02=new Thread(()->{
            for(int i=0;i<50000;i++){
                counter.increase();
            }
        });
        thread01.start();
        thread02.start();
        //join()等待执行完成,在进行下面的代码
        thread01.join();
        thread02.join();
        System.out.println(counter.count);
    }
}

输出
69176
原本应该是100000,但是输出才69176(每一次输出可能会不一样)
这就是线程不安全导致的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在单线程下指令重排没有问题,但是在多线程下可能就会有问题,一般是对象还没初始化完成就被别的线程调用了。

具体例子
在这里插入图片描述

解决线程安全问题
在这里插入图片描述
这里有一些想操作系统中的 PV操作思想(获取到一个资源,对这个资源临界区同时只能一个进程或者线程访问,在退出时归还临界区)

首先要明白不同的对像不同的锁

import org.w3c.dom.css.Counter;

public class ThreadUnsafeCount {
    public static class Counter{
        int count=0;
        void increase(){
            count++;
        }
    }
    public static void main(String [] args) throws InterruptedException {

        Counter counter1=new Counter();
        Counter counter2=new Counter();
        Thread t1=new Thread(()->{
            for(int i=0;i<5000;i++){
                counter1.increase();
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<5000;i++){
                counter2.increase();
            }
        });
        t1.start();
        t.start();
        //join()等待执行完成,在进行下面的代码
        t1.join();
        t2.join();
        int total=counter1.count+ counter2.count;
        System.out.println(total);
    }
}

这样输出的结果就是正确的(100000)
在这里插入图片描述

2,使用synchronized来实现

import org.w3c.dom.css.Counter;

public class ThreadUnsafeCount {
    public static  class Counter{
        int count=0;
        synchronized void increase(){
            count++;
        }
    }
    public static void main(String [] args) throws InterruptedException {

        Counter counter=new Counter();
        /*Counter counter01=new Counter();
        Counter counter02=new Counter()*/;
        Thread thread01=new Thread(()->{
            for(int i=0;i<5000;i++){
                /*counter01.increase();*/
                counter.increase();
            }
        });
        Thread thread02=new Thread(()->{
            for(int i=0;i<5000;i++){
               /* counter02.increase();*/
                counter.increase();
            }
        });
        thread01.start();
        thread02.start();
        //join()等待执行完成,在进行下面的代码
        thread01.join();
        thread02.join();
        /*int total=counter01.count+ counter02.count;
        System.out.println(total);*/
        System.out.println(counter.count);
    }
}

在方法块上加入synchronized就可以实现。

 synchronized void increase(){
            count++;
        }

在这里插入图片描述
在这里插入图片描述
这里先了解一下 JMM(java Memory Model)
在这里插入图片描述
synchronized支持线程可重入
加入目前有两个线程,非别是 小红 和小吕(他们之前没有线程进入)
小红进入了一个synchronized锁定的代码块,在代码块中还有synchronized锁,小红还是可以进去,但是对于小吕来说第一个synchronized都进不去,主要原因是 他们是在同一个对象下实现的,一般这里是这样实现的,在synchronized头中定义一个计数器,进入一个线程就+1,退出就-1(和操作系统中的PV一个原理;
在这里插入图片描述
synchronized修饰静态方法
synchronized 对一个静态方法上锁

import sun.plugin2.message.BestJREAvailableMessage;

public class SychronizedInstance {
    public static void main(String[] args) {
        Counter counter01=new Counter();
        Counter counter02=new Counter();
        Counter counter03=new Counter();

        Thread t1=new Thread(()->{
            counter01.count();
            //System.out.println(Thread.currentThread().getName()+"获取到了锁");
        },"t1");
        Thread t2=new Thread(()->{
            counter02.count();
            //System.out.println(Thread.currentThread().getName()+"获取到了锁");
        },"t2");
        Thread t3=new Thread(()->{
            counter03.count();
            //System.out.println(Thread.currentThread().getName()+"获取到了锁");
        },"t3");
        t1.start();
        t2.start();
        t3.start();
    }

    private static class Counter{
        synchronized static void count() {
            while(true){//如果没有while(true)循环 这个锁被其中一个对象占用后又释放,看不出静态
                // 方法锁的一个类,所以静态方法是对这个类上的锁,和创建多少个对象无关
                System.out.println(Thread.currentThread().getName()+"我是静态方法上的锁,锁的是整个类");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

截取输出的一部分
t2我是静态方法上的锁,锁的是整个类
t1我是静态方法上的锁,锁的是整个类
t3我是静态方法上的锁,锁的是整个类
t2我是静态方法上的锁,锁的是整个类
t1我是静态方法上的锁,锁的是整个类

方法锁的一个类,所以静态方法是对这个类上的锁,和创建多少个对象无关

你可以将将这里的 方法中的static去掉;如下,你就会感受到这是一个在并发的,并没有互斥的执行。

private static class Counter{
        synchronized  void count() {
            while(true){//如果没有while(true)循环 这个锁被其中一个对象占用后又释放,看不出静态
                // 方法锁的一个类,所以静态方法是对这个类上的锁,和创建多少个对象无关
                System.out.println(Thread.currentThread().getName()+"我是静态方法上的锁,锁的是整个类");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值