JUC并发编程(狂神说笔记)

本文围绕JUC并发编程展开,回顾了线程和进程等基础知识,对比了synchronized锁和Lock锁。探讨了生产者和消费者问题的不同实现,分析集合类不安全的原因及解决方案。还介绍了Callable、常用辅助类、线程池等内容,涉及JMM、Volatile、CAS等并发编程关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JUC并发编程

什么是JUC

java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks这三个包

前期回顾

什么是线程和进程?

进程:运行中的程序

线程:一个进程包含多个线程,至少包含一个。java默认有两个线程(main线程、GC线程)

JAVA并不能真的开启线程(JAVA无法直接操作硬件,是底层C++去做的)

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
	//本地方法、底层的C++,JAVA无法直接操作硬件
    private native void start0();

并发和并行

并发:一核CPU,多个任务交替使用CPU(多线程操作同一资源)

并行:多核CPU,多个线程可以同时执行

    public static void main(String[] args) {
        //CPU密集型 IO密集型
        //获取CPU的核数
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

并发编程的本质:充分利用CPU的资源

线程的几个六个状态

    public enum State {
        //新生
        NEW,

        //运行中
        RUNNABLE,

        //阻塞
        BLOCKED,

        //等待 死死的等
        WAITING,

        //超时等待
        TIMED_WAITING,

        //终止
        TERMINATED;
    }

wait和sleep的区别

  1. 来自不同的类

    wait->Object

    sleep->Thread

  2. 关于锁的基础

    ​ wait会释放锁,sleep不会

  3. 使用范围不同

    sleep可以在任何地方使用,wait必须在同步代码块中使用

synchronized锁和Lock锁

传统的synchronized锁

public class TestSynchronize {
    public static void main(String[] args) {
        //并发编程 多个线程操作统一资源 把资源类丢入线程
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.saleTicket();
                }
            }, "A");
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.saleTicket();
            }
        }, "B");
        Thread thread3 = new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.saleTicket();
            }
        }, "C");

        thread1.start();
        thread2.start();
        thread3.start(); //注意:Thread不要用匿名方式创建,否则会出现第一个线程一直独占CPU。这是匿名对象的原因,匿名对象不是强引用,对象声明生命周期短,当第一条线程执行run之后,二三四条线程应该是被虚拟机回收了。一般多线程都不用匿名对象
    }
}


//并发的实质就是多线程操作统一资源
//资源类 属性+方法
//若该资源直接实现runnable接口 耦合性高 不便于解耦
class Ticket{
    private int ticketNums = 20; //票数

    //synchronized实质:队列+锁
    //售票
    public synchronized void saleTicket(){
        if(ticketNums > 0) {
            System.out.println(Thread.currentThread().getName() + "售出了第" + ticketNums-- + "张票,剩余票数:" + ticketNums);
        }
    }
}

Lock锁(接口)

三个实现类:ReentrantLock(可重入锁,较为常用)、 ReentrantReadWriteLock.ReadLock(读锁)、 ReentrantReadWriteLock.WriteLock(写锁)

Lock锁的使用规则:

