java多线程-JUC

JUC之java高并发-多线程学习

1. JUC定义

源码 + 官方文档
java多线程-JUC_第1张图片

java.util工具包下的三个接口及工具类

**业务:**普通的线程代码:Thread

**Runnable接口:**没有返回值、效率相比于Callable较低

java多线程-JUC_第2张图片

java多线程-JUC_第3张图片

2. 线程和进程

线程、进程使用一句话来概括

**进程:**一个程序,如QQ.exe,Music.exe程序的集合

  • 一个进程往往可以包含多个线程,至少会包含一个!
  • java默认有两个线程:mainGC

**线程:**比如当前开启了一个Typora进程,在其中进行打字,保存,删除等单一操作是由特定的线程负责的

  • java实现线程:ThreadRunnableCallable

Java程序真的可以开启线程吗?看源码!

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the run method of this thread.
     * 

* The result is that two threads are running concurrently: the * current thread (which returns from the call to the * start method) and the other thread (which executes its * run method). *

* It is never legal to start a thread more than once. * In particular, a thread may not be restarted once it has completed * execution. * * @exception IllegalThreadStateException if the thread was already * started. * @see #run() * @see #stop() */ 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 */ } } } private native void start0();

  • 注意看start0()方法,是使用native关键字修饰的,这个是本地方法,不是java程序完成的,是别的C/C++程序操作的,因为java程序必须执行在JVM环境中,无法直接操作计算机硬件,所以创建线程本质上并非java程序完成!

并发、并行的区别

并发编程:并发并行

并发:(多线程操作同一资源)

  • CPU一核,模拟出来多条线程,通过快速交替执行来达到并发的目的

并行:(多个线程同时执行)

  • CPU多核,多个线程可以同时执行;线程池

java多线程-JUC_第4张图片

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

线程的几个状态

/**
     * A thread state.  A thread can be in one of the following states:
     * 
    *
  • {@link #NEW}
    * A thread that has not yet started is in this state. *
  • *
  • {@link #RUNNABLE}
    * A thread executing in the Java virtual machine is in this state. *
  • *
  • {@link #BLOCKED}
    * A thread that is blocked waiting for a monitor lock * is in this state. *
  • *
  • {@link #WAITING}
    * A thread that is waiting indefinitely for another thread to * perform a particular action is in this state. *
  • *
  • {@link #TIMED_WAITING}
    * A thread that is waiting for another thread to perform an action * for up to a specified waiting time is in this state. *
  • *
  • {@link #TERMINATED}
    * A thread that has exited is in this state. *
  • *
* *

* A thread can be in only one state at a given point in time. * These states are virtual machine states which do not reflect * any operating system thread states. * * @since 1.5 * @see #getState */ public enum State { // 创建 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待-一直等待 WAITING, // 超时等待-限制时间(过时不候) TIMED_WAITING, // 销毁 TERMINATED; }

wait/sleep的区别

1. 来自不同的类

wait() ==>>> Object类 sleep() ==>>> Thread类

2. 关于锁的释放

wait会释放锁,sleep如同人睡着了,忘记释放锁的操作!

3.使用的范围不同

wait ==>>> 同步代码块 sleep ==>>> 任意地方

4.异常捕获

二者都需要进行中断异常捕获!

3. Lock锁(重点)

传统的synchronized

Lock

java多线程-JUC_第5张图片

java多线程-JUC_第6张图片

公平锁:十分公平,先来先得

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

Synchronized锁和Lock锁的区别

1. Synchronized是内置的java关键字,Lock是一个java接口

2. Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁;

3. Synchronized会自动释放锁,Lock必须要手动释放锁,如果不释放锁,就会产生死锁;

4. Synchronized 线程(获取锁时会进入阻塞状态)、其余线程只能一直等待当前线程释放锁;Lock可以尝试获取当前锁,未必会一直等待下去;

5. Synchronized 可重入锁,不可以中断,为非公平锁;Lock为可重入锁,可以判断锁的状态,默认为非公平锁,但可以自行设置;

6. Synchronized 适合锁少量代码同步问题,Lock适合锁大量的同步代码块。

锁是什么?如何判断锁的对象是谁?

4. 生产者和消费者问题

面试程序:单例模式、八大排序算法、生产者和消费者、死锁

Synchronized wait notify版本为老版本!

问题存在,A、B两个线程执行没有任何问题;A、B、C、D多线程同时启动,还能保证线程同步吗?—虚假唤醒

java多线程-JUC_第7张图片

  • 解决:将if单词判断修改为循环判断

JUC版本的生产者和消费者问题,话不多说,见JDK源码:

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock(); try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally { lock.unlock(); }
   }

   public Object take() throws InterruptedException {
     lock.lock(); try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally { lock.unlock(); }
   }
 } 
  • Synchronized版本 : wait()方法 || notify()方法
  • Lock版本 : await()方法 || signal()方法

