《JUC构建高并发系统实战》学习笔记

JUC并发编程-大厂

课程说明

《JUC构建高并发系统实战》学习笔记

线程基础知识复习

多线程为什么那么重要

硬件:摩尔定律失效

软件:异步回调的生产需求

《JUC构建高并发系统实战》学习笔记

start一个线程

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

Java多线程相关概念

《JUC构建高并发系统实战》学习笔记
《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

用户线程和守护线程

即便是写一个最简单的HelloWord程序,也会有两个线程:用户线程(main)和 守护线程(GC垃圾回收线程)

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

CompletableFuture

Future和Callable接口

Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

Callable接口中定义了需要有返回的任务需要实现的方法。 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,过了一会才去获取子任务的执行结果。

《JUC构建高并发系统实战》学习笔记

FutureTask

《JUC构建高并发系统实战》学习笔记

规范:system.out.println(futureTask.get());放在最后

只要出现get方法,不管是否计算完成都阻塞等待结果完成再运行后面的代码

《JUC构建高并发系统实战》学习笔记

对Future的改进

CompletableFuture的优点:

异步任务结束时,会自动回调某个对象的方法;

异步任务出错时,会自动回调某个对象的方法;

主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

set、get 的区别

set和get基本上没啥区别,join不抛出异常

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

案例:电商网站性价需求

《JUC构建高并发系统实战》学习笔记

记录一次你项目中的一次亮点:

我做过一个比价需求,这个需求是要求我们去爬别的网站的数据,针对于同一件物品,看看他们在不同的网站上售价分别是多少。爬虫的兄弟提供给了我一些JOSN格式的数据,我自己做了一个HashMap(或Redis的zest中),做完数据清洁,保证没有重复数据之后,大概是有一万条数据。

针对这一万条数据,最笨的方法是全文扫描,一个一个的过,但是这样虽然可以实现,但是比较慢

后面了解到JUC里面有个CompletableFuture,可以做异步多线程并发,而且不阻塞,用它之后,就可以把网站的性能从xxx秒优化到xxx秒,这就是我技术上一个比较值得骄傲的亮点。

而且CompletableFuture默认使用的 forkjoin 的线程池,我自己手写了线程池,ThreadPollExactor,具体的参数根据自己的系统来定义

package com.juc;

import jdk.nashorn.internal.objects.annotations.Getter;

import java.util.*;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: GengKY
 * @Date: 2021/12/29 13:29
 */

class ComplateFutureNetMallDemo{

    static List<NetMall> list= Arrays.asList(
            new NetMall("JD"),
            new NetMall("PDD"),
            new NetMall("TaoBao"),
            new NetMall("DangDang"),
            new NetMall("TianMao")
    );

    public static List<String> getPriceByStep(List<NetMall> list,String productName){
        return list.stream()
                .map(netMall -> String.format(productName+"in %s price is %.2f",netMall.getMallName(),netMall.calPrice(productName)))
                .collect(Collectors.toList());
    }


    //List<NetMall> ---->List<completableFuture<String>> --->List<string>
    public static List<String> getPriceByASync(List<NetMall> list,String productName){
        return list.stream()
                .map(netMall -> CompletableFuture.supplyAsync(
                        () -> String.format(productName + "in %s price is %.2f", netMall.getMallName(), netMall.calPrice(productName))))
                .collect(Collectors.toList())
                .stream().map(CompletableFuture::join)
                .collect(Collectors.toList());

    }

    public static void main(String[] args) {

        System.out.println("1------------");
        long startTime=System.currentTimeMillis();
        List<String> list1 = getPriceByStep(list, "mysql");
        for (String element : list1) {
            System.out.println(element);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("花费时间: "+(endTime-startTime)+"毫秒");


        System.out.println("2------------");
        long startTime2=System.currentTimeMillis();
        List<String> list2 = getPriceByASync(list, "mysql");
        for (String element : list2) {
            System.out.println(element);
        }
        long endTime2=System.currentTimeMillis();
        System.out.println("花费时间: "+(endTime2-startTime2)+"毫秒");
    }
}

public class NetMall {
    private String mallName;
    public String getMallName() {
        return mallName;
    }
    public NetMall(String mallName){
        this.mallName=mallName;
    }
    public double calPrice(String productName) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
    }
}

《JUC构建高并发系统实战》学习笔记

CompletableFuture常用方法

获得结果和触发计算

《JUC构建高并发系统实战》学习笔记

对计算结果进行处理

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

对计算结果进行消费

《JUC构建高并发系统实战》学习笔记

对计算速度进行选用

《JUC构建高并发系统实战》学习笔记

对计算结果进行合并

《JUC构建高并发系统实战》学习笔记

家庭作业

《JUC构建高并发系统实战》学习笔记

thenCompose() 流水线

Java中的"锁"事

《JUC构建高并发系统实战》学习笔记

乐观锁和悲观锁

《JUC构建高并发系统实战》学习笔记

线程八锁/锁的是什么

根据锁的范围划分:

无锁、有锁;

锁区块、锁方法体;

对象锁、类锁

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

8种锁的案例实际体现在3个地方:

作用于实例方法,当前实例加锁,进入向步代码前要获得当前实例的锁

作用于代码块,对括号里配置的对象加锁

作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁

从自己码角度分析sync

sync同步代码块

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

锁同步代码块:对象锁

public void method(){
    Object object=new Object();
    synchronized (object){
        System.out.println("hello");
    }
}

锁同步代码块:类锁

public void method(){
    Object object=new Object();
    synchronized (object.getClass()){
        System.out.println("hello");
    }
}

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,

其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor实现的。每个对象中都内置了一个 ObjectMonitor对象。

另外,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

sync普通同步方法

《JUC构建高并发系统实战》学习笔记

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,

该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

sync锁静态同步方法

《JUC构建高并发系统实战》学习笔记

sync锁的是什么

《JUC构建高并发系统实战》学习笔记

管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。

这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

MarkWord预热

synchronized必须作用于某个对象中,所以Java在对象的头文件存储了锁的相关信息。锁升级功能主要依赖于 MarkWord 中的锁标志位和释放偏向锁标志位,后续讲解锁升级时候我们再加深,目前为了承前启后的学习,对下图先混个眼熟即可,O(∩_∩)O

《JUC构建高并发系统实战》学习笔记

