深入快出并发线程安全性问题

并发问题的源头–原子性,可见性,有序性

/**
 * 原子性验证
 */
public class AtomicTest {
    
    
    public static int count = 0;
    public static void methodSleep()  {
    
    
        try {
    
    
            Thread.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //不是一个原子性操作getstatic iadd putstatic
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(Demo1::methodSleep).start();
            System.out.println("-->"+count);
        }
        Thread.sleep(4000);// 保证上面的线程都执行完
        System.out.println(count);
    }
}
// 控制台终端使用javap -v xxx.class 命令
public static void methodSleep();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: ldc2_w        #2                  // long 2l
         3: invokestatic  #4                  // Method java/lang/Thread.sleep:(J)V
         6: goto          14
         9: astore_0
        10: aload_0
        11: invokevirtual #6                  // Method java/lang/InterruptedException.printStackTrace:()V
        14: getstatic     #7                  // Field count:I
        17: iconst_1
        18: iadd
        19: putstatic     #7                  // Field count:I
        22: return

发现count++不是一个原子性操作 实际分为三步;所以在多线程的操作之下,都有可能取到同一个值,或者加了但是还未putstatic情况发生
有序性:Java程序源码 再被编译器编译之后的 可能会发生指令的重排序
可见性:指在硬件层面的造成的不同情况下的不可见性

Java的内存模型Java Memory Model

点击深入理解JMM 真正理解了JMM就学习JVM事半功倍

同步关键字synchronized

  • 对于普通同方法 锁的是当前实例对象
  • 对于静态同步方法 锁的是当前类的class对象
  • 对于同步代码块 锁的是synchronized括号里面的对象
public class SynchronizedTest {
    
    
    //对象锁:同一个对象内有效
    public synchronized void thisTest(){
    
    }
    //对于静态的方法->类级别的锁 SynchronizedTest.class
    public synchronized static void classTest(){
    
    }

    public void thisTest1(){
    
    
        //TODO
        synchronized (this){
    
    }
        //TODO
    }

    public void classTest1(){
    
    
        //TODO
        synchronized (SynchronizedTest.class){
    
    }
        //TODO
    }
    /**
     * synchronized:底层实现原理-->monitorenter(获得锁)  monitorexit(释放锁)
     */
    public static void main(String[] args) {
    
    
        SynchronizedTest st = new SynchronizedTest();
        SynchronizedTest st1 = new SynchronizedTest();
        new Thread(st::thisTest).start();
        new Thread(st1::thisTest).start();
        new Thread(st::classTest1).start();
        new Thread(SynchronizedTest::classTest).start();
    }
}
//
 public void classTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class threadsecurity/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter // 加了synchronized之后 会有
         5: aload_1
         6: monitorexit // 加了synchronized之后 会有
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit // 如果异常了也会释放锁
        13: aload_2
        14: athrow
        15: return

JDK1.6以后对synchronized进行了优化

  • 自适应自旋锁
  • 偏向锁 轻量级锁
  • 锁消除 锁粗化

volatile

  • 怎么保证可见性?

加了volatile之后 对于的变量汇编指令会有一个lock:使得当前处理器的缓存写入内存 同时其他的处理器的缓存无效
触发了禁用缓存

  • volatile怎么解决有序性问题?
 		/**
         * CPU层面的内存屏障: 解决了指令重排序的问题
         * store barrier, 写屏障 保证在该指令之后的写指令 后执行{先把该指令之前的 写操作写到CPU缓存里面}
         * load barrier, 读屏障 保证在该指令之后的读指令 后执行{先把该指令之前的 读操作先读出来}
         * full barrier, 集合了前面两个指令的功能
         */
        
        /**
         * 在Java层面:4种屏障
         * LoadLoad Barrier:
         * StoreStore Barrier:
         * LoadStore Barrier:
         * StoreLoad Barrier:
         * 本质上 volatile 实际上是通过内存屏障来防止指令重排序 
         * 以及禁止CPU高速缓存来解决可见性问题
         */