Condition接口的优势在什么地方?— 精准通知和唤醒线程

5. 8锁现象-解决判断锁的对象问题

对象–>多个/class–>唯一

  • 8锁之问题一--->>标准情况下,两个线程是发短信先进行,还是打电话先进行(先执行发短信,再执行打电话)
    
  • 八锁问题之二--->>增加了非同步方法后,线程执行不受锁的影响(先执行非同步方法);
    * 两个对象,两个同步方法,谁先执行,就先有锁锁住方法调用者,没有延迟的先执行
    
  • 八锁问题之三--->>增加两个静态同步方法,静态方法在类加载就产生,为.class模板,一个类中的所有静态方法使用的是同一个
    * Class模板,所以static方法会顺序执行,无论有多少个对象进行方法调用
    
  • 八锁问题之四--->>一个普通的同步方法和一个静态的同步方法,在一个对象进行调用时;
    * 一个普通的同步方法和一个静态的同步方法,在两个对象进行调用时;我的都是先执行静态同步方法
    

小结

  • new出来的实例,this关键字可修饰,是一个类的具体实例;

  • static Class模板,是一个类的唯一模板

6. 集合类不安全

List不安全

  • 会造成ConcurrentModificationException并发修改异常!

解决方案如下:

1、List list = new Vector<>();
2、List list = Collections.synchronizedList(new ArrayList<>());
3、List list = new CopyOnWriteArrayList<>(); 因为Vector中读写方法都为同步方法,在执行效率方面不如CopyOnWriteArrayList的非同步方法效率高,CopyOnWriteArrayList中使用的是Lock锁,对锁的状态操作比较灵活

Set不安全

java多线程-JUC_第8张图片

解决方案如下:

1、Set set = Collections.synchronizedSet(new HashSet<>()); 请爸爸来帮忙
2、Set set = new CopyOnWriteArraySet<>();
  • hashSet底层是什么?
/**
  * Constructs a new, empty set; the backing HashMap instance has
  * default initial capacity (16) and load factor (0.75).
 **/
public HashSet() {
    map = new HashMap<>();
}
/**
  * add方法 set 的底层本质就是map, hashmap中key是无法重复的
 **/
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

Map不安全

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-viaGIiok-1625019931941)(D:\APP-文档\学习笔记\java多线程-JUC.assets\image-20210324160816584.png)]
java多线程-JUC_第9张图片

7. Callable

Callable、Runnable异同

  • 可以有返回值;
  • 可以抛出异常;
  • 实现方法不同:run()/call()

java多线程-JUC_第10张图片

java多线程-JUC_第11张图片

  • 细节:
    • 1、有缓存,提高效率;
    • 2、结果可能需要等待,会阻塞!

8. 常用的辅助类

8.1 CountDownLatch

java多线程-JUC_第12张图片

原理:

  • countDownLatch.countDown();	// 初始化数量减1
    
  • countDownLatch.await();	// 等待计数器归零,再执行其下面的程序(起拦截的作用),每次使用countdown()方法将计数器值减1,当计数器值为0时,await()方法就会被唤醒,继续执行后续的程序!
    
8.2 CyclicBarrier

java多线程-JUC_第13张图片