在这里插入图片描述

  1. new ReentrantLock();

  2. lock.lock();//加锁

  3. try语句块中写业务代码

  4. finally=>lock.unlock();//解锁

    示例:

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestLock {
        public static void main(String[] args) {
            //并发编程 多个线程操作统一资源 把资源类丢入线程
            Ticket01 ticket = new Ticket01();
            Thread thread1 = new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); }, "A");
            Thread thread2 = new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); }, "B");
            Thread thread3 = new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); }, "C");
    
            thread1.start();
            thread2.start();
            thread3.start(); //注意:Thread不要用匿名方式创建,否则会出现第一个线程一直独占CPU。这是匿名对象的原因,匿名对象不是强引用,对象声明生命周期短,当第一条线程执行run之后,二三四条线程应该是被虚拟机回收了。一般多线程都不用匿名对象
        }
    }
    
    
    //资源类 属性+方法
    class Ticket01{
        private int ticketNums = 40; //票数
    
        Lock lock = new ReentrantLock();
    
        //售票
        public void saleTicket(){
            lock.lock();
            try {
                if(ticketNums > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出了第" + ticketNums-- + "张票,剩余票数:" + ticketNums);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    ReentrantLock:

    在这里插入图片描述

    公平锁:十分公平,先来后到

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

synchronized锁和Lock锁的区别

synchronizedLock
java内置关键字Lock是一个java类
无法判断是否获取到了锁可以判断是否获取到了锁
自动释放锁手动释放锁
可重入锁,不可以终端,非公平锁公不公平可以自己设置
适合锁少量的代码同步问题适合锁大量的同步代码
线程1获得锁后阻塞,那么线程2会一直傻傻的等着不一定会一直等待下去

生产者和消费者问题

Synchronized版

/**
 * 线程之间的通信问题:生产者和消费者问题   等待唤醒 通知唤醒
 * 线程交替执行 A、B操作同一个变量 num = 0
 * A:num+1
 * B:num-1
 */
public class TestPCSynchronize {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) { data.increment(); }
            }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) { data.decrement(); }
        }, "B").start();
    }
}

//判断等待 业务 通知
class Data{
    private int num = 0;

    //+1
    public synchronized void increment(){
        if(num != 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num = num + 1;
        System.out.println(Thread.currentThread().getName() + "实现了+1操作,当前num: " + num);
        this.notifyAll();
    }

    //-1
    public synchronized void decrement(){
        if (num != 1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num = num - 1;
        System.out.println(Thread.currentThread().getName() + "实现了-1操作,当前num: " + num);
        this.notifyAll();
    }
}

输出结果:
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0

问题存在:只有A、B两个线程是线程安全的,但是如果是四个线程则线程不安全

四个线程的输出结果:
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
A实现了+1操作,当前num: 2
C实现了+1操作,当前num: 3
D实现了-1操作,当前num: 2
B实现了-1操作,当前num: 1
B实现了-1操作,当前num: 0
D实现了-1操作,当前num: -1
C实现了+1操作,当前num: 0
C实现了+1操作,当前num: 1
A实现了+1操作,当前num: 2
C实现了+1操作,当前num: 3
D实现了-1操作,当前num: 2
B实现了-1操作,当前num: 1
D实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0

产生问题的原因:虚假唤醒

​ Object.wait()官方解释:

在这里插入图片描述

虚假唤醒:虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的,发生在两个以上的多线程生产者和消费者问题。而避免虚假唤醒的解决办法是让wait方法处于循环中。

将if改为while后,四个线程的输出结果:
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0

注意:wait()方法最好不要放在if中,最好在while循环中

Lock版(JUC版)(重点)

在这里插入图片描述

java.util.concurrent.locks包下的三个接口:Condition、Lock、ReadWriteLock

在这里插入图片描述

Lock取代了synchronized方法和语句的使用,Condition取代了对象监视器方法的使用

Lock锁的使用:
try{
	lock.lock();
	//业务代码
}catch(Exception e){
	e.printStackTrace();
}finally{
	lock.unlock();
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//+1 -1交替执行
public class TestPCLock {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) { data.increment(); }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) { data.decrement(); }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) { data.increment(); }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) { data.decrement(); }
        }, "D").start();
    }
}

