JUC

JMM

JVM运行程序的实体是线程,每个线程创建时JVM都会分配一个线程自己私有的工作内存

JMM(java memory model)是指Java内存模型(一种规范,并不真实存在),不是Java内存布局,不是所谓的栈、堆、方法区。

JMM关于同步的规定
1.线程解锁前,必须把共享变量的值刷新回主存(共享内存区域)
2.线程加锁前,必须读取主内存的最新值到自己的工作内存(各个线程私有的数据区域——所以线程间的通信必须通过主存
3.加锁解锁是同一把锁

每个Java线程都有自己的工作内存。操作数据,首先从主内存中读,得到一份拷贝,操作完毕后再写回到主内存

image.png

JMM可能带来可见性、原子性和有序性问题。

所谓可见性,就是某个线程对主内存内容的更改,应该立刻通知到其它线程。原子性是指一个操作是不可分割的,不能执行到一半,就不执行了。所谓有序性,就是指令是有序的,不会被重排。

volatile关键字

volatile关键字是Java提供的一种轻量级同步机制。
三大特性:
1.它能够保证可见性
2.但是不能保证原子性
3.禁止指令重排,有序性

可见性

可能存在一个问题:一个线程AAA修改了共享变量X的值但还没写回主存,另一个线程BBB又对主存同一个共享变量进行了操作,但此时A线程工作内存中共享变量x对线程B来说不可见,这种工作内存于主存同步延迟的现象造成了可见性问题

因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

class MyData{
    volatile int num = 0;
    public void addTo60(){
        this.num = 60;
    }
}

public class VolatileDemo {

    /**
     1.验证可见性
        1.1 加入int num=0;num之前没有添加volatile关键字
            进入while死循环
        1.2加入volatile到num,3秒后main离开死循环
     **/

    public static void main(String[] args) {
        MyData myData = new MyData();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in");
            //延时3秒
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTo60();
            System.out.println(Thread.currentThread().getName() + " updata value:" + myData.num);
        }, "AAA").start();

        while (myData.num == 0){
            //num=0 main一直在这里等待循环
            //没有volatile时候,进入死循环,因为main线程
            //不知道3秒后AAA更新了num

        }
        //加了volatile(可见性),main线程才可以得到num改变的通知
        System.out.println(Thread.currentThread().getName() + " mission over;");
    }
}
volatile不保证原子性

结果不是20000,显然不保证原子性

class MyData{
    volatile int num = 0;
    public void addTo60(){
        this.num = 60;
    }
    public void addPP(){
        num++;
    }
}

public class VolatileDemo {
    /**
     1.验证可见性
        1.1 加入int num=0;num之前没有添加volatile关键字
            进入while死循环
        1.2加入volatile到num,3秒后main离开死循环
     2.不保证原子性:不可分割,完整性,某个线程做某具体业务时候中间不可被分割,
                    要么同时成功要么同时失败
        2.1 结果不是20000
     **/

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

        while (Thread.activeCount() > 2){//除了main和gc线程
            Thread.yield();//异步执行,main让出资源
        }
        System.out.println(Thread.currentThread().getName() + " num :" + myData.num);
    }

Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。

++操作底层是3条指令

getfield //读,拿到原始num
iconst_1 //工作内存的变量+1
iadd //加操作
putfield //写操作(可能写覆盖)

  • 解释:为什么这里的可见性不能保证原子性,因为赋值和自增等操作在底层是多步的,即使见到其他线程对主存的修改时,这个线程的读取原子操作已经结束,进入下一个原子操作了,最终造成重复写
    一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。
    线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。
    问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。

解决方法
1)对该方法加锁
2)使用concurrent包中的AtomicInteger类
原子操作做完了,其他线程才能读

 public void addato() {
       atomicInteger.getAndIncrement();
    }
    AtomicInteger atomicInteger = new AtomicInteger();
volatile禁止指令重排

