JUC并发编程学习笔记(二)

16. JMM

什么是JMM:Java内存模型。
关于JMM的一些同步约定:

  1. 线程解锁前:必须立即将共享内存同步回主存
  2. 线程加锁前:必须读取主存中的最新值到工作内存中
  3. 加锁和解锁是同一把锁

线程 工作内存 主内存
8种操作
在这里插入图片描述

问题:线程B修改了值,但线程A不能及时可见。
在这里插入图片描述

java内存模型JMM理解整理:https://www.cnblogs.com/null-qige/p/9481900.html

17. Volatile

请你说一下对volatile的理解:volatile是Java虚拟机提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
    (原子性:不可分割。线程A在执行任务的时候,不能被打扰,也不能被分割。同时成功或失败。)
  3. 禁止指令重排

Volatile保证可见性Demo:

package com.huathy.volatileTest;

import java.util.concurrent.TimeUnit;

/**
 * @ClassPath: com.huathy.volatileTest.JMMDemo
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-16 17:00
 */

/**
 * 测试2:
 * private static int num = 0;
 * 程序输出1,但线程任务仍未停止。
 * 两个线程A,B
 * A线程工作内存读取num=0。B线程修改工作内存num=1,并回写主内存。
 * 但线程A不能及时可见主内存中的num值。
 *
 * 测试1:
 * private volatile static int num = 0;
 *
 */
public class JMMDemo {
    //不加volatile程序会陷入死循环。
    //    private static int num = 0;
    //加上volatile可以保证程序的可见性
    private volatile static int num = 0;

    public static void main(String[] args) {
        new Thread( ()->{
            while (num == 0){

            }
        } ).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        System.out.println(num);
    }
}

Volatile不保证原子性,Atomic原子类Demo

问题:如何不使用Synchronized和lock锁,来保证原子性。
在这里插入图片描述

可以使用原子类Atomic,其底层直接与操作系统相关。

package com.huathy.volatileTest;

/**
 * @ClassPath: com.huathy.volatileTest.VDTest
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-16 17:18
 */

import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile不保证原子性
 */
public class VDTest {
    //volatile不保证原子性
//    private volatile static int num = 0;
    private static AtomicInteger num = new AtomicInteger();

    private static void add(){
//        num ++;     //这不是一个原子性操作
        num.getAndIncrement();      //底层是CAS。Unsafe类,这里的+1操作是在内存中修改值。
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread( ()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            } ).start();
        }

        while (Thread.activeCount() > 2){
            Thread.yield();     //让出CPU资源
        }

        System.out.println(Thread.currentThread().getName() + "-" + num);
    }
}

指令重排:

  1. 什么是指令重排:计算机并不是按照所写的程序代码执行的。

    源代码 -> 编译器优化重排 -> 指令并行重排 -> 内存系统重排 -> 执行。处理器在进行指令重排的时候,会考虑到数据之间的依赖性问题。

  2. volatile可以避免指令重排!
    内存屏障。CPU指令。作用:

    a. 保证特定操作的执行顺序。

    b. 可以保证某些变量的内存可见性。(利用这些特性,可以保证volatile的可见性。)

在这里插入图片描述

Volatile保证可见性,不保证原子性,由于内存屏障,可以防止指令重排。
内存屏障在单例模式使用较多。

18. 单例模式

饿汉式,DCL懒汉式。枚举为什么可以避免单例模式被破坏。

饿汉式

package com.huathy.single;

/**
 * @ClassPath: com.huathy.single.Demo01
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 16:48
 */

/**
 * 单例模式:饿汉式
 * 饿汉式问题:会造成资源浪费
 */
public class Hungry {

    private final static Hungry HUNGRY = new Hungry();

    //构造方法私有
    private Hungry(){}

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

DCL懒汉式

package com.huathy.single;

/**
 * @ClassPath: com.huathy.single.Lazy
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 16:53
 */

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 单例模式:懒汉式。
 *
 */
public class Lazy {

//    private static Lazy lazy;
    private volatile static Lazy lazy;

    private static boolean flag = false;

    private Lazy(){
        synchronized (Lazy.class){
//            if(lazy != null){
//                throw new RuntimeException("不要试图使用反射破坏单例");
//            }

            if( flag == false ){
                flag = true;
            }else{
                throw new RuntimeException("不要试图使用反射破坏单例");
            }
            System.out.println(Thread.currentThread().getName() + " 创建");
        }
    }

//    在并发下,这么写还是存在多次创建的问题
//    public static Lazy getInstance(){
//        if(lazy == null){
//            lazy = new Lazy();
//        }
//        return lazy;
//    }