for (int i = 1; i <= 7; i++) {
    final int temp = i;
    // lambda表达式不能直接操作i
    new Thread(()-> {
        System.out.println(Thread.currentThread().getName() + "收集了" + temp + "个龙珠");
        try {
            // 等待
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();
}
8.3 Semaphore(信号量)

在这里插入图片描述

原理:

  • semaphore.acquire();	// 获得资源,如果资源全被使用,就等待资源被释放--信号量-1操作
    
  • semaphore.release();	// 释放资源,--信号量+1操作,等待别的线程进行调度
    

作用:

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

9. 读写锁

**ReadWriteLock**

java多线程-JUC_第14张图片

自定义缓存---ReadWriteLock(独占锁-写锁[一次只能被一个线程占有];共享锁-读锁[一次可以被多个线程占有])
1、读-读:可以共存;
2、读-写:不能共存;
3、写-写:不能共存

示例:

class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();
    /**
     * 读写锁,更加细粒度的控制
     **/
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * 存/写操作
     */
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    /**
     * 取/读操作
     */
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

10. 阻塞队列

阻塞/队列:[写操作]–如果队列满了,就必须阻塞等待读操作进行;[读操作]–如果队列为空,就必须阻塞等待写操作进行。

  • 实现:

java多线程-JUC_第15张图片

java多线程-JUC_第16张图片

什么情况下会使用阻塞队列?多线程并发执行/线程池使用线程队列

  • 学会使用队列

添加/移除

四组API

方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
添加 add() offer() put() offer(E e, long timeout, TimeUnit unit)
移除 remove() poll() take() poll(long timeout, TimeUnit unit) throws InterruptedException
检测判断队列首 element() peek()

同步队列-SynchronousQueue

没有容量,进入一个元素,必须等待其被取出之后才能再次放入元素

  • 同步队列-和其他的BlockQueue不同,SynchronousQueue不存储元素向队列put一个元素,必须从里面先使用take操作取出,否则不能再put其他元素
    

11. 线程池(重点)

[三大方法、七大参数、四种拒绝策略]

**池化技术:**事先准备好一些资源,有人要使用,就来读这些现成的资源,使用完成后归还!

  • 程序的运行,其本质:占用系统的资源,故池化技术==>优化资源的使用

线程池、连接池、内存池、对象池…

  • 线程池的优势:

1、降低资源的消耗;

2、提高响应的速度;

3、方便管理。

1. 线程池的三大方法:

// 单个线程
Executors.newSingleThreadExecutor();
// 创建固定大小的线程池
Executors.newFixedThreadPool(5);
// 可伸缩线程池,遇强则强、遇弱则弱
Executors.newCachedThreadPool();

2. 线程池的七大参数:

源码分析:

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,				// 越等于21亿:容易导致OOM
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

// 本质:ThreadPoolExecutor()来创建线程池
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.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

手动创建一个线程池:

ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                // 队列满了会尝试和最早的线程竞争,也不会抛出异常
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

四种拒绝策略:

java多线程-JUC_第17张图片

{new ThreadPoolExecutor.AbortPolicy());}-->阻塞队列已满,还有线程访问,就不接受此线程并抛出异常
{new ThreadPoolExecutor.CallerRunsPolicy());}-->哪里来的就回到哪里去!
{new ThreadPoolExecutor.DiscardPolicy());}-->队列满了会丢掉其余的线程,不会抛出异常
{new ThreadPoolExecutor.DiscardOldestPolicy());}-->队列满了会尝试和最早的线程竞争,也不会抛出异常

小结和扩展:

  • 了解:io密集型/cpu密集型

线程池中的最大线程数如何设置:

自定义线程池:最大线程到底如何定义:
1、cpu密集型:电脑cpu为几核,最大线程池就定义为多大,这样可以保持cpu的效率最高
2、IO密集型:判断程序中十分消耗IO的线程(15个大型任务,io十分占用资源)

12. 四大函数接口(必须掌握)

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

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

  • 示例:
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

// @FunctionalInterface:简化编程模式,在新版本的框架中大量使用
// forEach(参数为消费者类的函数式接口)

java多线程-JUC_第18张图片

Function:函数式接口

// 工具类:输出输入的值
Function<String, String> function = str -> str;

Predicate:断定式接口

// 判定接口:有一个输入参数,其返回值只能是boolean值
Predicate<String> predicate = String::isEmpty;

Consumer:消费型接口

// 消费型接口:只有输入,没有输出
Consumer<String> consumer = System.out::println;

Supplier:供给型接口

// 供给型接口:没有参数,只有返回值
Supplier supplier = () -> 1024;

13. Stream流式计算

什么是Stream流式计算?

  • 大数据:存储+计算—>集合,数据库就是用来存储东西的,计算都应该交给流来操作!
list.stream()
                .filter(user -> user.getId() % 2 == 0)
                .filter(user -> user.getAge() > 23)
                .map(user -> user.getName().toUpperCase())
                .sorted(Comparator.reverseOrder())
                .limit(1)
                .forEach(System.out::println);

14. ForkJoin