volatile可以保证有序性,也就是防止指令重排序。所谓指令重排序,就是出于优化考虑,CPU执行指令的顺序跟程序员自己编写的顺序不一致。就好比一份试卷,题号是老师规定的,是程序员规定的,但是考生(CPU)可以先做选择,也可以先做填空。

源代码--编译器优化的重排--质量并行的重排--内存系统的重排--最终执行指令
单线程:代码顺序执行结果和程序最终结果一样
多线程:线程交替执行,由于编译器优化重排(考虑数据依赖性)的存在,两个线程使用的变量能否保证一致无法确定

  • 数据依赖性

int x = 11; //语句1
int y = 12; //语句2
x = x + 5; //语句3
y = x * x; //语句4

以上例子,可能出现的执行顺序有1234、2134、1342,这三个都没有问题,最终结果都是x = 16,y=256。因为数据依赖性所以3和4不可能第一位执行,因为还没有声明

  • 指令重排
class ResortSeqDemo {

    int a=0;
    boolean flag=false;
    /*
    多线程下flag=true可能先执行,还没走到a=1就被挂起。
    其它线程进入method02的判断,修改a的值=5,而不是6。
     */
    public void method01(){
        a=1;
        flag=true;
    }
    public void method02(){
        if (flag){
            a+=5;
            System.out.println("*****retValue: "+a);
            //多线程可以是6 5
        }
    }
}

这里提一下挂起:进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态,系统在超过一定的时间没有任何动作.

volatile底层是用CPU的内存屏障(Memory Barrier)指令来实现的,有两个作用,一个是保证特定操作的顺序性,二是保证变量的可见性——其实就是volatile具有的两个特性。在指令之间插入一条Memory Barrier指令,告诉编译器和CPU,在Memory Barrier指令之间的指令不能被重排序

  • 总结:
    1) 工作内存和主内存同步延迟现象导致的可见性问题——synchronized和volatile解决,一个线程修改变量后立刻对其他线程可见
    2)对指令重排导致的可见性问题和有序性问题,可以用volatile解决——禁止重排序优化
单例模式

回顾单例模式:

public class SingletonDemo {
    private static SingletonDemo instance = null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法");
    }
    public static SingletonDemo getInstance(){
        if(instance == null){
            instance = new SingletonDemo();
        }
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//main   我是构造方法
        //true true true
    }
}

懒汉模式:调用getInstance时候才创建对象
饿汉模式:开始就创建了对象

多线程下的安全问题:

        for (int i = 0; i < 11; i++) {
            new Thread(() ->{
                SingletonDemo.getInstance();
            },String.valueOf(i)).start();
        }
//        0  我是构造方法
//        2  我是构造方法
//        1  我是构造方法
  • 单例模式下的volatile
synchronizated大材小用,而且锁死整个函数;
DCL模式:double check lock双端检索机制
加锁前后都检索
if(instance == null){
            synchronized (SingletonDemo.class){
                if (instance == null){
                    instance = new SingletonDemo();
                }
            }
        }
可能会出现异常,因为new分为三步:
1)分配内存空间
2)初始化对象
3)设置instance指向刚分配的内存地址,此时不等于null了
2和3不存在数据依赖,所以指令重排可以导
致1,3,2这时候对象还没初始化但instance!=null了

一条线程访问不为空,但此时instance未必已经完成初始化,
return会错误,线程安全问题

解决方法:

双端检索+volatile
private static volatile SingletonDemo instance = null;

CAS(compare and swap)

定义:

public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        //expect和主存值要一样,才写回修改
        System.out.println(atomicInteger.compareAndSet(5, 5000) + "\t curr" +
                "ent data: " + atomicInteger.get());
//        true   current data: 5000
        System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t curr" +
                "ent data: " + atomicInteger.get());
//        false  current data: 5000
    }
底层原理,unsafe的理解:
  • 进入AtomicInteger类