//判断等待 业务 通知
class Data2{
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //+1
    public void increment(){
        try {
            lock.lock();
            //业务代码
            while (num != 0){//使用while 防止虚假唤醒
                condition.await();
            }
            num = num + 1;
            System.out.println(Thread.currentThread().getName() + "实现了+1操作,当前num: " + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //-1
    public void decrement(){
        try {
            lock.lock();
            //业务代码
            while (num != 1){
                condition.await();
            }
            num = num - 1;
            System.out.println(Thread.currentThread().getName() + "实现了-1操作,当前num: " + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

输出结果:
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
A实现了+1操作,当前num: 1
B实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0
C实现了+1操作,当前num: 1
D实现了-1操作,当前num: 0

问题:现在只实现了+1、-1交替执行,线程是随机的一个状态,但是我想要A、B、C、D交替执行(如:A B C D A B C D),使得线程精准执行,不然的话与普通的生产者消费者并没有什么区别,那么为什么还要去用JUC新技术呢。

解决方案:使用Condition精准通知唤醒

使用Condition精准通知唤醒

//A、B、C交替执行 适用场景:生产线工作
public class TestCondition {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        }, "C").start();
    }
}

class Data3{

    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    int num = 1;//1A 2B 3C

    public void printA(){
        try {
            lock.lock();
            //判断等待->执行->通知
            while(num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "-> AAA");
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        try {
            lock.lock();
            //判断等待->执行->通知
            while(num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "-> BBB");
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        try {
            lock.lock();
            //判断等待->执行->通知
            while(num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "-> CCC");
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

输出结果:
A-> AAA
B-> BBB
C-> CCC
A-> AAA
B-> BBB
C-> CCC
A-> AAA
B-> BBB
C-> CCC
A-> AAA
B-> BBB
C-> CCC

八锁现象

锁到底是什么?锁的到底是谁?

锁的只会是两个东西(对象、Class模板)

八锁现象实质是8道题

  1. 问题1:两个线程谁先执行? 发短信还是打电话
public class TestEightLock01 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()-> {phone.sendText();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {phone.call();}).start();
    }
}

class Phone{

    public synchronized void sendText(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

}

输出:
发短信
打电话
  1. 问题2:两个线程谁先执行? 发短信还是打电话
//新增:发短信方法延迟4秒
public class TestEightLock01 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()-> {phone.sendText();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {phone.call();}).start();
    }
}

class Phone{
	
	//synchronized锁的是方法的调用者(phone)
	//phone只有一个 所以谁先获得锁 谁就先执行(TimeUnit.SECONDS.sleep不会释放锁,Object.wait才会释放锁)
    public synchronized void sendText(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

}

输出:
发短信
打电话
  1. 问题3:两个线程谁先执行?发短信还是hello
//新增:普通的hello方法
public class TestEightLock02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()-> {phone.sendText();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {phone.hello();}).start();
    }
}

class Phone2{

    public synchronized void sendText(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

    //这里没有锁,不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("call");
    }
}

输出:
call
发短信
  1. 问题4:哪个线程先执行?打电话还是发短信
//新增:两个对象
public class TestEightLock02 {

    public static void main(String[] args) {
        //两个对象 两个调用者 两把锁
        Phone2 phone = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()-> {phone.sendText();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {phone2.call();}).start();
    }
}

class Phone2{
    public synchronized void sendText(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

    public void hello(){
        System.out.println("call");
    }
}

输出:
打电话
发短信
  1. 两个线程谁先执行?发短信还是打电话
//新增:两个静态方法
public class TestEightLock03 {

    public static void main(String[] args) {
        Phone3 phone = new Phone3();
        Phone3 phone1 = new Phone3();
        new Thread(()-> {phone.sendText();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {phone1.call();}).start();
    }
}

class Phone3{
    //static静态方法,类一加载就有了,从属于类(Class) 
    //此时synchronized锁的是Class(模板,Class对象是唯一的)
    public static synchronized void sendText(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话
  1. 两个线程谁先执行?打电话还是发短信
//新增:一个静态同步方法,一个普通同步方法
public class TestEightLock04 {

    public static void main(String[] args) {
        Phone4 phone = new Phone4();
        new Thread(()-> {phone.sendText();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {phone.call();}).start();
    }
}

class Phone4{
    //静态同步方法
    public static synchronized void sendText(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    //普通同步方法
    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:(一个锁的是Class模板,一个锁的是对象,两个锁的不一样,所以不需要等待)
打电话
发短信

集合类不安全及解决方案

List不安全

在这里插入图片描述

解决方案一:使用Vector(Vector是线程安全的)

底层分析:   
	//ArrayList
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    //Vector
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

解决方案二:Collections工具类方法

在这里插入图片描述

List<String> list = Collections.synchronizedList(new ArrayList<>());

解决方案三:JUC(CopyOnWriteArrayList)

在这里插入图片描述

import java.util.concurrent.CopyOnWriteArrayList;
//CopyOnWrite:写入时复制 COW思想 计算机程序设计领域的一种优化策略
//在写入的时候避免覆盖,造成数据问题
List<String> list = new CopyOnWriteArrayList<>();

源码分析:
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
    final void setArray(Object[] a) {
        array = a;
    }
        private transient volatile Object[] array;

CopyOnWrite容器即写时复制的容器。当我们新添加一个元素到容器时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWrite比Vector的好处:

​ synchronized方法效率比较低,而Vector中的方法几乎都用synchronized关键字修饰,而CopyOnWrite使用的是Lock锁(代码如下)

    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;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

Set不安全

在这里插入图片描述
在这里插入图片描述

解决方案一:Collections工具类方法

Set<String> set = Collections.synchronizedSet(new HashSet<>());

解决方案二:JUC(CopyOnWriteArraySet)

Set<String> set = new CopyOnWriteArraySet<>();

**扩充:**HashSet的底层是什么?

//源码分析
   public HashSet() {
        map = new HashMap<>();//HashSet的本质是HashMap
    }

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;//将值作为map的Key 因为Set不能重复
    }

    private static final Object PRESENT = new Object();

Map不安全

在这里插入图片描述

解决方案一:Collections工具类方法

Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());

解决方案二:JUC(ConcurrentHashMap)

Map<String, Object> map = new ConcurrentHashMap<>();

Callable

Callable和Runnable类似,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。Callable可以有返回值并且可以抛出异常(而Runnable既没有返回值,也不返回异常)。Callable需要实现call方法,Runnable需要实现run方法。

在这里插入图片描述

代码测试

public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable).start
        //new Thread(new TutureTask(Callable).start

        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);//适配类  
        new Thread(futureTask).start();
        new Thread(futureTask).start();//只会打印一个call(注意:结果会被缓存)

        Integer o = (Integer)futureTask.get();//获取返回值  这个get方法可能会产生阻塞 把它放到最后或者采用异步通信
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

常用辅助类

CountDownLatch

countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。相当于一个减法计数器。

在这里插入图片描述

原理:

countDownLatch.countDown();//数量-1
countDownLatch.await(); //等待计数器归零 然后再向下执行
每次有线程调用countDownLatch.countDown()就数量-1,直至计数器变为0,变为0后,countDownLatch.await()方法就会被唤醒,继续执行

CyclicBarrier

加法计数器

在这里插入图片描述

Semaphore

一个计数信号量,既可以+1也可以-1

在这里插入图片描述

原理:

semaphore.acquire();//得到,假设已经满了,则等待,直到释放为止
semaphore.release();//释放,会将当前的信号量+1,然后唤醒等待的线程

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数

ReadWriteLock(读写锁)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpInu1od-1622625130287)(C:\Users\Anita\Pictures\138.png)]

ReadWriteLock维护一对关联的locks,一个用于只读操作,一个用于写入。读可以被多线程同时读,写的时候只能有一个线程去写。

不加锁的读写测试

public class TestReadWriteLock {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 0; i < 20; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(String.valueOf(temp), String.valueOf(temp));
            }, String.valueOf(temp)).start();
        }

        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(String.valueOf(temp));
            }, String.valueOf(temp)).start();
        }
    }
}

