JUC并发编程

1、什么是JUC

JUC 是 java.util.concurrent 包名的简写,是关于并发编程的API,与 JUC 相关的有三个包:

  • java.util.concurrent、
  • java.util.concurrent.atomic、
  • java.util.concurrent.locks。

1.1、线程和进程

进程是一个程序的集合,如:QQ.exe,进程往往包含多个线程(至少包含一个)。

  1. Java 默认有几个线程?

2个,main 线程和 GC 线程

  1. Java 真的可以开启线程吗?

通过源码可以分析出 Java 不能开启线程,需要通过本地方法调用底层 C++ 实现。 同理 Java 无法直接操作硬件,需要通过本地方法实现。

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();
  1. 并发与并行

并发:多个线程操作同一个资源,CPU一核,模拟出来多线程,基于CPU时间片快速调度

并行:多个人一起行走,CPU多核,多个线程可以同时执行

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

package com.liuxiang;

public class Test {
    public static void main(String[] args) {
        //在Java中可以通过此方法 查看CPU核心数量
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println(i);
    }
}
8

1.2、多线程

  • 线程六大状态

阅读源码可知,线程有以下六大状态:

public enum State {
    //新生
    NEW,

    //运行
    RUNNABLE,

    //阻塞
    BLOCKED,

    //等待
    WAITING,

    //超时等待
    TIMED_WAITING,

    //死亡
    TERMINATED;
}
  • wait和sleep的区别
  1. 来自不同的类:wait 来自 Object 类,sleep 来自线程类
  2. 关于锁的释放 :wait 会释放锁,sleep 不会释放锁
  3. 使用范围不同:wait 必须在同步代码块中使用,sleep 任何地方都可以睡眠
  4. 是否需要捕获异常:wait 不需要捕获异常,sleep 必须捕获异常
Thread.sleep(1000); //睡眠1秒
//在实际工作中,不会使用sleep让线程睡眠
//而使用TimeUnit让线程睡眠
TimeUnit.DAYS.sleep(1);//睡眠一天
TimeUnit.SECONDS.sleep(2);//睡眠两秒

2、锁

2.1、synchronized

synchronized 是解决线程不安全的关键,它的实现原理就是队列和锁

由于我们可以通过 private 关键词来保证数据变量(对象)只能被方法访问,所以我们只需要针对方法提出一套机制。这套机制就是 synchronized 关键字。

synchronized 方法控制对对象的访问,每个对象对应一把锁,每一个 synchronized 方法都必须获得调用该方法对象的锁才能执行,否则会线程阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续往下执行。

缺陷:如果一个大的方法被申明 synchronized 将会影响效率。

  • 使用方法
  1. synchronized 同步方法
//同步方法
public synchronized void method(int ages){}

synchronized 方法锁的是对象本身 this,谁获得这个对象的锁才能执行这个方法。

如果锁的是静态方法,则锁住的是 class 模板类(唯一)

  1. synchronized 同步代码块
//synchronized同步代码块
synchronized(obj){}

同步块锁的是 obj,obj 称为同步监视器

他可以是任何对象,但是推荐使用共享资源对象

同步方法种无需指定同步监视器,因为同步方法的同步监视器就是 this 就是这个对象的本身

2.2、Lock锁

Lock 实现提供比使用 synchronized 方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象 Condition

Lock 默认有三个实现类:

  1. 从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock 对象充当。
  2. java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
  3. ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock(可重复锁),可以显式加锁、释放锁。

ReentrantLock() 源码:

  1. 公平锁:先来后到,不可插队
  2. 非公平锁:可插队(默认)
package com.liuxiang;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 测试JUC Lock的使用
 * @author 86187 
 */