公平锁和非公平锁

《JUC构建高并发系统实战》学习笔记

class Ticket{
    private int number = 30;
    ReentrantLock lock = new ReentrantLock();

    public void sale(){
        lock.lock();
        try{
            if(number > 0)
                System.out.println(Thread.currentThread().getName()+"卖出第:\t"+(number--)+"\t 还剩下:"+number);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


public class SaleTicketDemo{
    public static void main(String[] args){
        Ticket ticket = new Ticket();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"a").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"b").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"c").start();
    }
}

《JUC构建高并发系统实战》学习笔记

为什么会有公平锁/非公平锁的设计?为什么默认非公平?

1、恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。

2、使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

使用公平锁会有什么问题?

公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,

这就是传说中的 “锁饥饿”

什么时候用公平?什么时候用非公平?

如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;

否则那就用公平锁,大家公平使用。

《JUC构建高并发系统实战》学习笔记

可重入锁(递归锁)

《JUC构建高并发系统实战》学习笔记

说明:

可重入锁又名递归锁

是指在同一个线程在外层方法获取锁的时候再进入该线程的内层方法自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚

所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁

隐式的可重入锁

指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的

与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。

同步块

public class ReEntryLockDemo
{
    public static void main(String[] args){
        final Object objectLockA = new Object();
        new Thread(() -> {
            synchronized (objectLockA){
                System.out.println("-----外层调用");
                synchronized (objectLockA){
                    System.out.println("-----中层调用");
                    synchronized (objectLockA){
                        System.out.println("-----内层调用");
                    }
                }
            }
        },"a").start();
    }
}

同步方法

public class ReEntryLockDemo{
    public synchronized void m1(){
        System.out.println("-----m1");
        m2();
    }
    public synchronized void m2(){
        System.out.println("-----m2");
        m3();
    }
    public synchronized void m3(){
        System.out.println("-----m3");
    }
    public static void main(String[] args){
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        reEntryLockDemo.m1();
    }
}

Synchronized的重入的实现机理

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。

执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

显式的可重入锁

加锁、解锁次数不一样的话,自己玩没事

多个线程一起的话,会卡住别人的线程

public class ReEntryLockDemo{
    static Lock lock = new ReentrantLock();
    public static void main(String[] args){
        new Thread(() -> {
            lock.lock();//1、第一次lock
            try{
                System.out.println("----外层调用lock");
                lock.lock();//2、第二次lock
                try{
                    System.out.println("----内层调用lock");
                }finally {
                    //lock.unlock(); // 正常情况,加锁几次就要解锁几次
                    //正常运行,不会卡住
                }
            }finally {
                lock.unlock();
            }
        },"a").start();
    }
}
public class ReEntryLockDemo{
    static Lock lock = new ReentrantLock();
    public static void main(String[] args){
        new Thread(() -> {
            lock.lock();//1、第一次lock
            try{
                System.out.println("----外层调用lock");
                lock.lock();//2、第二次lock
                try{
                    System.out.println("----内层调用lock");
                }finally {
                    // 这里故意注释,实现加锁次数和释放次数不一样
                    // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
                    //lock.unlock(); // 正常情况,加锁几次就要解锁几次
                    //第二个线程会卡住
                }
            }finally {
                lock.unlock();
            }
        },"a").start();

        new Thread(() -> {
            lock.lock();
            try{
                System.out.println("b thread----外层调用lock");
            }finally {
                lock.unlock();
            }
        },"b").start();
    }
}

死锁

《JUC构建高并发系统实战》学习笔记

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

《JUC构建高并发系统实战》学习笔记

手写一个死锁案例

public class DeadLockDemo{
    public static void main(String[] args){
        final Object objectLockA = new Object();
        final Object objectLockB = new Object();

        new Thread(() -> {
            synchronized (objectLockA){
                System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLockB){
                    System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
                }
            }
        },"A").start();

        new Thread(() -> {
            synchronized (objectLockB){
                System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLockA){
                    System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
                }
            }
        },"B").start();
    }
}

怎么证明是死锁

使用 jconsole 命令调出控制台

《JUC构建高并发系统实战》学习笔记

使用 jps -l jstack 线程ID

《JUC构建高并发系统实战》学习笔记

其他的一些锁

《JUC构建高并发系统实战》学习笔记

线程中断机制

《JUC构建高并发系统实战》学习笔记

面试题反馈

《JUC构建高并发系统实战》学习笔记

什么是中断

《JUC构建高并发系统实战》学习笔记

中断的相关API的方法

《JUC构建高并发系统实战》学习笔记

如何使用中断标志停止线程

《JUC构建高并发系统实战》学习笔记

通过一个volatile变量实现

《JUC构建高并发系统实战》学习笔记

通过AtomicBoolean

《JUC构建高并发系统实战》学习笔记

通过Thread类自带的中断api方法实现

《JUC构建高并发系统实战》学习笔记

interrupt源码分析

如果一个被阻塞的线程(wait()、join()、sleep())是 中断的话,将会清除中断标志位(重新设置为false),并抛出InterruptedException 异常

《JUC构建高并发系统实战》学习笔记

isInterrupted源码分析

《JUC构建高并发系统实战》学习笔记

Interrupt是否是立刻停止

《JUC构建高并发系统实战》学习笔记

以下代码说明,当对一个线程调用Interrupt时,并不会立刻停止

《JUC构建高并发系统实战》学习笔记

后手案例

后手案例-1

《JUC构建高并发系统实战》学习笔记

执行结果:

程序正常执行,在3秒内持续打印 hello interrupt ,3秒后,打印“***程序结束”

后手案例-2

相较于“后手案例-1”,在 while 循环内 执行休眠操作,并用 try-catch 捕获异常

猜测:打印六次 “hello interrupt”,打印 “***程序结束”

《JUC构建高并发系统实战》学习笔记

运行结果:

报异常,程序无法停止

《JUC构建高并发系统实战》学习笔记

出现问题的原因:

线程t1在休眠时,线程t2设置 t1.interrupt,导致报异常

报异常之后,interrupt 状态重新设置为 false ,无法停止

解决方案:

在 try-catch 中在设置一次 interrupt

后手案例-3

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

结论

《JUC构建高并发系统实战》学习笔记

静态方法 Thread.interrupted

《JUC构建高并发系统实战》学习笔记

