JUC并发

什么是JUC

在这里插入图片描述

juc就是java.util工具包下面的以上几种的统称。
Runnable 没有返回值,效率相比于Callable较低。

进程和线程

  • 进程:运行的程序集合,是进行资源分配的基本单位
  • 线程:cpu调度和执行的基本单位
  • Java默认有几个线程?2个。main和gc
  • 对于Java:Thread、Runnable、Callable
  • Java真的可以开启线程吗? 不能
    说明如下:
    在这里插入图片描述

点入start方法声明处

在这里插入图片描述

点入start0方法声明处

在这里插入图片描述

可以发现这是一个本地方法,通过**底层C++**实现开启线程,
原因:Java无法直接操作硬件

  • 并发:多线程操作同一个资源,交替执行。例如单核多条线程,快速交替
  • 并行:多个操作同时处理执行。例如多核多线程同时执行。线程池。
  • 并发编程的本质:为了充分利用CPU的资源

线程的状态(6种)

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,//创建

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,//运行

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,//阻塞

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        WAITING,//等待(死等)

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,//超时等待

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;//终止
    }
  • wait/sleep的区别

企业中不用sleep,而是用juc下的TimeUnit类下的方法。
wait是Object类的方法,sleep是Thread类的方法。
wait会释放锁,sleep不会释放锁
使用范围不同:wait必须在同步代码块中使用,sleep可以在任何地方使用
是否需要捕获异常:wait不需要捕获异常,sleep必须捕获异常

Lock锁(重点)

传统synchronized

真正的多线程开发:
线程就是一个单独的资源类,没有任何附属的操作
1.属性 & 方法
并发:多线程操作同一个资源类,把资源类丢入线程

package com.jerry.demo01;/*
 * @author jerry404lee
 * @date 2021/7/7 21:50
 */


//基本的卖票例子
public class Test1 {

    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();
        //@FunctionalInterface 函数式接口, 用lambda表达式 (参数)->{代码}
        new Thread(
                ()->{
                    for (int i = 1; i < 60 ; i++) {
                        ticket.sale();
                    }
                }, "A"
        ).start();
        new Thread(
                ()->{for (int i = 1; i < 60 ; i++) {
                    ticket.sale();
                }}, "B"
        ).start();
        new Thread(
                ()->{for (int i = 1; i < 60 ; i++) {
                    ticket.sale();
                }}, "C"
        ).start();

    }
}

//资源类
class  Ticket{
    //属性 方法 
    private  int number = 50;
    
    // 卖票的方式
    //synchronized本质:队列&锁
    public synchronized void sale(){
        if(number > 0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
        }
    }
    
    
    
}

Lock

在这里插入图片描述

在这里插入图片描述

  • 公平锁:先来后到
  • (默认) 非公平锁:可以插队

Lock三步:

  1. new ReentrantLock();
  2. lock.lock(); //加锁
  3. 业务代码写在try块中
  4. finally块中执行 lock.unlock(); //解锁
package com.jerry.demo01;/*
 * @author jerry404lee
 * @date 2021/7/7 21:50
 */


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

//基本的卖票例子
public class Test1 {

    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();
        //@FunctionalInterface 函数式接口, 用lambda表达式 (参数)->{代码}
        new Thread(
                ()->{
                    for (int i = 1; i < 60 ; i++) {
                        ticket.sale();
                    }
                }, "A"
        ).start();
        new Thread(
                ()->{for (int i = 1; i < 60 ; i++) {
                    ticket.sale();
                }}, "B"
        ).start();
        new Thread(
                ()->{for (int i = 1; i < 60 ; i++) {
                    ticket.sale();
                }}, "C"
        ).start();




    }


}

//资源类
class  Ticket{
    //属性 方法
    
    private  int number = 50;


    Lock lock = new ReentrantLock();