//模拟缓存
class MyCache{
    volatile Map<String, String> map = new HashMap<>();//缓存
    //写入
    public void put(String key, String value){
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入成功");
    }
    //读取
    public void get(String key){
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        String value = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取成功");
    }
}

输出:(这样的读写操作是有问题的,写入不应该被共享,读操作才能共享)
0写入0
1写入1
1写入成功
0写入成功
2写入2
3写入3
2写入成功
3写入成功
4写入4
4写入成功
5写入5
5写入成功
6写入6
6写入成功
7写入7
7写入成功
9写入9
9写入成功
8写入8
8写入成功
10写入10
10写入成功
11写入11
11写入成功
12写入12
13写入13
17写入17
17写入成功
18写入18
0读取0
19写入19
19写入成功
0读取成功
18写入成功
14写入14
14写入成功
15写入15
16写入16
13写入成功
12写入成功
16写入成功
15写入成功
2读取2
2读取成功
3读取3
3读取成功
5读取5
5读取成功
4读取4
4读取成功
1读取1
1读取成功
7读取7
6读取6
8读取8
7读取成功
9读取9
9读取成功
8读取成功
6读取成功

加锁的读写操作

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 独占锁(写锁):一次只能被一个线程占有
 * 共享锁(读锁):多个线程可以同时占有
 * 读-读:可以共存
 * 读-写:不能共存
 * 写-写:不能共存
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(String.valueOf(temp), String.valueOf(temp));
            }, String.valueOf(temp)).start();
        }

        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(String.valueOf(temp));
            }, String.valueOf(temp)).start();
        }
    }
}

