并发编程问题的源头及解决方法


前言

并发编程问题的源头主要包含三个方面:原子性、 可见性、有序性

一、理解线程安全

是指当多个线程访问同一个对象的时候,不管运行时环境采用何种调度方法或者这些线程之间如何调度执行,并且在主调函数中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。

二、性能优化带来的问题

  • CPU增加了高速缓存,均衡与内存的速度差异(可见性)
  • 操作系统增加进程、线程、以及分时复用cpu,均衡cpu与i/o设备的速度差异(原子性)
  • 编译程序优化指令的执行顺序,使得能够更加合理的利用缓存(有序性)

三、三大问题分析

问题分析
可见性由于每一个CPU中都有自己的高速缓存,CPU在操作自己的高速缓存的时候对于其它CPU是不可见的
原子性线程在运行的过程中由于执行时间过程或者遇到阻塞等情况的时候,CPU调度的时间片的切换,导致读取或写入内存的值与预期结果不一致
有序性编译重排序–>指令重排序–>内存系统重排序–>最终指令重排序,导致线程执行的顺序不可控,导致多个操作同一个值时,结果与预期结果不一致
public class VisibilityProblem {
    static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            int i = 0;
            while (!stop){
                i++;
            }
            System.out.println("子线程执行结束");
        });
        thread.start();
        System.out.println("子线程开始运行");
        Thread.sleep(1000L);
        stop = true;
    }
}

public class AtomicityProblem {
    static int i = 0;
    static void add(){
        try {
            Thread.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        i++;
    }
    public static void main(String[] args) throws InterruptedException {
        for (int j = 1; j < 1001; j++) {
            new Thread(AtomicityProblem::add).start();
        }
        Thread.sleep(4000L);
        System.out.println(i);
    }
}

四、什么是JMM?

Java内存模型是一种抽象结构,它提供了合理的禁用缓存以及禁止重排序 的方法来解决可见性、有序性问题
使volatile、synchronized、final关键字来解决可见性和有序性的问题

五、Synchronized

可以解决可见性、原子性、有序性问题

1.锁范围

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。
public class SynchronizedTest {
    public synchronized void run(){
        //do something
    }
    
    public synchronized static void jump(){
        //do something
    }
    
    public void go(){
        //TODO
        synchronized (this){
            //do something
        }
        //TODO
    }
    public static void swim(){
        //TODO
        synchronized (SynchronizedTest.class){
            //do something
        }
        //TODO
    }
}

2.synchronized的优化

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

六、Volatile

volatile可以用来解决可见性和有序性问题

1.底层lock指令作用

  • 将当前处理器缓存行的数据写回到系统内存。
  • 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。(禁止高速缓存保证可见性)

2.使用场景

当存在多个线程对同一个共享变量进行修改的时候,需要增加volatile,保证数据修改的实时可见

3.总结

  • 本质上来说:volatile实际上是通过内存屏障来防止指令重排序以及禁止cpu高速缓存来解决可见性问题。
  • Lock指令,它本意上是禁止高速缓存解决可见性问题,但实际上在 这里,它表示的是一种内存屏障的功能。也就是说针对当前的硬件环境,JMM层面采用Lock指令作为内存屏障来解决可见性问题

七、final域

  • 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序
  • 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操 作之间不能重排序。

1.写final域重排序规则

  • JMM禁止编译器把final域的写重排序到构造函数之外。
  • 编译器会在final域的写之后,构造函数return之前,插入一个 StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造 函数之外

2.读域的重排序规则

  • 在一个线程中,初次读对象引用与初次读该对象包含的final 域,JMM禁止处理器重排序这两个操作,编译器会在读final 域操作的前面插入一个LoadLoad屏障

八、Happens-Before模型

Happens-Before是一种可见性规则,它表达的含义是前面一个操作的结 果对后续操作是可见的

  1. 程序顺序规则
  2. 监视器锁规则
  3. Volatile变量规则
  4. 传递性
  5. start()规则
  6. Join()规则

九、原子类Atomic

1.原子性问题的解决方案

  • synchronized、Lock
  • J.U.C包下的Atomic类

2.Atomic实现原理

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
 	//unsafe是java提供的获得对对象内存地址访问的类
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //内存偏移量
    private static final long valueOffset;
 
    static {
        try {
        	//通过反射获取value值
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
 
 	//记录时间变量,保证可见性和有序性
    private volatile int value;
 
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    //
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    public AtomicInteger() {
    }
 
    ......
}
//测试
public class AtomicTest {
    private static AtomicInteger atomicInteger = new AtomicInteger(0);
    private static int i = 0;

    public static void invoke(){
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //非原子操作
        i++;
        atomicInteger.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 2000 ; i++) {
            Thread thread1 = new Thread(AtomicTest::invoke);
            Thread thread2 = new Thread(AtomicTest::invoke);
            thread1.start();
            thread2.start();
        }
        TimeUnit.SECONDS.sleep(5);
        //对比输出结果
        System.out.println(atomicInteger);
        System.out.println(i);
    }
}

十、ThreadLocal的使用

public class ThreadLocalTest {

    static int num = 0;
    public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue(){
            return 0;
        }
    };

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(()->{
                int initValue=local.get();
                System.out.println("获取local里面的初始值:"+ initValue);
                initValue += 5;
                num = initValue;
               local.set(num);
                System.out.println(Thread.currentThread().getName()+ ":" + num);
            },"Thread" + i);
        }
        for (int i = 0; i < 5 ; i++) {
            threads[i].start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值