public class TestLock {
public static void main(String[] args) {
    Tikect tikect = new Tikect();
    new Thread(() -> {
        for (int i = 0; i < 20; i++) tikect.numTest();
    }).start();
    new Thread(() -> {
        for (int i = 0; i < 20; i++) tikect.numTest();
    }).start();
    new Thread(() -> {
        for (int i = 0; i < 20; i++) tikect.numTest();
    }).start();
}
}

 class Tikect {

    private Integer num = 20;

    //通过显示声明锁 进行手动加锁 和解锁
    private Lock lock = new ReentrantLock();

    public void numTest() {
        lock.lock();//加锁
        try {
            if (num > 0) {
                num--;
                System.out.println(num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }

    }
}
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0

2.3、synchronized与Lock的区别

  1. Synchronized 是内置的 Java 关键字,Lock 是一个 Java 类
  2. Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  3. Synchronized 会自动释放锁,lock必须要手动释放锁,如果不释放锁,可能造成死锁
  4. Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等),Lock 锁就不一定会等待下去
  5. Synchronized 可重入锁,不可以中断的,非公平,Lock 可重入锁,可以判断锁,非公平(可以自己设置)
  6. Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码

2.4、ReadWriteLock读写锁

ReadWriteLock 只有唯一的一个实现类 ReentrantReadWriteLock:

package com.liuxiang;

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

/**
 * 测试ReadWriteLock读写锁
 * @author 86187 
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        myCacheLock myCacheLock = new myCacheLock();
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCacheLock.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCacheLock.get(temp+"");
            },String.valueOf(i)).start();
        }
    }

}
//自定义缓存
class myCacheLock{

    private volatile Map<String,Object> map = new HashMap<>();
    //读写锁
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    //存的时候只有一个线程 存 取的时候可以多个线程取

    //存
    public void put(String key,Object value){
        lock.writeLock().lock();//写锁
        try {
            //业务代码
            System.out.println(Thread.currentThread().getName()+"号线程写入"+value);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"号线程写入成功");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();//释放锁
        }
    }

    //取
    public void get(String key){
        lock.readLock().lock();//读锁
        try {
            //业务代码
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"号线程读取"+o);
            System.out.println(Thread.currentThread().getName()+"号线程读取成功");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
}
1号线程写入1
1号线程写入成功
3号线程写入3
3号线程写入成功
4号线程写入4
4号线程写入成功
2号线程写入2
2号线程写入成功
5号线程写入5
5号线程写入成功
1号线程读取1
1号线程读取成功
5号线程读取5
5号线程读取成功
2号线程读取2
2号线程读取成功
4号线程读取4
3号线程读取3
3号线程读取成功
4号线程读取成功

3、生产者与消费者

3.1、传统synchronized版

package com.liuxiang;

/**
 * 使用 synchronized 测试生产者消费者问题
 * @author 86187
 */
public class TestPC {
    public static void main(String[] args) {
        Tikect tikect = new Tikect();

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

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

class Tikect{

    private int num =0;

    public synchronized void increment() throws InterruptedException {
        if (num!=0) {
            this.wait();//睡眠
        }
        System.out.println(Thread.currentThread().getName()+"==>"+(++num));
        this.notifyAll();//唤醒睡眠的线程
    }

    public synchronized void decrement() throws InterruptedException {
        if (num==0) {
            this.wait();
        }
        System.out.println(Thread.currentThread().getName()+"==>"+(--num));
        this.notifyAll();
    }
}
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0

如果只是两个线程之间进行通信是没有问题的,如果多个线程进行通信就会产生虚假唤醒问题(因为if()只会判断一次)。

因此我们应该根据官方文档,改成 while() 循环判断,来防止虚假唤醒:

package com.liuxiang;

/**
 * 使用 synchronized 测试生产者消费者问题
 * @author 86187
 */
public class TestPC {
    public static void main(String[] args) {
        Tikect tikect = new Tikect();

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

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

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

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

class Tikect{

    private int num =0;

    public synchronized void increment() throws InterruptedException {
        while (num!=0) {
            this.wait();//睡眠
        }
        System.out.println(Thread.currentThread().getName()+"==>"+(++num));
        this.notifyAll();//唤醒睡眠的线程
    }

    public synchronized void decrement() throws InterruptedException {
        while (num==0) {
            this.wait();
        }
        System.out.println(Thread.currentThread().getName()+"==>"+(--num));
        this.notifyAll();
    }
}
A==>1
B==>0
C==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
D==>0
A==>1
D==>0
C==>1
D==>0

3.2、Lock版

通过 Condition 替代了 Object 中睡眠唤醒方法:

通过 lock 找到 Condition:

package com.liuxiang;

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

/**
 * 使用 Lock 测试生产者消费者问题
 * @author 86187
 */
public class TestPC {
    public static void main(String[] args) {
        Tikect tikect = new Tikect();

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

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

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

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

class Tikect{

    private int num =0;

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

    public void increment() throws InterruptedException  {
        lock.lock();
        try{
            //防止虚假唤醒 使用while判断
            while (num!=0) {
                condition.await();//睡眠
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"==>"+num);
            condition.signalAll();//唤醒睡眠的线程
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try{
            while (num==0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"==>"+num);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}
A==>1
B==>0
A==>1
B==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0

3.3、Condition精准唤醒

package com.liuxiang;

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

/**
 * Condition实现精准唤醒
 * @author 86187 
 */
public class TestCondition {
    // A 执行完 唤醒B B执行完 唤醒C 顺序唤醒
    public static void main(String[] args) {
        Date date = new Date();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> { date.prinltTest1(); },"A").start();
            new Thread(() -> { date.prinltTest2(); },"B").start();
            new Thread(() -> { date.prinltTest3(); },"C").start();
        }
    }
}
class Date{

    private Lock lock = new ReentrantLock();
    //准备三个监视器
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    private int number = 1;
    //使用同一把锁进行加锁
    public void prinltTest1(){
        lock.lock();
        try {
            // 判断是否执行-> 执行具体业务 -> 唤醒其他线程
            while (number!=1){
                condition1.await();//睡眠
            }
            //业务
            number = 2;
            System.out.println("A执行了");
            //唤醒B
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void prinltTest2(){
        lock.lock();
        try {
            // 判断是否执行-> 执行具体业务 -> 唤醒其他线程
            while (number!=2){
                condition2.await();//睡眠
            }
            //业务
            number = 3;
            System.out.println("B执行了");
            //唤醒C
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void prinltTest3(){
        lock.lock();
        try {
            // 判断是否执行-> 执行具体业务 -> 唤醒其他线程
            while (number!=3){
                condition3.await();//睡眠
            }
            //业务
            number = 1;
            System.out.println("C执行了");
            //唤醒A
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了

实现精准唤醒:需要准备多个监视器,通过 condition1.signal(); 唤醒指定的监视器。

4、集合类不安全

在多线程下操作集合容易出现 java.util.concurrentModificationException 并发修改异常!

4.1、CopyOnWriteArrayList

在多线程并发情况下 ArrayList 不再具有安全性,此时我们应该使用安全的 List:

  1. 使用线程安全的类:Vector,Vector 在底层源码中使用 synchronized 来进行修饰的,所以它是线程安全的

  1. 使用 Collections 工具类转化线程不安全的类
//Collections转化线程安全的类
List<String> list = Collections.synchronizedList(new ArrayList<>());
  1. 使用 JUC 下线程安全的类 CopyOnWriteArrayList
 List<Object> objects = new CopyOnWriteArrayList<>();
  • CopyOnWrite 的意思是:写入时复制 List,简称 COW,它是计算机程序设计领域的一种优化策略,多个线程调用时,List 读取的时候,固定的,产生写入(覆盖)

  • CopyOnWriteArrayList 比 Vector 好在哪里呢?

我们查看底层源码可知,在 CopyOnWriteArrayList 中使用的是高性能 lock 锁:

4.2、CopyOnWriteArraySet

  1. 与 List 相似,我们可以使用 Collections 工具类的方式来转换线程不安全的类
 Set<Object> set = Collections.synchronizedSet(new HashSet<>());
  1. 使用 JUC 下线程安全的类 CopyOnWriteArraySet
Set<Object> set1 = new CopyOnWriteArraySet<>();

4.3、ConcurrentHashMap

  1. 使用线程安全的类 HashTable
Map<Object, Object> objectHashtable = new Hashtable<>();

HashTable 和 HashMap 的实现原理几乎一样,差别:HashTable 不允许 key 和 value 为 null

HashTable 线程安全的策略实现代价却比较大,get/put 所有相关操作都是 synchronized 的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞。

  1. 与 List 相似,我们可以使用 Collections 工具类的方式来转换线程不安全的类
Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
  1. 使用 JUC 下线程安全的类 ConcurrentHashMap
Map<Object, Object> map = new ConcurrentHashMap<>();

5、Callable

Callable 与 Runnable 的区别:

  1. 有返回值
  2. 可以抛出异常
  3. 方法不同 call()/run()
package com.liuxiang;

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

public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //寻常 多线程是如下方式开启 ,但是在Thread中不能直接传入Callable
       /* new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();*/

        //那么如何执行Callable呢?
        //我们知道在Runnable的实现类FutureTask 构造方法中可以传入Callable 于是可以如下使用
        MyCall call = new MyCall();
        FutureTask<String> futureTask = new FutureTask<>(call);
        new Thread(futureTask).start();
        //如何获取结果呢?
        String s = futureTask.get();//使用get参数 获取获取结果
        System.out.println(s);

        //如果多条线程执行呢?
        new Thread(futureTask).start();
        new Thread(futureTask).start();
        //会发现方法打印只执行了一次
        //原因是Callable有缓存
    }
}

class MyCall implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("执行方法");
        //调用结果时可能会产生阻塞,因为方法内部逻辑的执行是一个耗时操作
        return "ok";
    }
}
执行方法
ok

6、常用辅助类

6.1、CountDownLatch

package com.liuxiang;

import java.util.concurrent.CountDownLatch;

/**
 * 测试CountDownLatch减法计数器
 * @author 86187
 */
public class TestCountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"号线程 Go out");
                count.countDown();//数量-1
            },String.valueOf(i)).start();
        }
        count.await();//等待计数器归零后才往下执行

        System.out.println("Close the door");
    }
}
1号线程 Go out
6号线程 Go out
5号线程 Go out
3号线程 Go out
4号线程 Go out
2号线程 Go out
Close the door

6.2、CyclicBarrier

CyclicBarrier 有两个构造方法,一个只用于计数,一个是计数完可以执行一个线程:

package com.liuxiang;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class TestCyclicBarrier {
    public static void main(String[] args) {
        //计数器加到7时 执行线程
        CyclicBarrier barrier = new CyclicBarrier(7,() -> {
            System.out.println("召唤神龙");
        });

        //创建7个线程
        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"号线程集齐第"+temp+"颗龙珠");

                try {
                    barrier.await();//线程等待 计数器+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
1号线程集齐第1颗龙珠
5号线程集齐第5颗龙珠
4号线程集齐第4颗龙珠
3号线程集齐第3颗龙珠
2号线程集齐第2颗龙珠
6号线程集齐第6颗龙珠
7号线程集齐第7颗龙珠
召唤神龙

注意点:如果无法计数到指定数量,那么线程会卡死,一直等待下去

6.3、Semaphore

package com.liuxiang;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class TestSemaphore {
    public static void main(String[] args) {
        //Semaphore 主要应用于限流
        Semaphore semaphore = new Semaphore(3);

        //Semaphore的位置只有3个 现在有6个线程 先拿到通行证的线程先执行 其他线程必须等待释放以后执行
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();//获取通行证
                    System.out.println(Thread.currentThread().getName()+"号抢到停车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("离开停车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}
1号抢到停车位
2号抢到停车位
3号抢到停车位
离开停车位
离开停车位
离开停车位
5号抢到停车位
4号抢到停车位
6号抢到停车位
离开停车位
离开停车位
离开停车位

7、阻塞队列

BlockingQueue 不是一个新东西,它是跟 List,Set 同级的集合框架:

7.1、四组API

  • 抛出异常测试
package com.liuxiang;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * 抛出异常测试
 * @author 86187
 */
public class TestBlockingQueue {
    public static void main(String[] args) {
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        System.out.println(b.add("a"));
        System.out.println(b.add("b"));
        System.out.println(b.add("c"));
        System.out.println("===============");
        //队列已满 如果继续放会报错 java.lang.IllegalStateException: Queue full 队列已满异常
        //b.add("d");

        System.out.println(b.element()); //查看队首元素
        System.out.println(b.remove()); //弹出元素
        System.out.println(b.remove());
        System.out.println(b.remove());

        //队列已空 如果继续取出 会报错 java.util.NoSuchElementException
        b.remove();
    }
}
true
true
true
===============
a
a
b
c
Exception in thread "main" java.util.NoSuchElementException
	at java.util.AbstractQueue.remove(AbstractQueue.java:117)
	at com.liuxiang.TestBlockingQueue.main(TestBlockingQueue.java:28)
  • 有返回值,不抛出异常测试
package com.liuxiang;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * 有返回值,不抛出异常测试
 * @author 86187
 */
public class TestBlockingQueue {
    public static void main(String[] args) {
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        System.out.println(b.offer("a"));
        System.out.println(b.offer("b"));
        System.out.println(b.offer("c"));
        //队列已满 继续放入 返回值为false 没有异常
        System.out.println(b.offer("d"));

        System.out.println(b.peek()); //查看队首元素
        System.out.println(b.poll()); //弹出元素
        System.out.println(b.poll());
        System.out.println(b.poll());
        //队列已空 如果继续取出 为null  没有异常
        System.out.println(b.poll());

    }
}
true
true
true
false
a
a
b
c
null
  • 阻塞等待测试
package com.liuxiang;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * 阻塞等待测试
 * @author 86187
 */
public class TestBlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        //等待 阻塞(一直阻塞)
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        b.put("a");
        b.put("a");
        b.put("a");
        b.put("a"); //队列已满 一直阻塞

        b.take();
        b.take();
        b.take();
        b.take(); //队列已空 一直阻塞
    }
}

  • 超时等待测试
package com.liuxiang;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 超时等待测试
 * @author 86187
 */
public class TestBlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        //等待 超时等待
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        System.out.println(b.offer("a"));
        System.out.println(b.offer("b"));
        System.out.println(b.offer("c"));
        System.out.println(b.offer("d", 2, TimeUnit.SECONDS));//超时等待两秒 两秒之后退出

        //取出
        System.out.println(b.poll(2, TimeUnit.SECONDS));
        System.out.println(b.poll(2, TimeUnit.SECONDS));
        System.out.println(b.poll(2, TimeUnit.SECONDS));
        System.out.println(b.poll(2, TimeUnit.SECONDS));//超时等待两秒 取不到值就退出
    }
}
true
true
true
false
a
b
c
null

7.2、同步队列SynchronousQueue

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

package com.liuxiang;

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

/**
 * 测试同步队列SynchronousQueue
 * @author 86187
 */
public class TestSynchronousQueue {
    public static void main(String[] args) {
        //同步队列
        SynchronousQueue<String> queue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println("存"+"a");
                queue.put("a");
                System.out.println("存"+"b");
                queue.put("b");
                System.out.println("存"+"c");
                queue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("取"+queue.take());
                System.out.println("取"+queue.take());
                System.out.println("取"+queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
存a
取a
存b
存c
取b
取c

8、线程池

线程池有哪些好处?

  1. 降低资源的消耗
  2. 提供响应速度
  3. 统一的管理(方便管理)

总结:可以控制最大并发数,管理线程

8.1、Executors三大方法

package com.liuxiang;

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

/**
 * Executors 工具类 3大方法
 * @author 86187
 */
public class TestPool {
    public static void main(String[] args) {
        //ExecutorService executor  = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService executor = Executors.newFixedThreadPool(5);//创建一个固定的线程
        //ExecutorService executor = Executors.newCachedThreadPool();//可伸缩的 遇强则强

        //使用线程池 不再用原来的方式创建线程 而是用executor.execute()
        try {
            for (int i = 0; i < 10; i++) {
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName()+"线程执行");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            executor.shutdown();//使用完一定要关闭线程池
        }

    }
}
pool-1-thread-1线程执行
pool-1-thread-5线程执行
pool-1-thread-1线程执行
pool-1-thread-4线程执行
pool-1-thread-2线程执行
pool-1-thread-3线程执行
pool-1-thread-2线程执行
pool-1-thread-4线程执行
pool-1-thread-1线程执行
pool-1-thread-5线程执行

8.2、线程池七大参数

查看源码可以发现,上面创建线程池的三大方法都是调用了一个 ThreadPoolExecutor 的方法:

//单个线程
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>());
}

//线程池七大参数
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                          int maximumPoolSize,//最大线程池大小
                          long keepAliveTime,//存活时间,超时了没人调用就会释放
                          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;
}

在阿里云开发手册中有这样一句话:

8.3、四种拒绝策略

超过最大承载数时启动拒绝策略:

  1. AbortPolicy():不处理新的任务,直接抛出异常
  2. CallerRunsPolicy():哪来的回哪里去,如果是 main 线程传递过来的,让它回 main 线程处理去
  3. DiscardPolicy():队列满了,不会抛出异常,丢掉任务
  4. DiscardOldestPolicy():不会抛出异常,尝试跟最早的竞争,竞争失败也会丢掉任务

8.4、ThreadPoolExecutor创建线程池

  1. 线程池的最大承载数:阻塞队列数+最大线程数

  2. 最大线程数到底应该如何定义?

    • CPU密集型:有几核CPU就定义几,可以保证CPU效率最高

      //获取CPU核心数 确保再不同的电脑上运行
      Runtime.getRuntime().availableProcessors();
      
    • IO密集型:判断程序中,非常耗费 IO 的线程数,大于这个 IO 数(一般两倍)

package com.liuxiang;

import java.util.concurrent.*;

/**
 * 使用七大参数自定义线程池
 * @author 86187
 */
public class TestThreadPoolExecutor {
    public static void main(String[] args) {
        //使用七大参数自定义线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2, //核心线程数
                5, //最大线程数
                3,  //超过核心线程数的最大空闲时间
                TimeUnit.SECONDS, //以秒为时间单位
                new ArrayBlockingQueue<>(2), //创建阻塞队列 超过最大线程数后 启用队列 存放等待执行任务
                Executors.defaultThreadFactory(),  //默认线程工厂 一般不用改变
                new ThreadPoolExecutor.AbortPolicy()); //拒绝策略 当最大线程数满了 并且阻塞队列也满了时 启用拒绝策略
        try{
            for (int i = 1; i <= 7; i++) {
                //使用了线程池来创建线程
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ==> OK");
                });
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPoolExecutor.shutdown();
        }
    }
}
pool-1-thread-1 ==> OK
pool-1-thread-3 ==> OK
pool-1-thread-5 ==> OK
pool-1-thread-2 ==> OK
pool-1-thread-3 ==> OK
pool-1-thread-1 ==> OK
pool-1-thread-4 ==> OK

9、四大函数式接口

函数式接口用于简化编程模型

  • Function 函数型接口

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

package com.liuxiang.Function;

import java.util.function.Function;

/**
 * Function函数型接口
 * @author 86187 
 */
public class TestFunction {
    public static void main(String[] args) {
        //工具类
        Function function =new Function<String,String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };

        //Lambda表达式
        //Function function = (str) -> {return str;};

        System.out.println(function.apply("123"));

    }
}
123
  • Predicate 断定型接口

Predicate 断定型接口:有一个输入参数,返回值只能是布尔值

package com.liuxiang.Function;

import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Predicate断定型接口:有一个输入参数,返回值只能是布尔值
 * @author 86187
 */
public class TestPredicate {
    public static void main(String[] args) {
        //工具类
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };

        //Lambda表达式
        //Predicate<String> predicate1 = (str) -> {return str.isEmpty();};

        System.out.println(predicate.test(""));

    }
}
true
  • Consumer 消费型接口

Consumer 消费型接口:只有输入,没有返回值

package com.liuxiang.Function;

import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Consumer 消费型接口:只有输入,没有返回值
 * @author 86187
 */
public class TestConsumer {
    public static void main(String[] args) {
        Consumer<String> consumer = new Consumer<String>() {

            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };

        //Lambda表达式
        //Consumer<String> consumer = (str)->{System.out.println(str);};

       consumer.accept("123");

    }
}
123
  • Supplier 供给型接口

Supplier 供给型接口:没有参数,只有返回值

package com.liuxiang.Function;

import java.util.function.Supplier;

/**
 * Supplier 供给型接口:没有参数,只有返回值
 * @author 86187
 */
public class TestSupplier {
    public static void main(String[] args) {
        Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return 1024;
            }
        };

        //Lambda表达式
        //Supplier<Integer> supplier = ()->{return 1024;};
        
        System.out.println(supplier.get());

    }
}
1024