什么是ForkJoin?

ForkJoinJDK1.7出现,并行执行任务!提高效率大数据量

  • 大数据:Map Reduce(把)

java多线程-JUC_第19张图片

  • ForkJoin的特点:工作窃取–提高线程工作效率

java多线程-JUC_第20张图片

如何使用ForkJoin:

  • 1、通过ForkJoinPool接口调用:

在这里插入图片描述

  • 2、计算任务forkjoinTask.execute(函数式接口)

15. 异步回调

Future设计初衷对未来的某个结果进行建模

java多线程-JUC_第21张图片

// 发送一个请求 - 没有返回值的异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
});

System.out.println("1111");

completableFuture.get();    // 获取阻塞执行结果


// 有返回值的异步回调 - ajax包含有成功/失败的回调函数
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "runAsync=>Integer");
    int i = 10/0;
    return 1024;
});

completableFuture.whenComplete((t, u) -> {
    System.out.println("t => " + t);    // 正常的返回结果
    System.out.println("u => " + u);
}).exceptionally(e -> {
    System.out.println(e.getMessage());
    return 233;
});

16. JVM

Volatile关键字的理解!

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

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

什么是JMM?

**JMM:**java内存模型,是个抽象的概念或者约定!

  • 1、线程解锁前,必须把共享变量==立即==刷新回主存;
  • 2、线程加锁前,必须读取贮存中最新值到工作内存中;
  • 3、加锁和解锁必须是同一把锁。

8种操作:

java多线程-JUC_第22张图片

Java内存模型中定义了8种操作来完成,虚拟机保证了每种操作都是原子的。

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

如果要把一个变量从主存复制到工作内存:顺序执行 read 和 load 操作。

如果要把变量从工作内存同步会主存:顺序执行 store 和 write 操作。

17. Volatile

1、保证可见性

private static volatile int num = 0;	// volatile关键字修饰可以保证可见性

2、不保证原子性–不可分割(线程在执行任务的时候,不能被打扰,也不能被分割。要么同时成功,要么同时失败!)

// 理论上是20x1000=20000
for (int i = 1; i <= 20; i++) {
    new Thread(()-> {
        for (int j = 0; j < 1000; j++) {
            add();
        }
    }).start();
}

while (Thread.activeCount() > 2) {
    Thread.yield();
}

System.out.println(Thread.currentThread().getName() + " " + num);

// 执行结果:main 15595(小于20000)

java多线程-JUC_第23张图片

如果不适用Lock和Synchronized锁,如果保证原子性?

java多线程-JUC_第24张图片

  • 这些类的操作都直接和操作系统挂钩,在内存中修改值!Unsafe类是一个很特殊的类!

什么是指令重排?

  • 程序在执行时,计算机并不是按照你写的那样顺序执行的。

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

  • 处理器在进行指令重排的时候,需要考虑数据之间的依赖性
int x = 1;	// 1
int y = 2;	// 2
x = x + 5;	// 3
y = x * x;	// 4

// 期望的操作1234  但是2134/1324也可以达到相同的效果
// 不可能是4123
线程A 线程B
x = a y = b
b = 1 a = 2

正常的结果:x = 0; y = 0。但是可能由于指令重排,下面表格中的顺序也有可能发生!

线程A 线程B
b = 1 a = 2
x = a y = b

指令重排序导致的诡异结果:x = 2; y = 1。

volatile可以避免指令重排!

内存屏障/CPU指令作用:

  • 1、保证特定的操作的正常顺序执行;
  • 2、可以保证某些变量的内存可见性。

18. 单例模式

饿汉式 DCL懒汉式,深究!

package com.juc.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * @Description: 懒汉式单例模式
 * @author: L-B-S
 * @Date: 2021/3/26 18:37
 * @modified_By:
 * @Version: 0.0$
 */
public class Lazy {

    private static volatile Lazy lazy;

    private static boolean flag = false;

    private Lazy() {
        synchronized (Lazy.class) {
            if (!flag) {
                flag = true;
            } else {
                throw new RuntimeException("不要试图用反射破坏异常");
            }
        }
    }