    // 卖票的方式
    //synchronized本质:队列
    public void sale(){


        lock.lock();//加锁


        try {
            if(number > 0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
        
}

Synchronized和Lock的区别

  1. Synchronized 是内置的Java关键字,Lock 是一个Java类;
  2. Synchronized 无法判断获取锁的状态, Lock 可以判断是否获取了锁
  3. Synchronized 会自动释放锁, Lock 必须手动释放锁,如果不释放锁,会造成死锁问题
  4. Synchronized 线程1获得锁并阻塞,线程2会死等。Lock 锁不一定,因为有tryLock方法;
  5. Synchronized 是可重入锁某个线程已经获得了某个锁,可以再次获取锁而不会出现死锁),不可以中断非公平。 Lock 是可重入锁,可以判断锁,非公平(可以自己设置)
  6. Synchronized 适合锁少量的代码同步问题。Lock 适合锁大量的代码。

生产者和消费者问题

package pc;/*
 * @author jerry404lee
 * @date 2021/7/8 0:28
 * 线程之间的通信问题 等待唤醒&通知唤醒
 * 线程交替执行
 */



public class A {

    public static void main(String[] args) {

        Data data = new Data();

        new Thread(
                ()->{
                    for (int i = 0; i < 10; i++) {
                        try {
                            data.inc();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, "A"
        ).start();

        new Thread(
                ()->{
                    for (int i = 0; i < 10 ; i++) {
                        try {
                            data.dec();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, "B"
        ).start();

    }

}


class Data{//数字 资源类

    private  int number = 0;

    public  synchronized  void inc() throws InterruptedException {
        if(number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }

    public  synchronized void dec() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);

        this.notifyAll();
    }


}


这里判断语句用到if,可能出现虚假唤醒问题(上述代码如果加入类似的更多线程比如C+1、D-1,执行结果就会出现不正确的同步结果)
在这里插入图片描述
上述代码中实现“生产者-消费者问题”用的是“Synchronized+wait+notifyAll”。那么与Synchronized对应的Lock类如何解决相同问题呢?用Condition。

在这里插入图片描述

在这里插入图片描述例子:
在这里插入图片描述
使用Condition的优势

  • Condition 可以实现精准的通知和唤醒线程
package pc;/*
 * @author jerry404lee
 * @date 2021/7/8 13:10
 *A执行完调用B,B执行完调用C,C执行完调用A
 */

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

public class B {

    public static void main(String[] args) {

        DataB data = new DataB();


        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 DataB{//资源类 Lock

    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    private Condition conditionC = lock.newCondition();
    private int number = 1;
    //标记位 1A 2B 3C


    public  void printA(){
        lock.lock();

        try {
            while (number !=1){
                conditionA.await();//根据标记位,等待
            }
            System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAAAA");
            number = 2; //更改标记位
            conditionB.signal();//唤醒指定的B线程

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public  void printB(){
        lock.lock();

        try {
            while (number !=2){
                conditionB.await();//根据标记位,等待
            }
            System.out.println(Thread.currentThread().getName()+"=>BBBBBBBB");
            number = 3; //更改标记位
            conditionC.signal();//唤醒指定的C线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public  void printC(){
        lock.lock();

        try {
            while (number !=3){
                conditionC.await();//根据标记位,等待
            }
            System.out.println(Thread.currentThread().getName()+"=>CCCCCCCCCCC");
            number = 1; // 更改标记位
            conditionA.signal();//唤醒指定的A线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


}

运行结果
在这里插入图片描述应用场景:生产线:下单-》支付-》交易-》物流

重点问题:什么是锁?如何判断锁的是谁?

8 锁现象

synchronized 锁的对象是方法的调用者,谁先拿到谁先执行

在这里插入图片描述

并发下不安全的ArrayList(集合类)

解决方案:
在这里插入图片描述

其中,CopyOnWrite写入时复制,是计算机程序设计中的一种优化策略:多个线程调用的时候,list读取的时候是固定的,写入的时候,避免覆盖造成数据问题;读写分离。(之前用的是lock锁,现在可能是因为synchronized被优化,所以用synchronized)

在这里插入图片描述看HashSet的源码,可以发现
在这里插入图片描述事实上是用一个HashMap来实现的。

在这里插入图片描述
而在其他比如add操作上,实际上也是进行map操作,这里是利用了map中键值唯一性,正好实现set中无重复元素的特性。

Callable

在这里插入图片描述着重比较与Runnable的区别:

  1. 可以有返回值
  2. 可以跑出异常
  3. 方法不同,run()/call()
package callable;/*
 * @author jerry404lee
 * @date 2021/7/8 16:21
 */

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {

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

        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(
                futureTask,"A"
        ).start();
        Integer o = (Integer)futureTask.get();
        System.out.println(o);


    }

}

class MyThread implements Callable<Integer> {


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

运行结果
在这里插入图片描述
关键:Thread只认Runnable,所以用Callable初始化Thread的过程,需要借助FutureTask。
其中futureTask的get方法可能会产生阻塞,所以把它放在最后,或者需要使用异步通信来处理。

常用辅助类

CountDownLatch

作用:计数
在这里插入图片描述代码示例:

package countdownlathc;/*
 * @author jerry404lee
 * @date 2021/7/8 16:48
 */

import java.util.concurrent.CountDownLatch;

//减法计数器
public class CDLdemo {

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

        CountDownLatch countDownLatch = new CountDownLatch(6); //从6开始倒计时

        for (int i = 0; i < 6; i++) {
            new Thread(
                    ()->{
                        System.out.println(Thread.currentThread().getName()+"gone");
                        countDownLatch.countDown();
                    },String.valueOf(i)
            ).start();


        }


        //countDownLatch.await();//等待计数器归零,然后再向下执行
        System.out.println("结束");


    }

}

其中countDownLatch的await方法重要:等待计时器归零,然后才继续向下执行。否则结果可能如下:
在这里插入图片描述
去除注释,结果如下:
在这里插入图片描述
即所期望的“结束”出现在最后。
应用场景:针对必须要执行的任务的时候使用。

CyclicBarrier

在这里插入图片描述

在这里插入图片描述

Semaphore

在这里插入图片描述
在这里插入图片描述运行结果:

在这里插入图片描述
应用:限流的时候也会用
信号量:联想操作系统中的部分,没有差别。

  • semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止。
  • semaphore.release():释放,会将当前的信号量释放+1,然后唤醒等待的线程。

ReadWriteLock读写锁

在这里插入图片描述比起Lock,是更细粒度,分为读锁和写锁

package rw;/*
 * @author jerry404lee
 * @date 2021/7/8 19:05
 */

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {


    public static void main(String[] args) {

        MyCache myCache = new MyCache();


        //5个线程写入
        for (int i = 0; i < 5; i++) {
            final int tmp = i;
            new Thread(
                    ()->{
                        myCache.put(tmp+"",tmp*2);
                    },String.valueOf(i)
            ).start();
        }
        //5个线程读取
        for (int i = 0; i < 5; i++) {
            final int tmp = i;
            new Thread(
                    ()->{
                        myCache.get(tmp+"");
                    },String.valueOf(i)
            ).start();
        }

    }


}


/*
* 自定义缓存
*
* */

class MyCache{

    private  volatile Map<String, Object> map = new HashMap<>();
    private  ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //存,写
    public void put(String key, Object value){
        readWriteLock.writeLock().lock();


        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key+":"+value);
            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);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

}

运行结果

在这里插入图片描述小结

  • 独占锁–写锁:一次只能被一个线程占有
  • 共享锁–读锁:多个线程可以同时占有
  • ReadWriteLock
  • 读-读 可以共存
  • 读-写 不能共存
  • 写-写 不能共存

BlockingQueue阻塞队列

在这里插入图片描述在这里插入图片描述总结4组API
在这里插入图片描述

SynchronousQueue 同步队列

  • 没有容量
  • 进去一个元素,必须等待取出来之后才能再往里面放一个元素。
  • put & take

线程池(重点)

池化技术三大方法七大参数四种拒绝策略
优化资源的使用
线程池、连接池。。。
线程池的好处

  1. 降低资源的消耗
  2. 提高响应的速度
  3. 方便管理
    核心:线程复用,可以控制最大并发数,管理线程

三大方法

在这里插入图片描述但是这里标红的原因是不符合编码规约,Alibaba手册中说明了不使用Executors来创建线程池的原因是为了规避资源耗尽的风险,而是应该通过ThreadPoolExecutor来创建线程池。

package poll;/*
 * @author jerry404lee
 * @date 2021/7/8 23:25
 */

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test01 {

    public static void main(String[] args) {

        //ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程
       //ExecutorService executorService =Executors.newFixedThreadPool(5);//创建一个固定线程数的线程池
        ExecutorService executorService =Executors.newCachedThreadPool();//创建动态的线程池


        try {

            for (int i = 0; i < 100; i++) {
                executorService.execute(
                        ()->{
                            System.out.println(Thread.currentThread().getName()+"ok");
                        }
                );

            }



        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();//用来关闭线程池

        }


    }


}

七大参数

点入上述Executors创建线程池的源码中,可以看到都是由ThreadPoolExecutor创建的线程池
在这里插入图片描述所以观察一下ThreadPoolExecutor到底是什么

在这里插入图片描述关键是七大参数
在这里插入图片描述在这里插入图片描述

四种拒绝策略

在这里插入图片描述在这里插入图片描述那么,线程池的最大线程数如何设置
需要了解IO密集型和CPU密集型(调优

四大函数式接口

  • lambda表达式

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

在这里插入图片描述

在这里插入图片描述
Function:函数型接口。一个输入,一个输出
在这里插入图片描述Predicate:断定型接口。有一个输入参数,返回值只能是布尔类型
在这里插入图片描述Consumer:消费型接口。只有输入,没有返回值。

在这里插入图片描述

Supplier:供给型接口。没有输入,只有返回值。

在这里插入图片描述

Stream流式计算

什么是流式计算?

在这里插入图片描述举例
在这里插入图片描述在这里插入图片描述这种写法就是链式编程。

ForkJoin

并行执行任务,提高效率,针对大数据量。

在这里插入图片描述特点:工作窃取

如何使用ForkJoin?

  1. ForkJoinPool创建
  2. 新建计算任务 forkjoinPool.ForkJoinTask
    在这里插入图片描述

异步回调

Future
一般用其实现类CompletableFuture
在这里插入图片描述

理解JMM

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

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

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

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

可以再看一下缓存一致性协议MESI

Volatile

Volatile是Java虚拟机提供的轻量级的同步机制

  1. 保证可见性
    在这里插入图片描述

  2. 不保证原子性
    原子性:不可分割,要么全部执行,要么全部不执行。即线程在执行任务的时候,不能被分割,也不能被中断,要么同时成功,要么同时失败。

package vola;/*
 * @author jerry404lee
 * @date 2021/7/9 12:47
 * //不保证原子性
 */

public class Demo {


    private  static  int num = 0;

    public static void add(){
        num++;
    }


    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {
            //理论上num的最终运行结果应该为20000
            new Thread(
                    ()->{
                        for (int j=0; j<1000; j++) {
                            add();
                        }
                    },String.valueOf(i)
            ).start();

        }

        while (Thread.activeCount()>2){//main线程&gc
            Thread.yield();//yield让出计算资源并重新竞争资源,礼让,让main线程放弃CPU给别的线程,这样main线程就不会执行下去,等到其他线程都死了,main线程再往下执行
        }

        System.out.println(num);

    }

}

运行结果,num不是20000
在这里插入图片描述
此时,就算将num加上关键字volatile,这个结果仍然不是预期的20000,这也说明了volatile不保证原子性
在这里插入图片描述

但是,如果在用synchronized修饰add方法,这就保证了add方法中的num++的原子性。运行如图:
在这里插入图片描述这里,也体现出一点**volatile(轻量级)synchronized(重量级)**两种同步机制的使用差别。

使用javap -c命令可以看到num++操作实际上是多个步骤
在这里插入图片描述当然,还有另外的方法保证原子性,也就是使用原子类Atomic
在这里插入图片描述原子类的底层和操作系统挂钩,CAS。在内存中修改值。Unsafe类是一个特殊的存在。

  1. 禁止指令重排

指令重排就是在不改变期望的运行结果的前提下,改变指令运行顺序以提升计算速度的操作。算是计算机进行优化的一种方式。但是当这种优化面对多线程并发的情况时就容易出现“问题”。
处理器在进行指令重排的时候,考虑:数据之间的依赖。
内存屏障:禁止volatile上面指令和下面指令顺序交换。

  • 保证特定操作的执行顺序
  • 保证某些变来个的内存可见性

单例模式

饿汉式

package single;/*
 * @author jerry404lee
 * @date 2021/7/9 18:02
 */
//饿汉式单例
public class Hungry {

    private Hungry(){
        System.out.println(Thread.currentThread().getName()+"创建实例成功");
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }



}

双重检测锁-懒汉式

package single;/*
 * @author jerry404lee
 * @date 2021/7/9 18:04
 */

public class Lazy {

    private Lazy(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static  Lazy lazy;

    public static Lazy getLazy(){

        if(lazy == null){
            synchronized (Lazy.class){
                if(lazy == null){
                    lazy = new Lazy();
                }
            }

        }
        return  lazy;

    }


    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(
                    ()->{
                        getLazy();
                    }
            ).start();
        }


    }

}

在这里插入图片描述如果不是双重校验锁的话,同样在多线程的情况下会创建多个实例,破坏单例。
在这里插入图片描述同时,这里要注意lazy变量的修饰符volatile的作用。既然synchronized关键字已经给class(仅一个)加锁了,也就是保证了原子性,那么volatile的作用是什么呢?volatile是不是多此一举呢

  • 双重检测加锁:保证了操作的原子性,只有一个线程能创建一个实例,其他线程无法创建第二个
  • volatile关键字:是为了防止因为new lazy()这个操作因为底层是多条指令组成,所以可能会在多线程&指令重排的双重环境下导致只是完成了“分配空间”+“对象地址引用”这两步(指令重排),此时另外的线程过来发现对象已经被创建了(实际上还有第三步没有执行完,所以也就是没有真正完成初始化,因为指令重排的缘故而被欺骗了),但是导致最终获取到的对象是还没有被初始化完成的。

通过反射破坏单例

 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Lazy instance = Lazy.getLazy();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);//无视了私有的构造器
        Lazy tmp = declaredConstructor.newInstance();

        if (  tmp.hashCode()==instance.hashCode() && tmp.equals(instance)) {
            System.out.println("相同实例");
        } else {
            System.out.println("创建了两个不同实例,破坏了单例");
        }
    }

在这里插入图片描述

CAS(compare and swap)

在这里插入图片描述如上图:
CAS比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环。
缺点

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

比较初始值initialValue和期望值expectedValue是否相等,也就是是否与期望相符

  • 如果不相等,则atomicInteger不修改值(仍然是initialValue的值)
  • 如果相等,则将atomicInteger修改为新值newValue

可能还会问道Unsafe

ABA问题

ABA问题(狸猫换太子)

旧A-被修改为B-为修改为新A,看似值还是A,但是这个修改的过程已经发生了。
在这里插入图片描述
Java中如何解决ABA问题?
用原子引用AtomicStampedReference,原理其实就是加了版本号,时间戳,进一步在值相等的情况下区分旧A新A(联想MVCC版本控制
在这里插入图片描述可以看到,多了一个参数initialStamp
在这里插入图片描述而compareAndSet函数中也多了两个参数expectedStampnewStamp

package CAS;/*
 * @author jerry404lee
 * @date 2021/7/9 19:15
 * //CAS:compare and swap比较并交换
 */

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

public class CASDemo {


    public static void main(String[] args) {
        AtomicStampedReference<Object> atomicInteger =
                new AtomicStampedReference<>(19, 1);

        //public final boolean compareAndSet(int expectedValue, int newValue)期望值,新值
        //如果期望的值与定义值相等,那么就更新为新值,否则不更新


        new Thread(
                ()->{



                    atomicInteger.compareAndSet(19,20,atomicInteger.getStamp(),atomicInteger.getStamp()+1);
                    System.out.println("线程A的版本号是"+atomicInteger.getStamp());

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


                },"a"
        ).start();

        new Thread(
                ()->{
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    atomicInteger.compareAndSet(20,21,atomicInteger.getStamp(),atomicInteger.getStamp()+1);
                    System.out.println("线程B的版本号是"+atomicInteger.getStamp());


                },"b"



        ).start();





    }

}

注意,这里,因为自动装箱的原因,有个大坑,推荐使用较小的参数(<128)进行测试。如果泛型是一个包装类,注意对象的引用问题。
在这里插入图片描述

公平锁、非公平锁

Lock部分有提及。
在这里插入图片描述

可重入锁、自旋锁等

可重入锁(递归锁):某个线程已经获取了某个锁,那么他可以再次获取该锁而不陷入死锁。
详细可见 可重入锁详解

自旋锁

在这里插入图片描述简单介绍各种锁 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

死锁排查

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

  2. 使用 jstack 进程号 查看进程信息
    在这里插入图片描述这里显示GC,是因为在源程序中写了死循环测试。

  3. 日志

  4. 看堆栈信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值