三个重要的成员变量:
1)private static final Unsafe unsafe = Unsafe.getUnsafe();

unsafe类保证原子性:通过native访问底层,直接操作特定的
内存数据,类似指针操作内存
2)static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
valueOffset表示该变量在内存中的偏移地址,Unsafe根据它来
获取数据
3)private volatile int value;
volatile保证有序性和可见性


CAS:一条cpu并发原语,判断内存中某个位置是否为预期值,
如果是就更改为更新的值,这个过程是原子的,unsafe类中的CAS方法

this表示当前对象;valueoffset是内存偏移量(内存地址)
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }


进入unsafe类:
 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
//var2地址的值--var5
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//再读一次当前地址值,和var5比较,那么加
//1,成功就跳出循环了,失败就继续比较,直到成功
        return var5;
    }

CAS就是一条cpu原语,不会被打断。由unsafe类底层实现,compareAndSwapInt是要给native方法,用到了底层汇编

  • 简单小总结:unsafe类:
    var1:当前对象
    var2:value的地址
    var4:需要变动的数量
    var5:用var1和var2找到主存中对应的值
    再读一次内存,和开始保存的var5比较:
    相同:修改写回主存
    不同:继续循环,直到两者相同,完成更新
  • 所谓为什么AutomicInteger用CAS而不用同步锁?
    因为同步的保证了一致性但是并发性下降,而CAS没有加锁,只是反复CAS循环比较,既保证了一致性,又保证了并发性
    应用底层.png

unsafe类+CAS思想(自旋)

CAS的缺点:

1)循环时间长(do-while),开销大(CPU开销大,如果多个线程都在自旋)
2)只能保证一个共享变量的原子操作
3)ABA问题:

ABA问题:

CAS--UnSafe--底层--ABA--原子引用更新--如何规避ABA问题

  • 介绍
    比如主存是A、两个速度不一样的线程,快的改了两次B-A,而慢的线程虽然写回时还是A,但其实主存已经变动过了


    示例.png

    文字表述.png
  • AtomicReference原子引用:对自定义类进行原子包装


        User z3 = new User("z3", 22);
        User z4 = new User("z4", 25);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(z3);
        System.out.println(atomicReference.compareAndSet(z3, z4) +"" +
                "\t" + atomicReference.get().toString());
  • AtomicStampedReference时间戳原子引用解决ABA问题
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

        new Thread(() -> {

            System.out.println(Thread.currentThread().getName()+
                    "\t第一次版本号" + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+
                    "\t第2次版本号" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+
                    "\t第3次版本号" + atomicStampedReference.getStamp());
        }, "t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+
                    "\t第一次版本号" + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //要求期望值和内存值相同,并且时间戳和期望时间戳相同才能修改。
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp,stamp+1);
            System.out.println(Thread.currentThread().getName()+
                    "\t第2次版本号" + atomicStampedReference.getStamp() + "\t修改成功吗?"+ result);
            System.out.println(Thread.currentThread().getName()+ "\t最新值为:" + atomicStampedReference.getReference());

        }, "t4").start();
//        t3    第一次版本号1
//        t4    第一次版本号1
//        t3    第2次版本号2
//        t3    第3次版本号3
//        t4    第2次版本号3 修改成功吗?false
//        t4    最新值为:100
    }

  • //要求期望值和内存值相同,并且时间戳和期望时间戳相同才能修改。
    boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp,stamp+1);

四 集合类的不安全问题

  • List(CopyOnWriteArrayList)
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.sql.Time;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collector;