    //双重检测锁模式,DCL懒汉式单例
    public static Lazy getInstance(){
        if(lazy == null){
            synchronized (Lazy.class){
                if(lazy == null){
                    //问题:不是原子性操作
                    lazy = new Lazy();
                    /**
                     * 1. 分配内存空间。
                     * 2. 执行构造方法,初始化对象。
                     * 3. 把这个对象指向这个空间。
                     * 如果这时发生指令重排:执行顺序由123 -> 132
                     * 多线程情况下B线程,在判断时不为空,直接返回lazy。但是由于lazy还未完成构造。
                     * 所以必须给lazy加上volatile关键字来防止指令重排
                     */
                }
            }
        }
        return lazy;
    }

    /**
     * 反射 可以破坏单例模式
     * 测试输出:可见创建了两次,并且两次的结果并不一致。
     * main 创建
     * main 创建
     * com.huathy.single.Lazy@14ae5a5
     * com.huathy.single.Lazy@7f31245a
     * 解决方案:
     * 在构造方法中进行加锁判断。问题:如果都使用反射进行创建?则可以在构造方法中加上flag判断
     */
    public static void main(String[] args) throws Exception {
        Lazy lazy = Lazy.getInstance();

        Field field = Lazy.class.getDeclaredField("flag");
        field.setAccessible(true);
        //1. 获取反射对象 2.获取无参的构造器
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        //设置构造器可见,无视私有构造器
        declaredConstructor.setAccessible(true);
        //通过反射创建对象
        field.set(lazy,false);
        Lazy lazy2 = declaredConstructor.newInstance();
        Lazy lazy3 = declaredConstructor.newInstance();
        System.out.println(lazy);
        System.out.println(lazy2);
        System.out.println(lazy3);
    }

//    public static void main(String[] args) {
//        测试
//        for (int i = 0; i < 10; i++) {
//            new Thread( ()->{
//                Lazy.getInstance();
//            } ).start();
//        }
//    }
}

静态内部类创建

package com.huathy.single;

/**
 * @ClassPath: com.huathy.single.Holder
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 17:07
 */

/**
 * 静态内部类
 */
public class Holder {
    private Holder(){}

    public static Holder getInstance(){
        return InnerCalss.HOLDER;
    }

    public static class InnerCalss{
        private static final Holder HOLDER = new Holder();
    }
}

反射导致单例不安全。枚举类

package com.huathy.single;

import java.lang.reflect.Constructor;

/**
 * @ClassPath: com.huathy.single.EnumSingle
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 17:39
 */
public enum EnumSingle {
    INSTANCE ;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance = EnumSingle.INSTANCE;
        EnumSingle instance2 = EnumSingle.INSTANCE;
        System.out.println(instance);
        System.out.println(instance2);
        //测试-> enum是单例的。
        //尝试破坏单例模式:异常 java.lang.IllegalArgumentException: Cannot reflectively create enum objects
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();
    }
}

枚举反编译源码

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package com.huathy.single;


public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/huathy/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

19. CAS

什么是CAS

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

缺点:

  1. 自旋锁循环消耗时间
  2. 一次只能保证一个共享变量的原子性
  3. 会存在ABA问题
package com.huathy.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassPath: com.huathy.cas.CASDemo01
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 23:51
 */
public class CASDemo01 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //CAS  compareAndSet:比较并交换。参数(期望,更新)
//        public final boolean compareAndSet(int expect, int update);
        //如果达到期望值,则更新,否则不更新。CAS是CPU的并发原语。
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

由于Java无法操作内存,但是Java可以通过native来调用本地C++方法操作内存。
在这里插入图片描述
在这里插入图片描述

CAS:ABA问题

B线程由于速度较快,在A还没有反应的时候,修改了内存的值。
在这里插入图片描述

package com.huathy.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassPath: com.huathy.cas.CASDemo01
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 23:51
 */
public class CASDemo01 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //CAS  compareAndSet:比较并交换。参数(期望,更新)
//        public final boolean compareAndSet(int expect, int update);
        //如果达到期望值,则更新,否则不更新。CAS是CPU的并发原语。
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2021,2020));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2020,6666));
        System.out.println(atomicInteger.get());
    }
}