代码演示

《JUC构建高并发系统实战》学习笔记

方法说明

《JUC构建高并发系统实战》学习笔记

Interrupted对比IsInterrupted

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

线程中断总结

《JUC构建高并发系统实战》学习笔记

加餐 | 要了解系统架构设计

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

LockSupport

什么是LockSupport

LockSupport Java doc

《JUC构建高并发系统实战》学习笔记

LockSupport是用来创建锁其他同步类基本线程阻塞原语

《JUC构建高并发系统实战》学习笔记

简单说明

一句话说明LockSupport:

LockSupport就是线程等待和唤醒机制(wait/notify)的加强改良版

LockSupport中的 park()unpark()的作用分别是阻塞线程解除阻塞线程

总之,比wait/notify,await/signal更强。

3种让线程等待和唤醒的方法

  • 方式1:使用Object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程

  • 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

  • 方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

两个重要方法

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

线程等待唤醒机制

《JUC构建高并发系统实战》学习笔记

Wait和Notify限制

《JUC构建高并发系统实战》学习笔记

Object类中的wait和notify方法实现线程等待和唤醒

public class WaitNotifyDemo {

	static Object lock = new Object();
	
	public static void main(String[] args) {
		new Thread(()->{
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName()+" come in.");
				try {
					lock.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+" 换醒.");
		}, "Thread A").start();
		
		new Thread(()->{
			synchronized (lock) {
				lock.notify();
				System.out.println(Thread.currentThread().getName()+" 通知.");
			}
		}, "Thread B").start();
	}
}

wait和notify方法必须要在同步块或者方法里面成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。

public class WaitNotifyDemo {
    static Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " come in.");
            try {
                lock.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 换醒.");
        }, "Thread A").start();

        new Thread(() -> {
            lock.notify();
            System.out.println(Thread.currentThread().getName() + " 通知.");
        }, "Thread B").start();
    }
}
Exception in thread "Thread B" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.notify(Native Method)
	at com.example.demo07.main.WaitNotifyDemo.lambda$main$1(WaitNotifyDemo.java:19)
	at java.base/java.lang.Thread.run(Thread.java:829)
Thread A come in.
Thread A 换醒.
java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Object.wait(Object.java:328)
	at com.example.demo07.main.WaitNotifyDemo.lambda$main$0(WaitNotifyDemo.java:11)
	at java.base/java.lang.Thread.run(Thread.java:829)

调用顺序要先wait后notify才OK。

先wait后notify、notifyall方法,等待中的线程才会被唤醒,否则无法唤醒

public class WaitNotifyDemo {
	static Object lock = new Object();
	public static void main(String[] args) {
		new Thread(()->{
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName()+" come in.");
				try {
					lock.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+" 换醒.");
		}, "Thread A").start();

		new Thread(()->{
			synchronized (lock) {
				lock.notify();
				System.out.println(Thread.currentThread().getName()+" 通知.");
			}
		}, "Thread B").start();
	}
}
Thread B 通知.
Thread A come in.
.......无限等待......

Await和Signal限制

《JUC构建高并发系统实战》学习笔记

Condition接口中的await后signal方法实现线程的等待和唤醒,与Object类中的wait和notify方法实现线程等待和唤醒类似。

public class ConditionAwaitSignalDemo {		
	public static void main(String[] args) {	
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
        
		new Thread(()->{	
			try {
				System.out.println(Thread.currentThread().getName()+" come in.");
				lock.lock();
				condition.await();				
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
			System.out.println(Thread.currentThread().getName()+" 换醒.");
		},"Thread A").start();
		
		new Thread(()->{
			try {
				lock.lock();
				condition.signal();
				System.out.println(Thread.currentThread().getName()+" 通知.");
			}finally {
				lock.unlock();
			}
		},"Thread B").start();
	}
}

输出结果:

Thread A come in.
Thread B 通知.
Thread A 换醒.

await和signal方法必须要在同步块或者方法里面且成对出现使用,否则会抛出 java.lang.IllegalMonitorStateException。

调用顺序要先await后signal才OK。

LockSupport方法介绍

API中的LockSupport方法摘要

《JUC构建高并发系统实战》学习笔记

传统的 synchronized 和 Lock 实现等待唤醒通知的约束

  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

LockSupport类中的park等待和unpark唤醒

Basic thread blocking primitives for creating locks and other synchronization classes.

This class associates, with each thread that uses it, a permit (in the sense of the Semaphore class). A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.) link

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。

可以把许可看成是一种(0.1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。

《JUC构建高并发系统实战》学习笔记

通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作

park()/park(Object blocker) - 阻塞当前线程阻塞传入的具体线程

public class LockSupport {
    ...
    public static void park() {
        UNSAFE.park(false, 0L);
    }
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    ...
}

permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为0并返回。

unpark(Thread thread) - 唤醒处于阻塞状态的指定线程

public class LockSupport {
    ...
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    ...
}

调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,pemit值还是1)会自动唤醒thead线程,即之前阻塞中的LockSupport.park()方法会立即返回。

LockSupport案例解析

《JUC构建高并发系统实战》学习笔记

正常使用

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 换醒. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread B");
        b.start();
    }
}

输出结果:

Thread A come in.
Thread B 通知.
Thread A 换醒.

正常 + 无锁块要求。

先前错误的先唤醒后等待顺序,LockSupport可无视这顺序。

【支持】先执行释放锁的操作unpark,后加锁

为什么可以先唤醒线程后阻塞线程?

因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 换醒. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread B");
        b.start();
    }
}

【支持】unpack多次

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 换醒. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            LockSupport.unpark(a);
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread B");
        b.start();
    }
}

【不支持】pack多次,导致无限等待

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 换醒. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread B");
        b.start();
    }
}

【支持】多个通行证,由不同的线程提供