/**
 * @author : liulinzhi
 * @date: 2020/09/03/14:27
 * @description:
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();

        for (int i = 1; i < 5; i++) {
            new Thread(() -> {

                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

 
         1.异常:java.util.ConcurrentModificationException

         2.原因:
            并发争抢修改导致;比如一个人正在写,另一个线程抢夺,导致数据不一致

         3.解决方案:
            1)Vector类add有同步锁,并发性下降
              ArrayList并发性上升,但牺牲了多线程安全性
            2)Collections.synchronizedList工具类的同步包装
            3)CopyOnWriteArrayList<E>
                    源码:
                底层是要给加了个volatile的Object数组:
                    private transient volatile Object[] array;

        不直接在array中添加,而是在一个赋值array添加,最后
         将原引用指向新的array。所以可以对CopyOnWrite进行并发读
         ,而不需要加锁,因为不会添加修改。读和写不同容器
        public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
         //底层array编程newElements了
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }

         读取操作没有加任何锁
        public E get(int index) {
            return get(getArray(), index);
        }

        @SuppressWarnings("unchecked")
        private E get(Object[] a, int index) {
            return (E) a[index];
        }

        final Object[] getArray() {
            return array;
        }
        4.避免同类问题,优化建议:
        读写分离思想,
    }
}
如果是多个线程读,则不会阻塞;

-set

public static void main(String[] args) {
//        List<String> list = new CopyOnWriteArrayList<>();
//        Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
//        Set<String> set1 = new CopyOnWriteArraySet<String>();
        Set<String> set1 = new HashSet<>();
        for (int i = 1; i < 50; i++) {
            new Thread(() -> {
                set1.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set1);
            },String.valueOf(i)).start();
        }

Hashset同样不安全
         解决:
         1.Collections.synchronizedSet(new HashSet<>());
         2.CopyOnWriteArraySet,底层用的CopyOnWriteArrayList

         HashSet底层就是HashMap,它的add方法,是add的key,value是一个
            公用的Object类型成员引用 PRESENT
         public boolean add(E e) {
            return map.put(e, PRESENT)==null;
         }
  • Map
Map<String, String> set1 = new ConcurrentHashMap<>();
        for (int i = 1; i < 50; i++) {
            new Thread(() -> {
                set1.put(UUID.randomUUID().toString().substring(0,8),
                        UUID.randomUUID().toString().substring(0,8));
                System.out.println(set1);
            },String.valueOf(i)).start();
        }

         HashMap:不安全
         解决:
         1.Collections.synchronizedMap
         2.ConcurrentHashMap(JUC)

1.8之前用锁分段.png

1.8之后用CAS+Synchronized.png

五 值传递和引用传递

public void changeValue(int age){
        age = 30;
    }

    public void changeValue1(Person age){
        age.setPersonName("xxx");
    }

    public void changeValue2(String age){
        age = "xxx";
    }

    public static void main(String[] args) {
        TestTransferValue testTransferValue = new TestTransferValue();
        int age =20;
        testTransferValue.changeValue(age);
        System.out.println(age);
rew
栈管内存(方法的堆叠,两个age分别时两个方法栈中的各自变量),堆管存储,
//        值传递,20

        Person person = new Person("abc");
        testTransferValue.changeValue1(person);
        System.out.println(person);
//        引用传递,堆空间修改 xxx

        String str = "abc";
        testTransferValue.changeValue2(str);
        System.out.println(str);
//        String对象,常量池中创建xxx,指向局部变量age

    }

六 锁

  • 公平锁和非公平锁
    ReentrantLock(boolean ):true为公平锁,默认非公平锁
公平锁:队列FIFO,多个锁按照申请锁的顺序来获取锁,先来后到;
非公平锁:多个线程获取锁的顺序并不是先来后到的,高并发的情况下
         可能造成优先级反转或饥饿现象;

饥饿:“不患寡,而患不均”,如果线程优先级“不均”,在CPU繁忙
的情况下,优先级低的线程得到执行的机会很小,就可能发生线程
“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“
饥饿”问题。

两者的区别:一个按FIFO从等待队列中选择线程获取锁
后者,线程上来就抢占,如果线程抢占失败,就再采用公平锁的那种
方式(派到队尾)

优点:
ReentrantLock默认非公平锁,吞吐量比公平锁大;
Synchronized也是非公平锁
  • 可重入锁(递归锁)
可重入锁(也叫递归锁:synchronized ReentrantLock,
可以避免死锁;

同一线程函数获得锁之后,内层递归函数仍然能获取该锁的代码,再
同一个线程在外层获取锁时候,再进入内层方法会自动获取锁

也就是线程可以进入任何一个它已经拥有锁的同步代码块;

比如:两个方法用同一把锁,method1获取锁时候,方法体中访问
method2时候同样获取该锁
syn void method1(){
    method2()
}
syn void method2(){
    
}


/**
 * @author : liulinzhi
 * @date: 2020/09/08/10:59
 * @description:
12   invoked sendSMS  Synchronized Lock
12   invoked sendMail 进入内层方法自动获取锁
13   invoked sendSMS
13   invoked sendMail

14   invoked get  ReentraitLock
14   invoked set
15   invoked get
15   invoked set

 */