解决方案:原子引用!

20. 原子引用

带版本号的原子操作。可解决ABA问题。

package com.huathy.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @ClassPath: com.huathy.cas.CASDemo01
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-17 23:51
 */
public class CASDemo02 {
    public static void main(String[] args) {
        /**
         * 类比乐观锁原理。
         * AtomicStampedReference:如果泛型是一个包装类,则需要注意对象的引用问题。
         * 这里的值不能写的太大。因为Integer对于在-128~127之间的值是在IntegerCache.cache中产生,会复用已有对象,可使用==比较。
         * 但之外的数据都会在堆中产生,并且不会服用已有对象。则需要使用equals比较。
         */
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);

        new Thread( ()->{
            //获得版本号
            System.out.println("A1 ==> " + atomicStampedReference.getStamp());

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(
                    atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("A2 ==> " + atomicStampedReference.getStamp());

            System.out.println(
                    atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("A3 ==> " + atomicStampedReference.getStamp());
        } ,"A" ).start();

        new Thread( ()->{
            int stamp = atomicStampedReference.getStamp();  //获得版本号
            System.out.println("B1 ==> " + atomicStampedReference.getStamp());

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(
                    atomicStampedReference.compareAndSet(1, 6,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("B2 ==> " + atomicStampedReference.getStamp());
        } ,"B" ).start();

    }
}

21. 锁

公平锁,非公平锁

公平锁:非常公平,不能插队,必须先来后到,顺序执行。抢占式。
非公平锁:不公平的,可以插队(默认都是非公平锁)非抢占式。

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

可重入锁(递归锁)

Synchronized

package com.huathy.lock;

/**
 * @ClassPath: com.huathy.lock.Demo01
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-18 13:55
 */
//Synchronized
public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread( ()->{
            phone.sms();
        } ,"A" ).start();

        new Thread( ()->{
            phone.sms();
        } ,"B" ).start();
    }
}

class Phone{
    //sms的锁需要等到call方法的锁释放后才会释放。
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName() + " sms");
        call();     //这里也有锁
    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + " call");
    }
}

Lock

package com.huathy.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassPath: com.huathy.lock.Demo01
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-18 13:55
 */
//Synchronized
public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();

        new Thread( ()->{
            phone.sms();
        } ,"A" ).start();

        new Thread( ()->{
            phone.sms();
        } ,"B" ).start();
    }
}

class Phone2{
    Lock lock = new ReentrantLock();
    //sms的锁需要等到call方法的锁释放后才会释放。
    public synchronized void sms(){
        //lock.lock(); lock.unlock();锁必须配对,否则可能死锁
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " sms");
            call();     //这里也有锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public synchronized void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

自旋锁 spinLock

package com.huathy.lock;

/**
 * @ClassPath: com.huathy.lock.SpinLockDemo
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-18 14:15
 */

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁,使用CAS操作
 */
public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "==> Lock");
        while (!atomicReference.compareAndSet(null,thread)){
        }
    }

    // 解锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "==> UnLock");
        atomicReference.compareAndSet(thread,null);
    }
}

class Test{
    public static void main(String[] args) {
        SpinLockDemo lock = new SpinLockDemo();

        new Thread( ()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"A" ).start();

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

死锁

死锁:资源互斥,请求保持,不可抢占,循环等待。
在这里插入图片描述

package com.huathy.lock;

import java.util.concurrent.TimeUnit;

/**
 * @ClassPath: com.huathy.lock.DeadLockDemo
 * @Author: Huathy
 * @Description:
 * @Date: 2021-01-18 14:43
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        String a = "A";
        String b = "B";

        new Thread(new MyThread(a,b),"T1" ).start();
        new Thread(new MyThread(b,a),"T2" ).start();
    }
}

class MyThread implements Runnable{
    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + " lock:" + lockA + " try get=>" + lockB);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + " lock:" + lockB + " try get=>" + lockA);
            }
        }
    }
}

死锁排查

  1. 使用jps -l定位进程号
    在这里插入图片描述

  2. 使用jstack 进程号查看进程信息,查看死锁问题
    在这里插入图片描述

posted @ 2021-01-18 15:42  Huathy  阅读(11)  评论(0)    收藏  举报  来源