10、Stream流式计算

package com.liuxiang;

import java.util.ArrayList;
import java.util.List;

public class TestStream {
    public static void main(String[] args) {

        User a = new User(1, "a", 13);
        User b = new User(2, "b", 23);
        User c = new User(3, "c", 10);
        User d = new User(4, "d", 5);
        User e = new User(5, "e", 9);

        List<User> list = new ArrayList<>();
        list.add(a);
        list.add(b);
        list.add(c);
        list.add(d);
        list.add(e);

        list.stream()
                //filter过滤
                .filter(user -> {return user.getId()%2!=0;})
                .filter(user -> {return user.getAge()>10;})
                //Function接口 传入一个user参数 返回一个user参数
                .map(user -> {return user.getName().toUpperCase();})
                //比较 排序
                .sorted()
                //分页 只获取一个参数
                .limit(1)
                //消费性接口Consumer 无返回值
                .forEach((user) -> {
                    System.out.println(user);
                });

    }
}
class User{
    int id;
    String name;
    int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
A

11、ForkJoin

  • 什么是 ForkJoin

分支合并,并行处理任务,将比较大的任务拆成小任务

适用场景:适合处理大型数据

ForkJoin 特点:工作窃取,此时有两个线程,A线程执行了一半,B线程已经执行完了,此时B线程会去窃取A线程的任务,来帮助它完成,这就叫工作窃取。

这里面维护的是双端队列

