首页 > 其他 > 详细

JUC并发编程(三)

时间:2021-04-06 20:33:10      阅读:11      评论:0      收藏:0      [点我收藏+]

15、异步回调

Future设计的初衷:对异步线程的返回结果进行建模。通俗的讲类似于ajax,当异步线程返回的结果是A的时候该如何,返回B又该如何。

public static void main(String[] args) throws ExecutionException, InterruptedException {

    //没有返回值的 runAsync 异步回调
    /*CompletableFuture completableFuture = CompletableFuture.runAsync(() ->{
            System.out.println(Thread.currentThread().getName()+"ok");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("11111111111111");
        completableFuture.get();*/


    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() ->{
        try {
            int result = 10/0;
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1024;
    });
    System.out.println("11111111111111");

    completableFuture.whenComplete((u,t) ->{//异步线程成功执行完成,并返回结果
        System.out.println("u======>"+u);
        System.out.println("t======>"+t);
    }).exceptionally((e) ->{//异步线程执行过程中报错
        System.out.println("exceptionally"+e.getMessage());
        return 2333;
    });
}

16、JMM

什么是JMM

? java内存模型,是一种约定,或则说是一个概念。它规定了java中主内存和线程内存之间的关系,具体如下图所示:

技术分享图片

? 如上图所示,内存交互经历了8个操作:

    1. lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
    1. unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
    1. read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
    1. load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
    1. use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
    1. assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
    1. store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
    1. write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

JMM 对这八种指令的使用,制定了如下规则:

  • 1.不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 2.不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 3.不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 4.一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
  • 5.一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 6.如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 7.如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 8.对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题: 程序不知道主内存的值已经被修改过了

技术分享图片

17、volatile

17.1 保证可见性
public class Demo1 {
    volatile  static boolean flag = false;
    public static void main(String[] args) {
        new Thread( () ->{
            System.out.println("线程开始");
            while(!flag){

            }
            System.out.println("线程结束");
        }).start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("已经修改了flag的值");
    }
}
17.2 不能保证原子性

技术分享图片

解决原子性问题:

public class Demo2 {
    static AtomicInteger atomicInteger = new AtomicInteger();
    public static void main(String[] args) {
        for (int i = 0; i < 20000; i++) {
            new Thread(() ->{
                atomicInteger.addAndGet(1);
            }).start();
        }
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInteger.get());
    }
}
17.3 不可重排序

指令重排:我们写的程序,计算机并不是按照你写的那样去执行的。

源代码 —> 编译器优化的重排 —> 指令并行也可能会重排 —> 内存系统也会重排 ——> 执行

处理器在执行指令重排的时候,会考虑:数据之间的依赖性

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

我们希望的执行顺序是1234,但是计算机执行的顺序可能是1324或则2413,但不可能是4132。

有些指令重排不会造成结果的不同,但有些会,例如:

默认 a=0,b=0
| 线程A   | 线程B   |
| ----- | ----- |
| x = a | y = b |
| b =1  | a = 2 |

我们期望得到的结果是x=0,y=0;指令重排后可能得到x=2,y=1。

volatile可以避免指令重排,其工作原理是通过建立内存屏障禁止上下指令的顺序交换,如下图所示:

技术分享图片

18、单例模式

18.1 饿汉式
//单例模式   饿汉式
public class Hungry {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    
    private static Hungry hungry = new Hungry();

    private Hungry() {
    }

    public Hungry getInstance() {
        return hungry;
    }
}
18.2 DCL懒汉式
//单例模式 DCL懒汉式
public class Lazy {
    /**
     *一定要用volatile修饰,保证new Lazy();过程不指令重排
     * 计算机指令执行顺序:
     * 1. 分配内存空间
     * 2、执行构造方法,初始化对象
     * 3、把这个对象指向这个空间
     *
     * 期望顺序是:123
     * 特殊情况下实际执行:132  ===>  此时 A 线程没有问题
     *    当A线程执行到第3步,若额外加一个 B 线程 进来,B线程会认为lazy已经实例化了,
     *	  但此时lazy还没有完成构造。
     */
    private volatile Lazy lazy;

    private Lazy() {
    }
    