package com.juc;

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo2 {
    public static void main(String[] args) {
        //park多次
        Thread thread1  = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 唤醒. " + System.currentTimeMillis());
        }, "Thread A");
        thread1.start();

        //线程B unpark一次
        new Thread(()->{
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread B").start();

        //线程C unpark一次
        new Thread(()->{
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread C").start();

        //线程D unpark一次
        new Thread(()->{
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName()+" 通知.");
        }, "Thread D").start();
    }
}

重点说明

LockSupport是用来创建锁和共他同步类的基本线程阻塞原语。

LockSuport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻寨之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程

LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,

调用一次unpark就加1变成1,

调用一次park会消费permit,也就是将1变成0,同时park立即返回。

如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证

形象的理解

线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。

当调用park方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出。
  • 如果无凭证,就必须阻塞等待凭证可用。

而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无放。

面试题

1、为什么可以先唤醒线程后阻塞线程?

因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

2、为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

因为凭证的数量最多为1(不能累加),连续调用两次 unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。

Java内存模型值JMM

《JUC构建高并发系统实战》学习笔记

面试题反馈

《JUC构建高并发系统实战》学习笔记

计算机硬件体系结构

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

什么是JMM

《JUC构建高并发系统实战》学习笔记

JVM规范下三大特性

可见性

《JUC构建高并发系统实战》学习笔记

原子性

指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰

有序性

《JUC构建高并发系统实战》学习笔记

多线程遍历的读取过程

《JUC构建高并发系统实战》学习笔记

读取过程

《JUC构建高并发系统实战》学习笔记

小总结

我们定义的所有共享变量都储存在物理主内存

每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)

线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)

不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能相互访问)

happens-before原则

《JUC构建高并发系统实战》学习笔记

x、y案例说明

《JUC构建高并发系统实战》学习笔记

先行发生原则说明

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

happens-before八条规则

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

代码说明

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

内存屏障

《JUC构建高并发系统实战》学习笔记

volatile内存语义

《JUC构建高并发系统实战》学习笔记

什么是内存屏障

《JUC构建高并发系统实战》学习笔记

内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatle实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。

内存屏障之前的所有写操作都要回写到主内存

内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。

《JUC构建高并发系统实战》学习笔记

四类内存屏障源码分析

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

四大屏障分别是什么意思

《JUC构建高并发系统实战》学习笔记

内存屏障插入策略

《JUC构建高并发系统实战》学习笔记

happens-before之volatile变量规则

《JUC构建高并发系统实战》学习笔记

写总结

1、在每个volatile 写操作的前面插入一个StoreStore屏障

2、在每个volatile 写操作的后面插入一个StoreLoad屏障

《JUC构建高并发系统实战》学习笔记

读总结

3、在每个volatile读操作的后面插入一个LoadLoad屏障

4、在每个volatile读操作的后面插入一个LoadStore屏障

《JUC构建高并发系统实战》学习笔记

volatile关键字

保证可见性

代码说明

不加volatile,没有可见性,程序无法停止

加了volatile,保证可见性,程序可以停止

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

volatile变量的读写过程

《JUC构建高并发系统实战》学习笔记


《JUC构建高并发系统实战》学习笔记

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

没有原子性

代码演示

输出结果:基本都会小于10000,有可能会出现一次10000

《JUC构建高并发系统实战》学习笔记

字节码角度说明

《JUC构建高并发系统实战》学习笔记

不保证原子性

《JUC构建高并发系统实战》学习笔记

读取一个普通变量的情况

《JUC构建高并发系统实战》学习笔记

volatile对指令的处理

《JUC构建高并发系统实战》学习笔记

读取赋值一个volatile变量的情况

《JUC构建高并发系统实战》学习笔记

间隙期不同非原子操作

《JUC构建高并发系统实战》学习笔记

禁止指令重排

《JUC构建高并发系统实战》学习笔记

说明与案例

《JUC构建高并发系统实战》学习笔记

内存屏障二次复习

《JUC构建高并发系统实战》学习笔记

volatile有关的禁止指令重排的行为

《JUC构建高并发系统实战》学习笔记

代码说明

《JUC构建高并发系统实战》学习笔记

正确使用volatile

《JUC构建高并发系统实战》学习笔记

使用案例:状态标志

《JUC构建高并发系统实战》学习笔记

使用案例:读多写少

《JUC构建高并发系统实战》学习笔记

使用案例:双端检索

问题代码

《JUC构建高并发系统实战》学习笔记

单线程、多线程下看问题代码

《JUC构建高并发系统实战》学习笔记

双端检索正确代码

《JUC构建高并发系统实战》学习笔记

静态内部类实现单例模式

《JUC构建高并发系统实战》学习笔记

volatile总结

《JUC构建高并发系统实战》学习笔记

内存屏障是什么

内存屏障:

是一种 屏障指令,它使得 CPU或编译器屏障指令的前和后 所发出的内存操作 执行一个排序的约束。也叫内存栅栏或栅栏指令

内存屏障四大指令

在每一个volatile写操作前面插入一个StoreStore屏障

在每一个volatile写操作后面插入一个StoreLoad屏障

在每一个volatile读操作后面插入一个LoadLoad屏障

在每一个volatile读操作后面插入一个LoadStore屏障

《JUC构建高并发系统实战》学习笔记

volatile关键字系统底层

字节码层面

《JUC构建高并发系统实战》学习笔记

关键字

《JUC构建高并发系统实战》学习笔记

volatile可见性

《JUC构建高并发系统实战》学习笔记

volatile禁重排

写指令

《JUC构建高并发系统实战》学习笔记

读指令

《JUC构建高并发系统实战》学习笔记

对比Lock理解

《JUC构建高并发系统实战》学习笔记

一句话总结

《JUC构建高并发系统实战》学习笔记

CAS

《JUC构建高并发系统实战》学习笔记

没有CSA之前

多线程环境下保证线程安全

《JUC构建高并发系统实战》学习笔记

什么是CAS

《JUC构建高并发系统实战》学习笔记

说明和原理

《JUC构建高并发系统实战》学习笔记

硬件级别的保证

《JUC构建高并发系统实战》学习笔记

CASDemo代码

《JUC构建高并发系统实战》学习笔记

compareAndSet源码

《JUC构建高并发系统实战》学习笔记

原子引用

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

手写自旋锁

手写自旋锁

《JUC构建高并发系统实战》学习笔记

什么是自旋锁

《JUC构建高并发系统实战》学习笔记

ABA问题

《JUC构建高并发系统实战》学习笔记

CAS的缺点:CPU空转

《JUC构建高并发系统实战》学习笔记

ABA问题的产生

《JUC构建高并发系统实战》学习笔记

代码:存在ABA问题

《JUC构建高并发系统实战》学习笔记

代码:解决ABA问题

带邮戳的原子引用:AtomicStampedReference