  • ForkJoin 的使用

  1. 集成 RecursiveTask 递归任务,重写 compute 方法
  2. 创建 ForkJoinPool 类,submit() 提交任务–有返回值,execute() 执行任务–无返回值
package com.liuxiang.Function;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

public class TestForkJoin extends RecursiveTask<Long> {
    private Long start;
    private Long end;

    //临界值
    private Long temp = 10000L;

    public TestForkJoin(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    //计算方法
    @Override
    protected Long compute() {
        long middle = (start+end)/2;//中间值
        TestForkJoin fork1 = new TestForkJoin(start, middle);
        fork1.fork();//拆分任务
        TestForkJoin fork2 = new TestForkJoin(middle, end);
        fork2.fork();
        return fork1.join()+fork2.join();//结果
    }
}

class TestFork{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test2();
    }

    //ForkJoin 计算long
    public static void test1() throws ExecutionException, InterruptedException {
        ForkJoinPool joinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = joinPool.submit(new TestForkJoin(0L, 10_0000_0000L));//提交任务
        long sum = submit.get();//获取结果
        System.out.println(sum);
    }

    //使用stream流式计算
    public static void test2(){
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println((end-start));
    }
}
500000000500000000
241

12、异步回调

CompletableFuture 类似与 ajax,用于异步执行任务

package com.liuxiang.Function;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("没有返回值的异步回调");
        });

        //有返回值的supplyAsync异步回调
        //与ajax一样 成功和失败都可以获取响应信息
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("有返回值的异步回调");
            return 1024;
        });

        future2.whenComplete((t,u) -> {
            //成功时 的回调方法
            System.out.println(t);//1024 t正常的返回结果
            System.out.println(u); // 错误的信息
        }).exceptionally(e -> {
            //失败时的回调
            e.printStackTrace();
            return 244;
        });

        future2.get();//阻塞等待执行结果

    }
}
没有返回值的异步回调
有返回值的异步回调
1024
null

