Java线程安全的可见性与原子性

1.1  可见性

代码分析:

public class Demo {
    static int money = 1000;//初始钱数
    public static void main(String[] args) {
        
        Thread women = new Thread(()->{
            while(money < 200000);

            System.out.println("我们可以结婚了");
        });

        Thread men = new Thread(()->{
            System.out.println("给我5年,我存够钱来娶你");
            //搞个休眠,模拟5年
            try{
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            money = 200000;
            System.out.println("我存够钱了!");
        });
        women.start();
        men.start();

    }
}

程序问题 : 女孩虽然知道结婚基金是二十万,但是当基金的余额发生变化的时候,女孩无法知道最新的余额。

1.2 volatile解决

以上案例出现的问题 :

当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题

1,堆内存是唯一的,每一个线程都有自己的线程栈。

2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。

3 ,在线程中,每一次使用是从变量的副本中获取的。

Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值

 

public class Demo {
    volatile static int money = 1000;//初始钱数
    public static void main(String[] args) {
        
        Thread women = new Thread(()->{
            while(money < 200000);

            System.out.println("我们可以结婚了");
        });

        Thread men = new Thread(()->{
            System.out.println("给我5年,我存够钱来娶你");
            //搞个休眠,模拟5年
            try{
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            money = 200000;
            System.out.println("我存够钱了!");
        });
        women.start();
        men.start();

    }
}

1.3 synchronized解决

synchronized解决 :

1 ,线程获得锁

2 ,清空变量副本

3 ,拷贝共享变量最新的值到变量副本中

4 ,执行代码

5 ,将修改后变量副本中的值赋值给共享数据

6 ,释放锁

public class Demo {
    static int money = 1000;//初始钱数
    public static void main(String[] args) {
        
        Thread women = new Thread(()->{
           while(true){
                synchronized (Money.lock){
                    if(Money.money != 200000){
                        System.out.println("我们可以结婚了");
                        break;
                    }
                }
            }
        });

        Thread men = new Thread(()->{
            System.out.println("给我5年,我存够钱来娶你");
            //搞个休眠,模拟5年
            try{
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            money = 200000;
            System.out.println("我存够钱了!");
        });
        women.start();
        men.start();

    }
}

2.1  原子性

概述 : 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

问题展示:

public class Main {
    static int count = 0;
    public static void main(String[] args) {
        ArrayList<Thread> arrayList = new ArrayList ();
        
        for (int i = 0; i < 10; i++) {

            Thread thread = new Thread ( () -> {
                for (int j = 0; j < 100000; j++) {
                   count++;
                }
            } );

            thread.start ();
            arrayList.add (thread);
        }

        //用与判断所有线程是否结束
        while (!arrayList.isEmpty ()) {
            for (int i = 0; i < arrayList.size(); i++) {
                if (!arrayList.get (i).isAlive ()){
                    arrayList.remove (i--);
                }
            }
        }
        System.out.println (count);
    }
}

 运行后会发现输出结果并不是1000000,而是每运行一次就变化一次的数,

count++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断,这是两个过程,先取值运算再赋值,有可能在赋值的时候被打断,之后回来就会导致数据偏差了.

前面使用的volatile关键字能不能保证原子性呢,结果显而易见是不可以的,它只是让共享数据实时更新,确保数据的实时性,这里出现的问题是线程被打断导致数据赋值出错,我们可以通过加锁实现原子性.

public class Main {
    static int count = 0;
    public static void main(String[] args) {
        ArrayList<Thread> arrayList = new ArrayList ();
        
        for (int i = 0; i < 10; i++) {

            Thread thread = new Thread ( () -> {
                for (int j = 0; j < 100000; j++) {
                    
                    synchronized(""){
                        count++;
                    }

                }
            } );

            thread.start ();
            arrayList.add (thread);
        }

        //用与判断所有线程是否结束
        while (!arrayList.isEmpty ()) {
            for (int i = 0; i < arrayList.size(); i++) {
                if (!arrayList.get (i).isAlive ()){
                    arrayList.remove (i--);
                }
            }
        }
        System.out.println (count);
    }
}

虽然加锁可以实现原子性,当会导致效率大大降低,所以java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。

2.2 原子性_AtomicInteger

因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解

使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型

AtomicInteger: 原子更新整型

AtomicLong: 原子更新长整型

以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:

public AtomicInteger():	   			    初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):  初始化一个指定值的原子型Integer

int get():   			 				获取值
int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():     				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):                 以原子方式将输入的数值与实例中的值
                                        (AtomicInteger里value)相加,并返回结果。
int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值。

代码实现:

public class Main {
    public static void main(String[] args) {
        ArrayList<Thread> arrayList = new ArrayList ();
        AtomicInteger ai = new AtomicInteger ( 0 );
        for (int i = 0; i < 10; i++) {
            
            Thread thread = new Thread ( () -> {
                for (int j = 0; j < 100000; j++) {
                    ai.incrementAndGet();
                }
            } );

            thread.start ();
            arrayList.add (thread);
        }

        while (!arrayList.isEmpty ()) {
            for (int i = 0; i < arrayList.size(); i++) {
                if (!arrayList.get (i).isAlive ()){
                    arrayList.remove (i--);
                }
            }
        }
        System.out.println (ai);
    }
}

2.3 AtomicInteger-源码解析

其实就是利用CAS算法实现线程安全,并不会加锁使效率降低

//先自增,然后获取自增后的结果
public final int incrementAndGet() {
        //+ 1 自增后的结果
        //this 就表示当前的atomicInteger(值)
        //1    自增一次
        return U.getAndAddInt(this, VALUE, 1) + 1;
}

public final int getAndAddInt(Object o, long offset, int delta) {
        //v 旧值
        int v;
        //自旋的过程
        do {
            //不断的获取旧值
            v = getIntVolatile(o, offset);
            //如果这个方法的返回值为false,那么继续自旋
            //如果这个方法的返回值为true,那么自旋结束
            //o 表示的就是内存值
            //v 旧值
            //v + delta 修改后的值
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
            //作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。
            //                                 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。
            //如果修改失败,那么继续自旋。
        return v;
}

2.4 悲观锁和乐观锁

synchronized和CAS的区别 :

相同点:在多线程情况下,都可以保证共享数据的安全性。

不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。

如果别人修改过,那么我再次获取现在最新的值。

如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值