atomicStampedReference.compareAndSet(x,y,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

使用:

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

原子操作类

红色和黑色的有什么区别?

为什么要用 LongAdder 替代 AtomicLong?

《JUC构建高并发系统实战》学习笔记

基本类型原子类

countDownLatch 你用在哪里?

《JUC构建高并发系统实战》学习笔记

class MyNumber{
    @Getter
    private AtomicInteger atomicInteger = new AtomicInteger();
    public void addPlusPlus(){
        atomicInteger.incrementAndGet();
    }
}


public class AtomicIntegerDemo{
    public static void main(String[] args) throws InterruptedException{
        
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(100);

        for (int i = 1; i <=100; i++) {
            new Thread(() -> {
                try{
                    for (int j = 1; j <=5000; j++){
                        myNumber.addPlusPlus();
                    }
                }finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//不加此行代码的话,由于main线程太快,main结束时,atomicInteger.incrementAndGet()还没算到 500000
        System.out.println(myNumber.getAtomicInteger().get());
    }
}

数组类型原子类

《JUC构建高并发系统实战》学习笔记

public class AtomicIntegerArrayDemo{
    public static void main(String[] args){
        
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});

        for (int i = 0; i <atomicIntegerArray.length(); i++) {
            System.out.println(atomicIntegerArray.get(i));
        }
        System.out.println();
        System.out.println();
        System.out.println();
        int tmpInt = 0;

        tmpInt = atomicIntegerArray.getAndSet(0,1122);
        System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
        atomicIntegerArray.getAndIncrement(1);
        atomicIntegerArray.getAndIncrement(1);
        tmpInt = atomicIntegerArray.getAndIncrement(1);
        System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
    }
}

引用类型原子类

《JUC构建高并发系统实战》学习笔记

AtomicReference

@Getter
@ToString
@AllArgsConstructor
class User{
    String userName;
    int    age;
}


public class AtomicReferenceDemo{
    public static void main(String[] args){
        User z3 = new User("z3",24);
        User li4 = new User("li4",26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
    }
}

手写自旋锁

package com.atguigu.Interview.study.thread;

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

/**
 * @auther zzyy
 * @create 2018-12-28 17:57
 * 题目:实现一个自旋锁
 * 自旋锁好处:循环比较获取没有类似wait的阻塞。
 *
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现
 * 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到。
 */
public class SpinLockDemo{
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t 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 myUnLock over");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            //暂停一会儿线程
            try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"A").start();
        //暂停一会儿线程,保证A线程先于B线程启动并完成
        try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"B").start();

    }
}

AtomicStampedReference

解决ABA问题

public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args)
    {
        abaProblem();
        abaResolve();
    }

    public static void abaResolve()
    {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t3 ----第1次stamp  "+stamp);
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            System.out.println("t3 ----第2次stamp  "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("t3 ----第3次stamp  "+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t4 ----第1次stamp  "+stamp);
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }

    public static void abaProblem()
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            atomicInteger.compareAndSet(100,20210308);
            System.out.println(atomicInteger.get());
        },"t2").start();
    }
}

AtomicMarkableReference

不建议用来解决ABA问题

用来解决一次性问题,不适合重复使用

package com.atguigu.juc.senior.inner.atomic;

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

/**
 * @auther zzyy
 * @create 2020-05-23 10:56
 */
public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
    static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);

    public static void main(String[] args)
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
            System.out.println(Thread.currentThread().getName()+"\t"+"update ok");
        },"t1").start();

        new Thread(() -> {
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicInteger.compareAndSet(100,2020);
        },"t2").start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(atomicInteger.get());

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("============以下是ABA问题的解决,让我们知道引用变量中途被更改了几次=========================");
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+stampedReference.getStamp());
            //故意暂停200毫秒,让后面的t4线程拿到和t3一样的版本号
            try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+stampedReference.getStamp());
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+stampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t =======1次版本号"+stamp);
            //暂停2秒钟,让t3先完成ABA操作了,看看自己还能否修改
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t=======2次版本号"+stampedReference.getStamp()+"\t"+stampedReference.getReference());
        },"t4").start();

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================");

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100,101,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+markableReference.isMarked());
            markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+markableReference.isMarked());
        },"t5").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
            //暂停几秒钟线程
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100,2020,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
        },"t6").start();
    }
}

对象的属性修改原子类

以一种线程安全的方式操作非线程安全对象内的某些字段

《JUC构建高并发系统实战》学习笔记

AtomicIntegerFieldUpdater

class BankAccount{
    private String bankName = "CCB";//银行
    public volatile int money = 0;//钱数
    AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");

    //不加锁+性能高,局部微创
    public void transferMoney(BankAccount bankAccount){
        accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
    }
}

/**
 * @auther zzyy
 * @create 2020-07-14 18:06
 * 以一种线程安全的方式操作非线程安全对象的某些字段。
 * 需求:
 * 1000个人同时向一个账号转账一元钱,那么累计应该增加1000元,
 * 除了synchronized和CAS,还可以使用AtomicIntegerFieldUpdater来实现。
 */
public class AtomicIntegerFieldUpdaterDemo{

    public static void main(String[] args){
        
        BankAccount bankAccount = new BankAccount();

        for (int i = 1; i <=1000; i++) {
            int finalI = i;
            new Thread(() -> {
                bankAccount.transferMoney(bankAccount);
            },String.valueOf(i)).start();
        }

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(bankAccount.money);
    }
}

AtomicReferenceField

class MyVar{
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = 	AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");

    public void init(MyVar myVar){
        if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE)){
            System.out.println(Thread.currentThread().getName()+"\t"+"---init.....");
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over");
        }else{
            System.out.println(Thread.currentThread().getName()+"\t"+"------其它线程正在初始化");
        }
    }
}


/**
 * @auther zzyy
 * @create 2021-03-18 17:20
 * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
 */
public class AtomicIntegerFieldUpdaterDemo{
    public static void main(String[] args) throws InterruptedException{
        MyVar myVar = new MyVar();

        for (int i = 1; i <=5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            },String.valueOf(i)).start();
        }
    }
}

原子操作增强类原理深度解析

《JUC构建高并发系统实战》学习笔记

LongAdder

基本使用

阿里要命题目

《JUC构建高并发系统实战》学习笔记

常用API