13、JMM

JMM:Java内存模型,不存在的东西,概念!

  • 关于 JMM 的一些同步约定
  1. 线程解锁前,必须把共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存
  3. 加锁和解锁是用一把锁
  • JMM 的八种交互操作(每个操作都为原子操作)
  1. lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  2. unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load动作使用
  4. load(载入):作用于工作内存的变量,它把 read 操作从主存中变量放入工作内存中
  5. use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  6. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  7. store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write 使用
  8. write(写入):作用于主内存中的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中
  • 对八种操作的规则
  1. 不允许 read 和 load、store 和 write 操作之一单独出现。即使用了 read 必须 load,使用了 store 必须 write
  2. 不允许线程丢弃他最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存
  3. 不允许一个线程将没有 assign 的数据从工作内存同步回主内存
  4. 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施 use、store 操作之前,必须经过 assign 和 load 操作
  5. 一个变量同一时间只有一个线程能对其进行 lock。多次 lock 后,必须执行相同次数的 unlock 才能解锁
  6. 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load 或 assign 操作初始化变量的值
  7. 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量
  8. 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存

14、Volatile

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

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

14.1、保证可见性

package com.liuxiang;

import java.util.concurrent.TimeUnit;

public class TestVolatile {
    //多个线程使用同一个变量时 需要加入volatile保证可见性
    //main线程修改了变量 要及时通知其他线程 否则会造成死循环
    private volatile static int sum =0;

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