class Phone implements Runnable{
    public synchronized void sendSMS() throws Exception{
        System.out.println(Thread.currentThread().getId() +
                "\t invoked sendSMS");
        sendMail();
    }

    public synchronized void sendMail() throws Exception{
        System.out.println(Thread.currentThread().getId() +
                "\t invoked sendMail");
    }

    Lock lock = new ReentrantLock();
    public void get() throws Exception{
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId() +
                    "\t invoked get");
            set();
        }finally {
            lock.unlock();
        }
    }
    public void set() throws Exception{
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId() +
                    "\t invoked set");
        }finally {
            lock.unlock();
        }
    }
    @Override
    public void run() {
        try {
            get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class ReentrantLockDemo {

    public static void main(String[] args) throws InterruptedException {
        //    Lock this
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t1").start();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t2").start();

        TimeUnit.SECONDS.sleep(1);

        Thread t3 = new Thread(phone);
        Thread t4 = new Thread(phone);
        t3.start();
        t4.start();

    }
}

锁匹配(lock,unlock),几把锁都可以正常运行,
语法编译通过,运行正常;

public void get() throws Exception{
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId() +
                    "\t invoked get");
            set();
        }finally {
            lock.unlock();
            lock.unlock();
        }
    }
  • 自旋锁
    unsafe+CAS:尝试获取锁得线程不会立即阻塞,而是尝试循环得方式持续获取锁
    好处:减少线程上下文切换得消耗
    缺点:消耗CPU
atomic
进入unsafe类:
 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
//var2地址的值--var5
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//再读一次当前地址值,和var5比较,那么加
//1,成功就跳出循环了,失败就继续比较,直到成功
        return var5;
    }
验证
//原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t my current come in");
        while(!atomicReference.compareAndSet(null, thread)){

        }

    }
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
    }
    public static void main(String[] args) {
        SpinLockDemo sld = new SpinLockDemo();
        new Thread(()->{
            sld.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sld.myUnlock();
        },"AA").start();

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

        new Thread(()->{
            sld.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sld.myUnlock();
        },"BB").start();

//        AA     my current come in
//        BB     my current come in
//        AA     invoked myUnlock()
//        BB     invoked myUnlock()

    }
  • (共享锁)读(独占锁)写锁理论(互斥锁)

独占锁:该锁一次只能被一个线程所持有,ReentrantLock和Synchronized都是独占锁;

共享锁:可被多个线程持有,ReentrantReadWriteLock读锁是共享锁,写锁是独占(写的时候共享读);

/**
 * @author : liulinzhi
 * @date: 2020/09/09/10:12
 * @description: 多个线程同时读一个资源没问题,为了满足并发量,读取共享
 * 资源应该可以同时进行,但是如果有一个线程想去写共享资源
 * ,就不应该有其他线程可以对该资源读写
 * 总结:
 * 读-读 可以共存
 * 写-写 互斥
 * 读-写 互斥
 * <p>
 * 第一个例子没有加锁:
 * 写操作:原子+独占,这个过程必须是一个完整得统一体,中间
 * 不许被分割,被打断;
 */