《JUC构建高并发系统实战》学习笔记

使用案例

LongAccumulator提供了自定义的函数操作

long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等

public class LongAccumulatorDemo
{

    LongAdder longAdder = new LongAdder();
    public void add_LongAdder()
    {
        longAdder.increment();
    }

    //LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y,0);
    LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
    {
        @Override
        public long applyAsLong(long left, long right)
        {
            return left - right;
        }
    },777);

    public void add_LongAccumulator()
    {
        longAccumulator.accumulate(1);
    }

    public static void main(String[] args)
    {
        LongAccumulatorDemo demo = new LongAccumulatorDemo();

        demo.add_LongAccumulator();
        demo.add_LongAccumulator();
        System.out.println(demo.longAccumulator.longValue());
    }
}

LongAdderAPIDemo

 
package com.atguigu.juc.atomics;

import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

/**
 * @auther zzyy
 * @create 2021-03-19 15:59
 */
public class LongAdderAPIDemo
{
    public static void main(String[] args)
    {
        LongAdder longAdder = new LongAdder();

        longAdder.increment();
        longAdder.increment();
        longAdder.increment();

        System.out.println(longAdder.longValue());

        LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2);

        longAccumulator.accumulate(1);
        longAccumulator.accumulate(2);
        longAccumulator.accumulate(3);

        System.out.println(longAccumulator.longValue());

    }
}

性能测试

1、定义五个方法

《JUC构建高并发系统实战》学习笔记

2、定义测试方法

《JUC构建高并发系统实战》学习笔记

3、测试结果

《JUC构建高并发系统实战》学习笔记

LongAdder为什么快

LongAdder源码分析

太难了,暂时未看,等待后期补充【2021-12-31】

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

Unsafe类

《JUC构建高并发系统实战》学习笔记

什么是UnSafe类

《JUC构建高并发系统实战》学习笔记

原子整形是否安全

《JUC构建高并发系统实战》学习笔记

getAndIncrement源码

《JUC构建高并发系统实战》学习笔记

底层汇编

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

ThreadLocal

Threadlocal简介

《JUC构建高并发系统实战》学习笔记

是什么/能干啥/API

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法〉都有自己的、独立初始化的变量副本。

ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来

实现每一个线程都有自己专属的本地变量副本(自己用自己的变量不麻烦别人,不和其他人共享,人人有份,人各一份),

主要解决了让每个线程绑定自己的值,通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

案例:买房提成

ThreadLocal初始化方法

《JUC构建高并发系统实战》学习笔记

买房提成案例

《JUC构建高并发系统实战》学习笔记

存在的问题

必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,

如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。

尽量在代理中使用try-finally块进行回收。

《JUC构建高并发系统实战》学习笔记

从阿里ThreadLocal规范开始

《JUC构建高并发系统实战》学习笔记

非线程安全的SimpleDateFormat

官方文档

《JUC构建高并发系统实战》学习笔记

错误的代码演示

《JUC构建高并发系统实战》学习笔记

出现的问题

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

源码分析结论

《JUC构建高并发系统实战》学习笔记

解决方案

方案1:

《JUC构建高并发系统实战》学习笔记

        for (int i = 1; i <=30; i++) {
            new Thread(() -> {
                try {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//每次用到的时候new
                    System.out.println(sdf.parse("2020-11-11 11:11:11"));
                    sdf = null;//帮助GC回收
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

方案2:

《JUC构建高并发系统实战》学习笔记

代码

    private static final ThreadLocal<SimpleDateFormat>  sdf_threadLocal =
            ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

《JUC构建高并发系统实战》学习笔记

次解决方案来自阿里推荐

《JUC构建高并发系统实战》学习笔记

其他方案:

《JUC构建高并发系统实战》学习笔记

代码

《JUC构建高并发系统实战》学习笔记

DataUtils

package com.atguigu.juc.senior.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @auther zzyy
 * @create 2020-05-03 10:14
 */
public class DateUtils
{
    /*
    1   SimpleDateFormat如果多线程共用是线程不安全的类
    public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String format(Date date)
    {
        return SIMPLE_DATE_FORMAT.format(date);
    }

    public static Date parse(String datetime) throws ParseException
    {
        return SIMPLE_DATE_FORMAT.parse(datetime);
    }*/

    //2   ThreadLocal可以确保每个线程都可以得到各自单独的一个SimpleDateFormat的对象,那么自然也就不存在竞争问题了。
    public static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static String format(Date date)
    {
        return SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().format(date);
    }

    public static Date parse(String datetime) throws ParseException
    {
        return SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().parse(datetime);
    }


    //3 DateTimeFormatter 代替 SimpleDateFormat
    /*public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String format(LocalDateTime localDateTime)
    {
        return DATE_TIME_FORMAT.format(localDateTime);
    }

    public static LocalDateTime parse(String dateString)
    {

        return LocalDateTime.parse(dateString,DATE_TIME_FORMAT);
    }*/
}

家庭作业

来自:阿里手册

《JUC构建高并发系统实战》学习笔记

    //3 DateTimeFormatter 代替 SimpleDateFormat
    /*public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String format(LocalDateTime localDateTime)
    {
        return DATE_TIME_FORMAT.format(localDateTime);
    }

    public static LocalDateTime parse(String dateString)
    {

        return LocalDateTime.parse(dateString,DATE_TIME_FORMAT);
    }*/

ThreadLocal源码分析

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

ThreadLocalMap

threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象。

当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放

《JUC构建高并发系统实战》学习笔记

ThreadLocal内存泄漏

《JUC构建高并发系统实战》学习笔记

存在的问题:

线程池环境下,线程经常会被复用

《JUC构建高并发系统实战》学习笔记

什么是内存泄漏

不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

《JUC构建高并发系统实战》学习笔记

再看ThreadLocalMap

ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以它为Key)不过是经过了两层包装的ThreadLocal对象:

(1)第一层包装是使用WeakReference<ThreadLoca<?>> 将ThreadLocal对象变成一个弱引用的对象;

(2)第二层包装是定义了一个专门的类Entry扩展WeakReference<ThreadLocal<?>>

《JUC构建高并发系统实战》学习笔记

整体架构

《JUC构建高并发系统实战》学习笔记

强软弱虚四大引用

当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。

强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null
一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。

 public static void strongReference(){
    MyObject myObject = new MyObject();
    System.out.println("-----gc before: "+myObject);

    myObject = null;
    System.gc();
    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

    System.out.println("-----gc after: "+myObject);
}

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。

对于只有软引用的对象来说,

系统内存充足时它 不会 被回收,

系统内存不足时它 会 被回收。

软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

class MyObject{
    //一般开发中不用调用这个方法,本次只是为了讲课演示
    @Override
    protected void finalize() throws Throwable{
        System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked....");
    }
}

/**
 * @auther zzyy
 * @create 2021-03-24 10:31
 */
public class ReferenceDemo{
    public static void main(String[] args){
        //当我们内存不够用的时候,soft会被回收的情况,设置我们的内存大小:-Xms10m -Xmx10m
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after内存够用: "+softReference.get());

        try
        {
            byte[] bytes = new byte[9 * 1024 * 1024];
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after内存不够: "+softReference.get());
        }
    }

    public static void strongReference(){
        MyObject myObject = new MyObject();
        System.out.println("-----gc before: "+myObject);

        myObject = null;
        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after: "+myObject);
    }
}

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

class MyObject
{
    //一般开发中不用调用这个方法,本次只是为了讲课演示
    @Override
    protected void finalize() throws Throwable
    {
        System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked....");
    }
}

/**
 * @auther zzyy
 * @create 2021-03-24 10:31
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("-----gc before内存够用: "+weakReference.get());

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

        System.out.println("-----gc after内存够用: "+weakReference.get());
    }

    public static void softReference()
    {
        //当我们内存不够用的时候,soft会被回收的情况,设置我们的内存大小:-Xms10m -Xmx10m
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after内存够用: "+softReference.get());

        try
        {
            byte[] bytes = new byte[9 * 1024 * 1024];
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after内存不够: "+softReference.get());
        }
    }

    public static void strongReference()
    {
        MyObject myObject = new MyObject();
        System.out.println("-----gc before: "+myObject);

        myObject = null;
        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after: "+myObject);
    }
}

《JUC构建高并发系统实战》学习笔记


虚引用需要java.lang.ref.PhantomReference类来实现。

顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,
不能单独使用不能通过它访问对象,虚引用必须和引用队列 (ReferenceQueue)联合使用。

虚引用的主要作用是跟踪对象被垃圾回收的状态。 仅仅是提供了一种确保对象被 finalize以后,做某些事情的机制。 PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。

其意义在于:说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。

换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。

《JUC构建高并发系统实战》学习笔记

class MyObject
{
    //一般开发中不用调用这个方法,本次只是为了讲课演示
    @Override
    protected void finalize() throws Throwable
    {
        System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked....");
    }
}

/**
 * @auther zzyy
 * @create 2021-03-24 10:31
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(new MyObject(),referenceQueue);
        //System.out.println(phantomReference.get());

        List<byte[]> list = new ArrayList<>();

        new Thread(() -> {
            while (true)
            {
                list.add(new byte[1 * 1024 * 1024]);
                try { TimeUnit.MILLISECONDS.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(phantomReference.get());
            }
        },"t1").start();

        new Thread(() -> {
            while (true)
            {
                Reference<? extends MyObject> reference = referenceQueue.poll();
                if (reference != null) {
                    System.out.println("***********有虚对象加入队列了");
                }
            }
        },"t2").start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
    }

    public static void weakReference()
    {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("-----gc before内存够用: "+weakReference.get());

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

        System.out.println("-----gc after内存够用: "+weakReference.get());
    }

    public static void softReference()
    {
        //当我们内存不够用的时候,soft会被回收的情况,设置我们的内存大小:-Xms10m -Xmx10m
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after内存够用: "+softReference.get());

        try
        {
            byte[] bytes = new byte[9 * 1024 * 1024];
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after内存不够: "+softReference.get());
        }
    }

    public static void strongReference()
    {
        MyObject myObject = new MyObject();
        System.out.println("-----gc before: "+myObject);

        myObject = null;
        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after: "+myObject);
    }
}

《JUC构建高并发系统实战》学习笔记

为什么要用弱引用

《JUC构建高并发系统实战》学习笔记


《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记


《JUC构建高并发系统实战》学习笔记

结论:

从前面的set,getEntry,remove方法看出,在threadLocal的生命周期里,针对threadLocal存在的内存泄漏的问题,

都会通过expungeStaleEntry,cleanSomeSlots,replaceStaleEntry这三个方法清理掉key为null的脏entry。

弱引用结论

《JUC构建高并发系统实战》学习笔记

最佳实践

《JUC构建高并发系统实战》学习笔记

Java对象内存布局和对象头

面试题反馈

《JUC构建高并发系统实战》学习笔记

Object object = new Object()

谈谈你对这句话的理解?一般而言JDK8按照默认情况下,new一个对象占多少内存空间

对象的内存布局

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:

一个对象实例:对象头(Header)、实例数据( Instance Daa)和对齐填充(Padding)

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

对象头

对象头= 对象标记(MarkWord)+类元信息(类型指针)

MarkWord被设计成一个非固定的数据结构

以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

默认存储:对象的HashCode、分代年龄和锁标志位等信息。

《JUC构建高并发系统实战》学习笔记

对象标记(MarkWord)

它保存什么?

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

默认存储对象的HashCode、分代年龄和锁标志位等信息。

这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构

以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

类元信息(类型指针)

对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

参考宋红康老师JVM课程中的原图

《JUC构建高并发系统实战》学习笔记

对象头多大

在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。

实例数据

存放类的属性(Field)数据信息,包括父类的属性信息,

如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

《JUC构建高并发系统实战》学习笔记

class MyObject{
	int i=5;
	char a='a';
}

MyObjec对象头 16个字节:MarkWord 8个字节 + 类型指针 8个字节
int类型:32bit 3个字节
char类型:16bit 2个字节
此时一共:16+3+2=21个字节
属性填充:3个字节
一共:24个字节

对齐填充

虚拟机要求对象起始地址必须是8字节的整数倍

填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐

对象头的MarkWord

官网理论

《JUC构建高并发系统实战》学习笔记

再说对象头MarkWord

《JUC构建高并发系统实战》学习笔记

32位虚拟机

《JUC构建高并发系统实战》学习笔记

64位虚拟机

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

64位虚拟机源码

《JUC构建高并发系统实战》学习笔记

对象布局说明和压缩指针

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

JOL使用案例

《JUC构建高并发系统实战》学习笔记

new Object代码说明

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

换个对象试试

《JUC构建高并发系统实战》学习笔记

修改GC年龄

GC年龄采用4位bit存储,最大值为15,使用参数修改大于15时,会报错,无法启动JVM

《JUC构建高并发系统实战》学习笔记

自动压缩

默认开启自动压缩

使用 打印JVM参数

《JUC构建高并发系统实战》学习笔记

关闭自动压缩

《JUC构建高并发系统实战》学习笔记

Synchronized锁升级

《JUC构建高并发系统实战》学习笔记

面试题引入

【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

说明︰尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

Synchronized的性能变化

《JUC构建高并发系统实战》学习笔记

为什么每一个对象都可以成为一个锁

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

Monitor(监视器锁)

《JUC构建高并发系统实战》学习笔记

Synchronized锁优化背景

《JUC构建高并发系统实战》学习笔记

用户态和内核态

《JUC构建高并发系统实战》学习笔记

锁升级过程

synchronized用的锁是存在Java对象头里的Mark Word中,锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位

《JUC构建高并发系统实战》学习笔记

锁升级详解

无锁

无锁案例

《JUC构建高并发系统实战》学习笔记

hashcode有调用才会存储

《JUC构建高并发系统实战》学习笔记

16进制下的hashcode

《JUC构建高并发系统实战》学习笔记

2进制下的hashcode

《JUC构建高并发系统实战》学习笔记

总结:程序不会有锁的竞争

《JUC构建高并发系统实战》学习笔记

偏向锁

什么是偏向锁:

当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁

为什么会有偏向锁:

Hotspot的作者经过研究发现,大多数情况下:多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能

过程说明:

那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。

如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。

升级为轻量级锁:

假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。

偏向锁的撤销

竞争线程尝试CAS更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁。

《JUC构建高并发系统实战》学习笔记

主要作用

《JUC构建高并发系统实战》学习笔记

64位标记图再看

《JUC构建高并发系统实战》学习笔记

偏向锁的持有说明

《JUC构建高并发系统实战》学习笔记

偏向锁的举例说明

《JUC构建高并发系统实战》学习笔记

偏向锁JVM命令

《JUC构建高并发系统实战》学习笔记

偏向锁代码演示【无效果】

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

偏向锁代码演示【有效果】

《JUC构建高并发系统实战》学习笔记

偏向锁的撤销

《JUC构建高并发系统实战》学习笔记

偏向锁获取撤销流程

《JUC构建高并发系统实战》学习笔记

轻量级锁

《JUC构建高并发系统实战》学习笔记

64位标记图

《JUC构建高并发系统实战》学习笔记

轻量级锁的获取

《JUC构建高并发系统实战》学习笔记

代码演示

《JUC构建高并发系统实战》学习笔记

自旋到一定次数

《JUC构建高并发系统实战》学习笔记

轻量级锁与偏向锁的区别和不同

《JUC构建高并发系统实战》学习笔记

重量级锁

《JUC构建高并发系统实战》学习笔记

锁标志位

《JUC构建高并发系统实战》学习笔记

代码演示

《JUC构建高并发系统实战》学习笔记

锁总结

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

锁消除和锁粗化

《JUC构建高并发系统实战》学习笔记

锁消除

《JUC构建高并发系统实战》学习笔记

锁粗化

《JUC构建高并发系统实战》学习笔记

AQS

是什么

AbstractQueuedSynchronizer

《JUC构建高并发系统实战》学习笔记

通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态

《JUC构建高并发系统实战》学习笔记

AQS相关

《JUC构建高并发系统实战》学习笔记

能干嘛

加锁会导致阻塞 => 有阻塞就需要排队,实现排队必然需要队列

《JUC构建高并发系统实战》学习笔记

能干嘛:解释说明

《JUC构建高并发系统实战》学习笔记

AQS内部体系结构

《JUC构建高并发系统实战》学习笔记

官网解释

《JUC构建高并发系统实战》学习笔记

AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。

《JUC构建高并发系统实战》学习笔记

ReentrantReadWriteLock

《JUC构建高并发系统实战》学习笔记

《JUC构建高并发系统实战》学习笔记

面试题引入

《JUC构建高并发系统实战》学习笔记

锁得演变过程

《JUC构建高并发系统实战》学习笔记

什么是读写锁

《JUC构建高并发系统实战》学习笔记

锁降级说明

《JUC构建高并发系统实战》学习笔记

读锁和写锁互斥

《JUC构建高并发系统实战》学习笔记

锁降级

《JUC构建高并发系统实战》学习笔记

锁降级说明

《JUC构建高并发系统实战》学习笔记

Java8官网说明

《JUC构建高并发系统实战》学习笔记

可以降级

《JUC构建高并发系统实战》学习笔记

不可锁升级

《JUC构建高并发系统实战》学习笔记

读写锁降级演示

《JUC构建高并发系统实战》学习笔记

锁可以降级,程序正常执行

《JUC构建高并发系统实战》学习笔记

程序不能正常执行,会卡住,写锁没有释放,无法获取读锁

《JUC构建高并发系统实战》学习笔记

持有写锁后获取读锁

《JUC构建高并发系统实战》学习笔记

Oracle源码之锁降级

《JUC构建高并发系统实战》学习笔记

StampedLock邮戳锁

什么是StampedLock

《JUC构建高并发系统实战》学习笔记

什么是锁饥饿问题

《JUC构建高并发系统实战》学习笔记

怎么缓解锁饥饿问题

《JUC构建高并发系统实战》学习笔记

StampedLock乐观读锁

《JUC构建高并发系统实战》学习笔记

StampedLock的特点

《JUC构建高并发系统实战》学习笔记

StampedLock三种访问模式

《JUC构建高并发系统实战》学习笔记

三种访问模式代码演示

《JUC构建高并发系统实战》学习笔记

StampedLock的缺点

StampedLock可能会导致死锁,建议不要再工作中使用,仅为了应付面试

《JUC构建高并发系统实战》学习笔记

结课总结

《JUC构建高并发系统实战》学习笔记

上一篇:JUC并发编程 -- 自定义线程池(阻塞队列)


下一篇:JAVA常用工具JUC