//模拟缓存
class MyCacheLock{
    volatile Map<String, String> map = new HashMap<>();//缓存
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //写入的时候只能有一个线程写入
    public void put(String key, String value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    //读取 可以有多个线程同时读
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            String value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

BlockingQueue(阻塞队列)

队列写入:如果队列满了,就必须阻塞等待

队列取出:如果队列是空的,必须阻塞等待生产

在这里插入图片描述

适用场景:多线程并发、线程池

在这里插入图片描述

四组API

方式抛出异常有返回值,不抛出异常阻塞 等待超时 等待
添加add(e)offer(e)put(e)offer(e, , )
移除remove()poll()take()poll( , )
判断队列首element()peek()
    //抛出异常
    public static void test(){
        //队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.add(1));//输出true
        System.out.println(arrayBlockingQueue.add(2));
        System.out.println(arrayBlockingQueue.add(3));

//        System.out.println(arrayBlockingQueue.add(4));//抛出异常 IllegalStateException: Queue full

        System.out.println(arrayBlockingQueue.element());//输出队首元素 a

        System.out.println(arrayBlockingQueue.remove());//输出1
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());

//        System.out.println(arrayBlockingQueue.remove());//抛出异常 NoSuchElementException
    }

    //有返回值,不抛出异常
    public static void test2(){
        //队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.offer(1));//输出true
        System.out.println(arrayBlockingQueue.offer(2));
        System.out.println(arrayBlockingQueue.offer(3));
        System.out.println(arrayBlockingQueue.offer(4));//返回flase 不抛出异常

        System.out.println(arrayBlockingQueue.peek());输出队首元素 a

        System.out.println(arrayBlockingQueue.poll());//输出1
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());//输出null 不抛出异常
    }

    //等待,阻塞(一直等待)
    public static void test3() throws InterruptedException {
        //队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        arrayBlockingQueue.put(1);//没有返回值;
        arrayBlockingQueue.put(2);
        arrayBlockingQueue.put(3);
        arrayBlockingQueue.put(4);//不会抛出异常 一直等待

        arrayBlockingQueue.take();//输出a
        arrayBlockingQueue.take();
        arrayBlockingQueue.take();
        arrayBlockingQueue.take();//不会抛出异常 一直等待


    }

    //等待,阻塞(超时等待 超过时间则不等待)
    public static void test4() throws InterruptedException {
        //队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.offer(1));//输出true
        System.out.println(arrayBlockingQueue.offer(2));
        System.out.println(arrayBlockingQueue.offer(3));
        System.out.println(arrayBlockingQueue.offer(4,2, TimeUnit.SECONDS));//等待超过2秒就退出


        System.out.println(arrayBlockingQueue.poll());//输出1
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS));等待超过2秒就退出
    }

SynchronousQueue 同步队列

没有容量

进去一个元素,必须等待取出之后,才能再往里面放一个元素(相当于最多只能有一个元素)

put、take操作

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * 同步队列
 * 和其他的BlockingQueue不一样,SynchronousQueue不存储元素
 * put了一个元素,就必须从里面take出来,否则不能继续Put值
 */