    public Lazy getInstance() {
        if(lazy == null ){
            synchronized (Lazy.class){
                if(lazy == null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
}
18.3 静态内部类
public class Holder  {
    private Holder () {
    }
    public Holder getInstance(){
        return InnerClass.HOLDER;
    }
    public static class InnerClass{
        private static Holder HOLDER = new Holder();
    }
}
18.4 单例模式一定只能创建一个实例吗

单例模式并不安全,一般的单例模式都可以通过反射创建多个实例,换句话说反射可以破坏单例!

//单例模式 DCL懒汉式
public class Lazy {
    private volatile Lazy lazy;
    private Lazy() {}
    public Lazy getInstance() {
        if(lazy == null ){
            synchronized (Lazy.class){
                if(lazy == null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class lazyClazz = Lazy.class;
        //获取构造方法
        Constructor<Lazy> declaredConstructor = lazyClazz.getDeclaredConstructor(null);
        //破坏构造方法的私有化
        declaredConstructor.setAccessible(true);
        //初始化第一个实例
        Lazy lazy1 = declaredConstructor.newInstance();
        //初始化第二个实例
        Lazy lazy2 = declaredConstructor.newInstance();
        System.out.println("layz1====>"+lazy1);
        System.out.println("layz2====>"+lazy2);
    }
}

执行结果:

技术分享图片

18.5 安全的单例模式(枚举)
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance() {
        return INSTANCE;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class enumSingleClazz = EnumSingle.class;

        Constructor declaredConstructor = enumSingleClazz.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = (EnumSingle) declaredConstructor.newInstance();
        EnumSingle enumSingle2 = (EnumSingle) declaredConstructor.newInstance();
        System.out.println("enumSingle1=====>"+enumSingle);
        System.out.println("enumSingle2=====>"+enumSingle2);
    }
}

执行结果:

技术分享图片

错误原因:

技术分享图片

19、深入理解CAS

什么是CAS?

CAS是CPU的开发原语。CAS全称CompareAndSet ,比较并更新。

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //符合期望 修改值
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        //加1
        atomicInteger.getAndIncrement();
        //不符合期望,不更改
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

    }
}

AtomicInteger源码分析

技术分享图片

技术分享图片

技术分享图片

CAS的问题:ABA问题,即一个线程将变量的值由2020改为2021,然后在改为2020,另外一个线程对该值的变化毫不知情。

技术分享图片

20、原子引用(解决ABA问题)

带版本号 的原子操作!即乐观锁。

public class CASDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference(1,1);
        //线程A
        new Thread( () ->{
            System.out.println(atomicReference.compareAndSet(1, 2,1,2));
            System.out.println(atomicReference.getReference());
            System.out.println(atomicReference.compareAndSet(2, 1,2,3));
            System.out.println(atomicReference.getReference());
        },"A").start();
        TimeUnit.SECONDS.sleep(3);
        //主线程
        System.out.println(atomicReference.compareAndSet(1, 3,1,2));
        System.out.println(atomicReference.getReference());
    }
}

执行结果

技术分享图片

21、关于各种锁得理解

21.1 公平锁、非公平锁

公平锁: 非常公平, 不能够插队,必须先来后到!

非公平锁:非常不公平,可以插队 (默认都是非公平)

public ReentrantLock() {
    sync = new NonfairSync(); 
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync(); 
}
21.2 可重入锁

可重入锁(递归锁)

技术分享图片

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            try {
                phone.call();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                phone.call();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{

    public synchronized void call() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"call=================");
        TimeUnit.SECONDS.sleep(1);
        msg();//这里也有锁(sms锁 里面的call锁)
    }
    public synchronized void msg(){
        System.out.println(Thread.currentThread().getName()+"msg=================");
    }
}

Lock 版

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            try {
                phone2.call();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                phone2.call();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone2{
    Lock lock = new ReentrantLock();
    public  void call() throws InterruptedException {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"call=================");
            TimeUnit.SECONDS.sleep(1);
            msg();//这里也有锁(sms锁 里面的call锁)
        }finally {
            lock.unlock();
        }
    }
    public  void msg(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"msg=================");
        }finally {
            lock.unlock();
        }
    }
}
21.3 自旋锁

源码中的自旋锁

技术分享图片

定义一个自旋锁

/**
 * 自定义一个自旋锁
 */
public class MyLock {
    //原子引用
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //加锁
    public void lock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"======>lock");

        while (!atomicReference.compareAndSet(null,thread)){//加锁  自旋

        }
    }
    //解锁
    public void unlock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"======>unlock");
        atomicReference.compareAndSet(thread,null);//解锁
    }
}
public class Test {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        new Thread(()->{
            myLock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                myLock.unlock();
            }

        },"A").start();
        new Thread(()->{
            myLock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                myLock.unlock();
            }
        },"B").start();
    }
}

执行结果

技术分享图片

21.4 死锁

死锁是什么

两个线程去抢夺对方的资源

技术分享图片

public class DeadLock {
    static Lock  callLock = new ReentrantLock();
    static Lock msgLock = new ReentrantLock();
    public static void main(String[] args) {
        Phone3 phone3 = new Phone3(callLock,msgLock);
        Phone3 phone4 = new Phone3(msgLock,callLock);
        new Thread(()->{
            phone3.test();
        },"A").start();
        new Thread(()->{
            phone4.test();
        },"B").start();
    }
}

class Phone3{
    Lock  callLock;
    Lock msgLock;
    public Phone3(Lock callLock, Lock msgLock) {
        this.callLock = callLock;
        this.msgLock = msgLock;
    }
    public void test(){
        synchronized (callLock){
                call();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (msgLock){
                msg();
            }
        }
    }
    public void call(){
        System.out.println(Thread.currentThread().getName()+"============call");
    }

    public void msg(){
        System.out.println(Thread.currentThread().getName()+"============msg");

    }
}

执行结果

技术分享图片

如何排查:

  1. jps定位进程号 jps -l

技术分享图片

2、 jstack查看堆栈信息 jstack 进程号

技术分享图片

?

?
(完结,撒花)

JUC并发编程(三)

原文:https://www.cnblogs.com/mc-rack/p/14622730.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!