final

  • final是Java的关键字,可以声明成员变量,方法,类;一旦将这个引用声明final就不能改变这个引用了
/**
 * @Description: 写final域的重排序规则
 */
public class FinalDemo {
    
    
    int i;
    final int j;
    static FinalDemo finalDemo;

    public FinalDemo(){
    
    
        i=1;
        j=2;
    }

    public static void write(){
    
    
        finalDemo = new FinalDemo();
    }

    public static void read(){
    
    
        FinalDemo obj = finalDemo;
        System.out.println(obj.i);
        System.out.println(obj.j);
    }

    public static void main(String[] args) {
    
    
        Thread thread = new Thread(()->{
    
    
            FinalDemo::write();
        });

        Thread thread1 = new Thread(()->{
    
    
            FinalDemo::read();
        });
        //可能会出现j=2;但是i=0的情况
        //在指令重排序之后 会出现普通变量i没来得及 被初始化,但是final修饰的j一定会在前面被初始化
        thread.start();
        thread1.start();
        /**
         * final与线程安全有什么关系?  对于final域,编译器和处理器要遵循两个重排序规则
         * 1.在构造函数内对于一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量, 这两个操作之间不能重排序-->保证先写再读
         * 2.初次读一个包含final域的对象的引用, 与随后初次读这个final域,这两个操作之间不能重排序-->保证两个线程之间不能重排序,按照先后顺序来
         *
         * 前提 是不能出现"逃逸"的情况-->就是不能在构造函数里面 初始化实例 object = this;-->指令重排序大于 初始化变量的指令,会是的final变量
         * 没来得及被初始化 就被可见 read!
         */
    }
}

happens-before规则

6种happens-before规则

  • 1.程序顺序规则(一般是从前往后), as-if-serial,对于单线程程序来说 不管怎么重排序 程序运行结果不会变
  • 2.监视器锁规则:一个锁的解锁 会先于 后续这个锁的加锁monitorenter(获得锁) monitorexit(释放锁)
  • 3.volatile 变量规则: volatile的变量的写 先于 后续任意对于它的读
  • 4.传递性: A>B, B>C–>A>C
  • 5.start()规则: A线程 来执行操作 ThreadB.start(),那么A线程的ThreadB.start()操作先于 线程B中的其他任意操作
  • 6.join()规则: A线程 来执行操作ThreadB.join(),那么线程B的任意操作先于线程A从ThreadB.join()操作成功返回.

原子类atomic

  • Unsafe类
  • CAS(Compare And Swap)
public class AtomicDemo {
    
    
    public static int count = 0;
    public static AtomicInteger atomicInteger = new AtomicInteger(0);
    //加上synchronized也可以实现原子性
    public static void methodSleep()  {
    
    
        try {
    
    
            Thread.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //不是一个原子性操作getstack add putstack
        atomicInteger.incrementAndGet();
        System.out.println(atomicInteger.get());
//        count++;
//        System.out.println(count);
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(AtomicDemo::methodSleep).start();
            Thread.sleep(1);
        }
        Thread.sleep(4000);
        System.out.println(atomicInteger.get());
//        System.out.println(count);
    }
}

ThreadLocal实现原理分析

public class ThreadLocalDemo {
    
    
    //会出现线程之间相互影响 不独立
    private static Integer num;
    //在使用threadLocal之后 每个线程会相互独立
    public static final ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
    
    
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
    
    
            threads[i] = new Thread(() -> {
    
    
                num = local.get();
                num += 10;
                local.set(num);
                System.out.println(Thread.currentThread().getName() + "-->" + num);
            }, "Thread-" + (i + 1));
        }

        for (Thread thread : threads) {
    
    
            thread.start();
        }
    }
}
// print result
Thread-1-->10
Thread-9-->10
Thread-8-->10
Thread-3-->10
Thread-2-->10
Thread-4-->10
Thread-7-->10
Thread-6-->10
Thread-10-->10
Thread-5-->10

猜你喜欢

转载自blog.csdn.net/blackxc/article/details/107942753
今日推荐