public class TestSynBQ {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "取出 " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "取出 " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "取出 " + blockingQueue.take());
            } catch (Exception e) {
                e.printStackTrace();
            }

        }).start();
    }
}

线程池(重点)

线程池:三大方法、7大参数、4种拒绝策略

池化技术

程序运行的本质:占用系统的资源。=》优化资源的使用=》池化技术

线程池、连接池、内存池、对象池:创建、销毁,十分浪费资源

池化技术:事先准备一些资源,有人要用,就来拿,用完之后再还回来。

线程池的好处

1:降低资源的消耗

2:提高响应的速度

3:方便管理

线程复用,可以控制最大并发数,管理线程

三大方法

在这里插入图片描述

测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

七大参数

//源码分析
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
本质:ThreadPoolExecutor
        public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                              int maximumPoolSize,//最大核心线程池大小
                              long keepAliveTime,//超时了没有人调用就会释放(没有使用下图的窗口5和窗口6,就会释放这两个窗口)
                              TimeUnit unit,//超时单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工厂 创建线程的 一般不用动
                              RejectedExecutionHandler handler//拒绝策略) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

在这里插入图片描述

自定义线程池

//Executors:工具类
public class TestExecutor {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定容量的线程池
//        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的
        ExecutorService threadPool = new ThreadPoolExecutor(2,
                3,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());//拒绝策略,银行满了,还有人进来,不处理这个人的,抛出异常
        try {
            //最大承载:maximumPoolSize+workQueue,超过了就会被拒绝策略接收
            for (int i = 1; i <= 5; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();//关闭线程池
        }
    }
}

测试结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四种拒绝策略

/**四种拒绝策略:
 * ThreadPoolExecutor.AbortPolicy();//银行满了,还有人进来,不处理这个人的,抛出异常
 * ThreadPoolExecutor.CallerRunsPolicy();//哪来的往哪去,让回来的人执行这条线程
 * ThreadPoolExecutor.DiscardPolicy();//队列满了,丢掉任务,不会抛出异常
  * ThreadPoolExecutor.DiscardOldestPolicy();//队列满了,尝试和最早的线程竞争,也不会抛出异常
 */

四种策略的测试结果

  • AbortPolicy(抛出RejectedExecutionException异常)

在这里插入图片描述

  • CallerRunsPolicy(main线程执行,所以回到main线程,让main线程代为执行)

在这里插入图片描述

  • DiscardPolicy(丢掉任务)

在这里插入图片描述

  • DiscardOldestPolicy

在这里插入图片描述

思考:如何设置最大线程池的大小呢?

1:CPU密集型(电脑是几核,大小就设置为几核,CPU效率最高)

Runtime.getRuntime().availableProcessors();//获取处理器的核数

2:IO密集型(判断程序中十分耗费IO的线程,大于其数量即可)

四大函数式接口

新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
//这样的函数式接口在java中非常多
//简化编程类型,在新版本的框架底层大量应用
//foreach(消费者类的函数式接口)

在这里插入图片描述

Function(函数型接口)

在这里插入图片描述

/**
 * 函数型接口:有一个输入参数,有一个输出
 */
public class TestInterface {
    public static void main(String[] args) {
        //工具类:输出输入的值
//        Function<String, String> function = new Function<String, String>() {
//            @Override
//            public String apply(String o) {
//                return o;
//            }
//        };
        //使用lambda表达式简化
        Function<String, String> function = (str) -> {return str;};
        System.out.println(function.apply("hello"));
    }
}

Predicate(断定型接口)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNfAp5dv-1622625130334)(C:\Users\Anita\Pictures\157.png)]

//断定型接口:有一个输入参数,返回值只能是布尔值
public class TestPredicate {
    public static void main(String[] args) {
        //判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };
        Predicate<String> predicate = (str) -> {return  str.isEmpty();};
        System.out.println(predicate.test("111"));
    }
}

Consumer(消费型接口)

在这里插入图片描述

//消费型接口:只有输入,没有输出
public class TestConsumer {
    public static void main(String[] args) {
        //打印字符串
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };
        //lambda表达式简化
        Consumer<String> consumer = (str) -> { System.out.println(str); };
        consumer.accept("hello");
    }
}