        new Thread(() -> {
            while (sum==0){ //sum等于0时 程序死循环

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);//确保线程启动

        sum=1;//主线程修改sum的值

        System.out.println(sum);
    }
}
1

Process finished with exit code 0

14.2、不保证原子性

package com.liuxiang;

import java.util.concurrent.TimeUnit;

public class TestVolatile {
    //volatile不保证原子性
    private volatile static int sum =0;

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

    public static void main(String[] args) {

        //理论上结果为10000 实际上volatile并不保证原子性 结果肯定不为两万
        for (int i = 1; i <= 10000; i++) {
            new Thread(()-> {
                add();
            }).start();
        }

        while (Thread.activeCount() > 2){//线程存活数
            //main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" ==> "+sum);
    }
}
main ==> 9999

在不使用 synchronized 和 lock 的情况下,我们应该如何保证原子性?

通过 javap -c 命令我们了解到 sum++ 并不是一个原子性操作:

可以使用 java.util.concurrent.atomic 包下的原子类来确保原子性:

package com.liuxiang;

import java.util.concurrent.atomic.AtomicInteger;

public class TestVolatile {
    //AtomicInteger int类型的原子类
    private volatile static AtomicInteger sum =new AtomicInteger();

    public synchronized static void add(){
        sum.getAndIncrement();// +1 操作  底层使用CAS 非常高效
    }