    public static Lazy getInstance() {
        // 加锁
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    // 不是一个原子性操作:1、分配内存空间;2、执行构造方法,初始化对象;3、将这个对象指向内存空间
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    // 单线程下安全

    public static void main(String[] args) throws Exception {
//        Lazy instance1 = Lazy.getInstance();

        Field flag = Lazy.class.getDeclaredField("flag");
        flag.setAccessible(true);

        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Lazy instance1 = declaredConstructor.newInstance();

        flag.set(instance1, false);

        Lazy instance2 = declaredConstructor.newInstance();

        System.out.println(instance1 == instance2);
    }
}

19. 深入理解CAS—修内功

什么是CAS?

java多线程-JUC_第25张图片

  • CAS底层使用的未自旋锁:当比较当前工作内存中的值和主存中的值,如果这个值是期望值,那么就执行后续操作;否则的话就一直循环等待!
  • 缺点:
  • 1、循环会耗时;
  • 2、一次性只能保证一个共享变量的原子性;
  • 3、容易导致ABA问题。

CAS核心问题:ABA问题(简称:狸猫换太子)

java多线程-JUC_第26张图片

// 捣乱的线程
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());

// 期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());

20. 原子引用

ABA问题的解决方案:引入原子引用,完美解决,每次更改都会有版本记录!

package com.juc.cas;

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

/**
 * @Description: CAS---CPU的并发原语!
 * @author: L-B-S
 * @Date: 2021/3/26 20:17
 * @modified_By:
 * @Version: 0.0$
 */
public class CASDemo {

    public static void main(String[] args) {

        // int Integer 注意:如果泛型是一个包装类,在比较时注意对象的引用问题
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            // 获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1 => " + stamp);

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

            System.out.println(atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a2 => " + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3 => " + atomicStampedReference.getStamp());

        }, "A").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1 => " + stamp);

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

            System.out.println(atomicStampedReference.compareAndSet(1, 3,
                    stamp, stamp + 1));
        }, "B").start();

        System.out.println("b2 => " + atomicStampedReference.getStamp());


    }
}

21. 各种锁的理解

1、公平锁,非公平锁

公平锁:非常公平(不能插队)

非公平锁:非常不公平(可以插队,默认都是非公平的)

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
public ReentrantLock() {
    sync = new NonfairSync();
}

Lock lock = new ReentrantLock(true);	// 重载方法:传入true,为公平锁,false则为非公平锁
2、可重入锁/递归锁

Synchronized关键字实现可重入锁

class Phone1 {
    public synchronized void sendMsg() {
        System.out.println(Thread.currentThread().getName() + " => sendMsg");
        call();
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " => call");
    }
}

Lock接口实现可重入锁

class Phone2 {
    Lock lock = new ReentrantLock();

    public synchronized void sendMsg() {
        // 第一把锁--锁必须配对,加几次锁必须解几次锁
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " => sendMsg");
            // 达到call方法内的锁
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public synchronized void call() {
        // 第二把锁
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " => call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • 细节需要注意锁的配对情况-》即使用了几把锁就必须有几把锁的钥匙(解锁),否则会造成死锁情况
3、自旋锁

SpinLock:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
  • CAS底层:标准的自旋锁!
4、死锁

死锁是什么?

java多线程-JUC_第27张图片

  • 怎么排除/测试死锁:

解决死锁:

1、使用 jps -l 定位进程号;

D:\java_study\juc-study>jps -l
9936 com.juc.lock.DeadLockDemo
12804 sun.tools.jps.Jps
17828 org.jetbrains.jps.cmdline.Launcher
12872
14024 org.jetbrains.idea.maven.server.RemoteMavenServer36

2、使用 jstack进程号 找到死锁问题

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

public synchronized void call() {
    // 第二把锁
    lock.lock();
    try {
        System.out.println(Thread.currentThread().getName() + " => call");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

}


- `细节`:**需要注意锁的配对情况-》即使用了几把锁就必须有几把锁的钥匙(解锁),否则会造成死锁情况**

#### 3、自旋锁

> **SpinLock:**

```java
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
  • CAS底层:标准的自旋锁!
4、死锁

死锁是什么?

  • 怎么排除/测试死锁:

解决死锁:

1、使用 jps -l 定位进程号;

D:\java_study\juc-study>jps -l
9936 com.juc.lock.DeadLockDemo
12804 sun.tools.jps.Jps
17828 org.jetbrains.jps.cmdline.Launcher
12872
14024 org.jetbrains.idea.maven.server.RemoteMavenServer36

2、使用 jstack进程号 找到死锁问题

java多线程-JUC_第28张图片

你可能感兴趣的:(java学习方面,java)