供给型接口

在这里插入图片描述

public class TestSupplier {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier() {
//            @Override
//            public String get() {
//                return "2014";
//            }
//        };
        Supplier<String> supplier = () -> {return "2014";};
        System.out.println(supplier.get());
    }
}

Stream流式计算

大数据:存储+计算

集合、MySQL的实质就是存储

计算应该交给流来计算

在这里插入图片描述

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现
 * 现在有5个用户,筛选:
 * 1:ID必须是偶数
 * 2:年龄必须大于23岁
 * 3:用户名转为大写字母
 * 4:用户名字母倒着排序
 * 5:只能输出一个用户
 */
public class TestStream {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 22);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(6, "e", 25);
        //集合就是存储
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //计算交给Stream流
        //链式编程
        list.stream()
                .filter(u -> {return u.getId() % 2 == 0;})
                .filter(u -> {return u.getAge() > 23;})
                .map(u -> {return u.getName().toUpperCase();})
                .sorted((u1, u2) -> {return u2.compareTo(u1);})
                .limit(1)
                .forEach((u) -> System.out.println(u));
    }
}

输出:E

ForkJoin(分支合并)

什么是ForkJoin

大数据:Map Reduce(将大任务拆成小任务)

ForkJoin在jdk1.7之后才有的,用来并行执行任务,用来提高效率的!(大数据量的时候才会使用)

在这里插入图片描述

ForkJoin特点:工作窃取

这个里面维护的是双端队列

在这里插入图片描述
在这里插入图片描述

测试

/**
 * 计算求和的任务
 * 3000 6000(ForkJoin) 9000(Stream并行流)
 * 如何使用ForkJoin
 * 1:forkjoinPool:通过它来执行
 * 2:计算任务forkjoinPool.execute(ForkJoinTask task)
 * 3:ForkJoinTask是一个抽象类 需要被继承
 */
public class TestForkJoin {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    //普通for循环
    static void test1(){
        long startTime = System.currentTimeMillis();

        long sum = 0;
        for (long i = 1; i <= 1000000000; i++) {
            sum = sum + i;
        }
        long endTime = System.currentTimeMillis();

        System.out.println("sum:" + sum + "   时间:" + (endTime - startTime ));
    }

    //forkJoin方式
    static void test2() throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        MyForkJoinTask task = new MyForkJoinTask(1,1000000000);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务 有结果
        long sum = submit.get();

        long endTime = System.currentTimeMillis();
        System.out.println("sum:" + sum + "   时间:" + (endTime - startTime ));

    }

    //Stream并行流
    static void test3(){
        long startTime = System.currentTimeMillis();

        //rangeClosed (]    range()
        long sum = LongStream.rangeClosed(0, 1000000000).parallel().reduce(0,Long::sum);

        long endTime = System.currentTimeMillis();
        System.out.println("sum:" + sum + "   时间:" + (endTime - startTime));
    }
}

public class MyForkJoinTask extends RecursiveTask<Long> {

    long start ;
    long end;
    long temp = 10000;
    public MyForkJoinTask(long start, long end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        if((end - start) < temp ){
            long sum = 0;
            for(long i = start; i <= end; i++){
                sum = sum + i;
            }
            return sum;
        }else{
            long middle = (start + end) / 2;
            MyForkJoinTask task1 = new MyForkJoinTask(start, middle);
            task1.fork();//拆分任务 把任务压入线程队列
            MyForkJoinTask task2 = new MyForkJoinTask(middle + 1, end);
            task2.fork();//拆分任务 把任务压入线程队列
            return task1.join() + task2.join();
        }

    }
}

异步回调

Future涉及的初衷:对将来的某个事件进行建模

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/**
 * 异步回调:CompletableFuture
 * 异步执行
 * 成功回调
 * 失败回调
 */