    public static void main(String[] args) {

        //理论上结果为10000 实际上volatile并不保证原子性 结果肯定不为两万
        for (int i = 1; i <= 10000; i++) {
            new Thread(()-> {
                add();
            }).start();
        }

        while (Thread.activeCount() > 2){//线程存活数
            //main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+""+sum);
    }
}
main10000

这些类的底层都直接和操作系统挂钩,直接操作内存 unsafe

14.3、禁止指令重排

什么是指令重排?

  1. 你写的程序,计算机并不是按照你写的顺序执行
  2. 源代码 -> 编译器优化的重排 -> 指令并行也可能会重排 ->内存系统也会重排 ->执行
  3. 处理器在进行指令重排的时候,会考虑数据的依赖问题

Volatile 如何避免指令重排?

内存屏障,作用:

  1. 保证特定的操作执行顺序
  2. 可以保证某些变量的内存可见性

15、CAS

15.1、什么是CAS

compareAndSet:比较并交换

如果期望的值达到了,那么就会更新,否则就会不更新

public static void main(String[] args) {
    //CAS 如果期望的值达到了 就更新
    //CAS 是CPU的并发原语
    AtomicInteger atomicInteger = new AtomicInteger(2020);
    atomicInteger.compareAndSet(2020,2021);
}

点进源码可以看到一个 unsafe 的东西,它是什么?

我们知道 Java 是无法操作内存的,但是可以通过 unsafe 这个类来操作内存:

查看原子类 AtomicInteger 的加1操作,再次理解 CAS:

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,则进行交换,否则会一直循环(自旋锁)

缺点:

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

15.2、原子引用解决ABA问题

什么是ABA问题?(狸猫换太子)