class MyCache {
    //保证缓存可见性
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock rwlock = new ReentrantReadWriteLock();

    public void put(String key, Object value) throws InterruptedException {
        rwlock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在写入 " + key);
            //模拟网络临时拥堵
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成 ");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwlock.writeLock().unlock();
        }

    }

    public void get(String key) throws InterruptedException {
        rwlock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在读取 ");
            //模拟网络临时拥堵
            TimeUnit.MILLISECONDS.sleep(300);
            Object val = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成 " + val);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwlock.readLock().unlock();
        }
        }
}
/** 
 * 加了ReentrantReadWriteLock之后保证了读共享和写互斥
 0   正在写入 0
 0   写入完成 
 4   正在写入 4
 4   写入完成 
 1   正在写入 1
 1   写入完成 
 3   正在写入 3
 3   写入完成 
 2   正在写入 2
 2   写入完成 
 0   正在读取 
 1   正在读取 
 2   正在读取 
 3   正在读取 
 4   正在读取 
 3   读取完成 3
 1   读取完成 1
 0   读取完成 0
 2   读取完成 2
 4   读取完成 4

 **/

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache mycache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                try {
                    mycache.put(tempInt + "", tempInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                try {
                    mycache.get(tempInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

    }
}

这个例子更能体现处读写锁ReentrantReadWriteLock的性质:
t1+t2---可以在对方解锁前进入read--读-读共享
t2+t3---必须在对方unlock后才可以进入--读--写互斥
t3+t4---必须在对方unlock后才可以进入write--写--写互斥

public class RWRLTest {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

    public void read(){
        try {
            readLock.lock();
            System.out.println("线程"+Thread.currentThread().getName()+"进入。。。");
            Thread.sleep(3000);
            System.out.println("线程"+Thread.currentThread().getName()+"退出。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            readLock.unlock();
        }
    }

    public void write(){
        try {
            writeLock.lock();
            System.out.println("线程"+Thread.currentThread().getName()+"进入。。。");
            Thread.sleep(3000);
            System.out.println("线程"+Thread.currentThread().getName()+"退出。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            writeLock.unlock();
        }
    }


    public static void main(String[] args) {
        final RWRLTest wr = new RWRLTest();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                wr.read();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                wr.read();
            }
        }, "t2");
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                wr.write();
            }
        }, "t3");
        Thread t4 = new Thread(new Runnable() {
            public void run() {
                wr.write();
            }
        }, "t4");

//        t1.start();
        t2.start();
        t3.start();
        //t4.start();
    }
  • CountDownLatch、CyclicBarrier、Semaphore的使用:
CountDownLatch:计数为0后,方可继续执行;

public static void main(String[] args) throws InterruptedException {
        CountDownLatch cdl = new CountDownLatch(6);
        for (int i = 1; i < 7; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 离开教室");
                cdl.countDown();
            },String.valueOf(i)).start();
        }

        cdl.await();//reach zero 才能继续执行
        //如何让main线程最后
        System.out.println(Thread.currentThread().getName() + "\t 班长关门离开教室");
    }
结合枚举
public enum CountryEnum {
    ONE(1, "齐"),TWO(2, "楚"),THREE(3, "燕"),FOUR(4, "赵"),FIVE(5, "魏"),SIX(6, "韩");


    private Integer retCode;
    private String retMsg;

    CountryEnum(Integer retCode, String retMsg) {
        this.retCode = retCode;
        this.retMsg = retMsg;
    }

    public Integer getRetCode() {
        return retCode;
    }

    public void setRetCode(Integer retCode) {
        this.retCode = retCode;
    }

    public String getRetMsg() {
        return retMsg;
    }

    public void setRetMsg(String retMsg) {
        this.retMsg = retMsg;
    }