public class TestCompleteFtureTask {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        //没有返回值的 runAsync 异步回调
//        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
//            try {
//                TimeUnit.SECONDS.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + "sunAsync");
//        });
//        System.out.println("1111111");
//        System.out.println("result" + future.get());//获取阻塞执行结果 等待异步线程执行完获取结果

		// 输出结果
        // 1111
        // result

        //有返回值的 supplyAsync 异步回调
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supplyAsync");
            return 1024;
        });
        future.whenComplete((t, u) -> {//正常执行
            System.out.println("t:" + t + "  u:" + u); //t:正常返回的结果 U:错误信息
        }).exceptionally((e) -> {//发生错误
            System.out.println(e.getMessage());
            return 233;
        });

    }
}

JMM

谈谈对volatile的理解

volitale是java虚拟机提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM

JMM:java内存模型,不存在的东西。概念!约定!

关于JMM一些同步的约定:

  1. 线程解锁前,必须把共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存的最新值到工作内存中
  3. 加锁和解锁用的是同一把锁

在这里插入图片描述

内存交互的八种操作如图红框(四组,操作不允许单一使用,必须成对使用)

测试

在这里插入图片描述

测试代码分析

在这里插入图片描述

问题:程序不知道主存中的值以及改变了

Volatile

1:保证可见性

在这里插入图片描述

2:不保证原子性

在这里插入图片描述

问题:如果不使用lock和synchronized,如何保证原子性呢?

答:使用原子类解决原子性问题(这些类的底层和操作系统有关,UnSafe类是一个特殊的存在)

在这里插入图片描述
在这里插入图片描述

3:禁止指令重排

什么是指令重排?

我们所写的程序,计算机并不是按照我们我们所写的那样去执行的

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

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

在这里插入图片描述

volatile在单例模式使用的最多

单例模式

/**
 * 饿汉式
 * 缺点:可能会浪费空间
 */
public class Hungry {
    //构造器私有 就不能随意创建该对象的实例了
    private Hungry(){
    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}
/**
 * 懒汉式
 * 缺点:多线程下会出现并发问题(使用双重检测锁+volatile解决) 但是通过反射创建对象依然会有问题
 */
public class Lazy {
    //构造器私有 就不能随意创建该对象的实例了
    private Lazy(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    private volatile static Lazy lazy;

//    public Lazy getInstance(){//单线程很完美 但多线程会出现问题
//        if(lazy == null){
//            lazy = new Lazy();//不是一个原子操作
//        }
//        return lazy;
//    }

    //DCL懒汉式 双重检测锁模式
    public Lazy getInstance(){
        if(lazy == null){
            synchronized (Lazy.class){
                if(lazy == null){
                    lazy = new Lazy();//不是一个原子操作
                    /**new Lazy()需要执行的操作
                     * 1:分配内存空间
                     * 2:调用构造方法
                     * 3:将这个对象指向空间
                     *
                     * 执行的顺序可能是123 或 132  在多线程下会出现问题 所以Lazy一定要加volatile修饰 避免指令重排
                     */
                }
            }
        }

        return lazy;
    }
}

CAS

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

缺点:

  1. 循环会耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

CAS:ABA问题(狸猫换太子)

在这里插入图片描述

在这里插入图片描述

原子引用解决CAS问题

解决ABA问题,引入原子引用 对应的思想:乐观锁

带版本号的原子操作

import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo {

    // AtomicStampedReference 注意:当传入的类型是包装类时,注意对象的引用问题

    // atomicStampedReference存入了一个值为1的Integer对象,并且版本号为1
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);

    public static void main(String[] args) {
        boolean b = atomicStampedReference.compareAndSet(1, 3, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
        System.out.println(b);
        System.out.println("stamp: " + atomicStampedReference.getStamp() + "      reference:" + atomicStampedReference.getReference());
    }
}

// 输出结果
true
stamp: 2      reference:3

原理就像乐观锁一样,更新值之前会去判断版本号,如果和期望的版本号不同,则不会更新

各种锁的理解

1:公平锁、非公平锁

公平锁:非常公平,先来后到,不能插队

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

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

ReentrantLock reentrantLock = new ReentrantLock(true);
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值