  1. 比如是有两个线程A,B,一个变量:苹果
  2. A线程期望拿到一个苹果
  3. B线程一进来把苹果改成了梨子,但是在最后结束的时候又把梨子换成了苹果
  4. A线程在此期间是不知情的,以为自己拿到的苹果还是原来的那一个,其实已经被换过了

如何解决ABA问题?

  1. 原子引用 AtomicStampedReference
  2. 类似于乐观锁,比较时会去对比版本号,确认变量是否被换过了
package com.liuxiang;

import java.util.concurrent.atomic.AtomicStampedReference;

public class TestAtomicStampedReference {
    public static void main(String[] args) {
        //AtomicStampedReference 构造方法需要传入变量 和 版本号
        AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            int stamp = reference.getStamp();//获取版本号
            System.out.println("a1=>"+stamp);
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //期望2020 换成2021 期望的版本号是reference.getStamp() 结束后版本号+1
            System.out.println(reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1));
            System.out.println("a2=>"+reference.getStamp());
            reference.compareAndSet(2,1,reference.getStamp(),reference.getStamp()+1);
            System.out.println("a3=>"+reference.getStamp());
        }).start();

        new Thread(() -> {
            int stamp = reference.getStamp();//获取版本号
            System.out.println("b1=>"+stamp);
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //如果版本号不是原来的那一个 那么修改会不成功
            System.out.println(reference.compareAndSet(1, 6, stamp, stamp + 1));
            System.out.println("b2=>"+reference.getStamp());
        }).start();
    }
}
a1=>1
b1=>1
true
false
b2=>2
a2=>2
a3=>3

16、各种锁的理解

16.1、可重入锁

又叫递归锁

package com.liuxiang;

public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone{

    //可重入锁 拿到外面的锁 也就获得了里面的锁
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"sms");
        call();
    }

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

16.2、自旋锁

自旋锁就是不断尝试,直到成功为止!

package com.liuxiang;

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

public class TestSpinlock {
    public static void main(String[] args) {
        //底层使用的自旋锁CAS
        Spinlock spinlock = new Spinlock();

        new Thread(()->{
            spinlock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlock.myUnLock();
            }

        },"T1").start();

        new Thread(()->{
            spinlock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlock.myUnLock(); //必须等T1释放锁T2才能释放锁
            }

        },"T2").start();

    }
}

class Spinlock{

    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==> myLock");

        //自旋锁
        while (!atomicReference.compareAndSet(null,thread));{

        }
    }

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

}
T1==> myLock
T2==> myLock
T1==> myUnLock
T2==> myUnLock

16.3、死锁

多个线程同时争夺对方的资源

  • 怎么排查死锁
  1. 使用 JPS 定位进程号,命令:jps -l
  2. 使用 jstack 加进程号找到死锁问题

posted @ 2021-08-21 17:08  有一个大佬梦  阅读(158)  评论(0)    收藏  举报