    public static CountryEnum forEach_CountryEnum(int index){
        CountryEnum[] values = CountryEnum.values();
        for(CountryEnum element : values){
            if(index == element.getRetCode()){
                return element;
            }
        }
        return null;
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch cdl = new CountDownLatch(6);
        for (int i = 1; i < 7; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 国灭");
                cdl.countDown();
            },CountryEnum.forEach_CountryEnum(i).getRetMsg()).start();
        }

        cdl.await();//reach zero 才能继续执行
        //如何让main线程最后
        System.out.println(Thread.currentThread().getName() + "\t 统一");

    }

CyclicBarrier:“人到齐才可以开会”,所有线程到达屏障才可以继续。
比如7个线程都到才解除阻塞

public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("****");
        });
        for (int i = 1; i < 8; i++) {
            final int tempInt = i;
            new Thread(() ->{
                System.out.println(Thread.currentThread().getName()
                + "\t 收集到第 " + tempInt);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

Semaphore:“争车位”,用途:
1)多个共享资源互斥使用
2)并发线程数量的控制

public static void main(String[] args) {
        //3个停车位
//        1  抢到车位
//        4  抢到车位
//        2  抢到车位
//        2  3s后离开车位
//        1  3s后离开车位
//        4  3s后离开车位
//        3  抢到车位
//        5  抢到车位
//        3  3s后离开车位
//        5  3s后离开车位
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i < 6; i++) {
            final int tempInt = i;
            new Thread(() ->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() +
                            "\t 抢到车位");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName() +
                            "\t 3s后离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                    //释放
                }
            },String.valueOf(i)).start();
        }

    }

阻塞队列
image.png

image.png

实现类.png

线程池的底层就是用三个红色的;

方法类型 抛出异常 返回布尔 阻塞 超时
插入 add(E e) offer(E e) put(E e) offer(E e,Time,TimeUnit)
取出 remove() poll() take() poll(Time,TimeUnit)
队首 element() peek()
image.png
public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> bq = new ArrayBlockingQueue<>(3);

        //1.抛出异常,add remove element
        //如果队满插入和队空删除会直接报错,一言不合就抛出异常
        System.out.println(bq.add("a"));
        System.out.println(bq.add("b"));
        System.out.println(bq.add("c"));
        System.out.println(bq.element());
        //IllegalStateException: Queue full
//        System.out.println(bq.add("d"));

        System.out.println(bq.remove());
        System.out.println(bq.remove("c"));

        System.out.println(bq.element());
//        true
//        true
//        true
//        a
//        a
//        true
//        b


        //2.返回布尔值组:offer poll peek
        //不抛异常,返回false(插入失败)  null(取失败)
        BlockingQueue<String> bq1 = new ArrayBlockingQueue<>(3);
        System.out.println("--------------");
        System.out.println(bq1.offer("a"));
        System.out.println(bq1.offer("b"));
        System.out.println(bq1.offer("c"));
        System.out.println(bq1.offer("d"));
        System.out.println(bq1.peek());
//        true
//        true
//        true
//        false
//        a
        System.out.println(bq1.poll());
        System.out.println(bq1.poll());
        System.out.println(bq1.poll());
        System.out.println(bq1.poll());
//        a
//        b
//        c
//        null

        //3.阻塞和超时控制,
        BlockingQueue<String> bq2 = new ArrayBlockingQueue<>(3);
        System.out.println("--------------");

        bq2.put("a");
        bq2.put("a");
        bq2.put("a");
        System.out.println("---");
        //线程阻塞,不结束
//        bq2.put("a");

        bq2.take();
        bq2.take();
        bq2.take();
        System.out.println("---");
//        bq2.take();

        //超时控制
        BlockingQueue<String> bq3 = new ArrayBlockingQueue<>(3);
        System.out.println("--------------");
        System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
        //阻塞2秒
        System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355