JUC并发编程

JUC并发编程学习笔记

(CAS,volatile详解 https://www.jianshu.com/p/1f19835e05c0)

(狂神视频地址:https://space.bilibili.com/95256449)

1. 什么是JUC

JUC就是java.util.concurrent(并发的,并存的)下面的类包,专门用于多线程的开发。

2. 线程和进程

进程

我们都知道计算机的核心是CPU,它承担了所有的计算任务,而操作系统是计算机的管理者,它负责任务的调度,资源的分配和管理,统领整个计算机硬件;应用程序是具有某种功能的程序,程序是运行于操作系统之上的。

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志

进程具有的特征:

动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;

并发性:任何进程都可以同其他进行一起并发执行;

独立性:进程是系统进行资源分配和调度的一个独立单位;

结构性:进程由程序,数据和进程控制块三部分组成

线程

在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。

后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元==,是处理器调度和分派的基本单位==。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)

一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成

而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。

进程与线程的区别

  1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;

  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

  3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信

号等),某进程内的线程在其他进程不可见;

  1. 调度和切换:线程上下文切换比进程上下文切换要快得多

1)进程

一个程序,QQ.EXE Music.EXE;数据+代码+pcb

一个进程可以包含多个线程,至少包含一个线程!

Java默认有几个线程?2个线程! main线程、GC线程

2)线程

开了一个进程Typora,写字,等待几分钟会进行自动保存(线程负责的)

对于Java而言:Thread、Runable、Callable进行开启线程的。

提问?JAVA真的可以开启线程吗? 开不了的!

Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。

  public synchronized void start() {/**

​     \* This method is not invoked for the main method thread or "system"

​     \* group threads created/set up by the VM. Any new functionality added

​     \* to this method in the future may have to also be added to the VM.

​     *

​     \* A zero status value corresponds to state "NEW".

​     */if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started

​     \* so that it can be added to the group's list of threads

​     \* and the group's unstarted count can be decremented. */

​    group.add(this);boolean started = false;try {start0();

​      started = true;} finally {try {if (!started) {

​          group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable then

​         it will be passed up the call stack */}}

  }

//这是一个C++底层,Java是没有权限操作底层硬件的

  private native void start0();

3)并发

多线程操作同一个资源。

CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。

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

4)并行

并行: 多个人一起行走

CPU多核,多个线程可以同时执行。 我们可以使用线程池!

获取cpu的核数

public class Test1 {

  public static void main(String[] args) {//获取cpu的核数

​    System.out.println(Runtime.getRuntime().availableProcessors());

  }

}

5)线程的状态

public enum State {//新生

​    NEW,//可运行  
       //线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位          于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 

​    RUNNABLE,
    
    //运行   
    //可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
    RUNNING

​      //阻塞

​    BLOCKED,//等待,死等
    
​   		 WAITING,//超时等待

​    		TIMED_WAITING,//终止(dead)

​    TERMINATED;(结束)

  }

6)wait/sleep

1、来自不同的类

wait => Object

sleep => Thread

一般情况企业中使用休眠是:

TimeUnit.DAYS.sleep(1); //休眠1天

TimeUnit.SECONDS.sleep(1); //休眠1s

2、关于锁的释放

wait 会释放锁;

sleep抱着锁睡觉,不会释放锁;

3、使用的范围是不同的

wait 必须在同步代码块中;

sleep 可以在任何地方睡;

4、是否需要捕获异常

wait是不需要捕获异常;

sleep必须要捕获异常;

3.Lock(重点)

传统的 synchronized

package com.marchsoft.juctest;
import lombok.Synchronized;
/**

 

 **/
public class Demo01 {

  public static void main(String[] args) {final Ticket ticket = new Ticket();new Thread(()->{for (int i = 0; i < 40; i++) {

​        ticket.sale();}},"A").start();new Thread(()->{for (int i = 0; i < 40; i++) {

​        ticket.sale();}},"B").start();new Thread(()->{for (int i = 0; i < 40; i++) {

​        ticket.sale();}},"C").start();

  }

}



// 资源类 OOP 属性、方法

class Ticket {

  private int number = 30;
  //卖票的方式

  public synchronized void sale() {if (number > 0) {

​      System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");}

  }

}

Lock

公平锁: 十分公平,必须先来后到~;

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

package com.marchsoft.juctest;

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;



public class LockDemo {

  public static void main(String[] args) {final Ticket2 ticket = new Ticket2();new Thread(() -> {for (int i = 0; i < 40; i++) {

​        ticket.sale();}}, "A").start();new Thread(() -> {for (int i = 0; i < 40; i++) {

​        ticket.sale();}}, "B").start();new Thread(() -> {for (int i = 0; i < 40; i++) {

​        ticket.sale();}}, "C").start();

  }

}

//lock三部曲

//1、  Lock lock=new ReentrantLock();

//2、  lock.lock() 加锁

//3、  finally=> 解锁:lock.unlock();

class Ticket2 {

  private int number = 30;

 

  // 创建锁

  Lock lock = new ReentrantLock();

  //卖票的方式

  public synchronized void sale() {

​    lock.lock(); // 开启锁try {if (number > 0) {

​        System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");}}finally {

​      lock.unlock(); // 关闭锁}
  }

}

Synchronized 与Lock 的区别

1、Synchronized 内置的Java关键字,Lock是一个Java类

2、Synchronized 无法判断获取锁的状态,Lock可以判断

3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

4. 生产者和消费者的关系

Synchronzied 版本
package com.marchsoft.juctest;


public class ConsumeAndProduct {

  public static void main(String[] args) {

​    Data data = new Data();new Thread(() -> {for (int i = 0; i < 10; i++) {try {

​          data.increment();} catch (InterruptedException e) {

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

​          data.decrement();} catch (InterruptedException e) {

​          e.printStackTrace();}}}, "B").start();

  }

}

 

class Data {

  private int num = 0;

  // +1

  public synchronized void increment() throws InterruptedException {// 判断等待if (num != 0) {this.wait();}

​    num++;

​    System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕this.notifyAll();

  }

 

  // -1

  public synchronized void decrement() throws InterruptedException {// 判断等待if (num == 0) {this.wait();}

​    num--;

​    System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 -1 执行完毕this.notifyAll();

  }
    

}	


‘2)存在问题(虚假唤醒)

问题,如果有四个线程,会出现虚假唤醒

解决方式 ,if 改为while即可,防止虚假唤醒

结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后

package com.marchsoft.juctest;

public class ConsumeAndProduct {

  public static void main(String[] args) {

​    Data data = new Data();new Thread(() -> {for (int i = 0; i < 10; i++) {try {

​          data.increment();} catch (InterruptedException e) {

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

​          data.decrement();} catch (InterruptedException e) {

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

​          data.increment();} catch (InterruptedException e) {

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

​          data.decrement();} catch (InterruptedException e) {

​          e.printStackTrace();}}}, "D").start();

  }

}

class Data {

  private int num = 0;

 

  // +1

  public synchronized void increment() throws InterruptedException {// 判断等待while (num != 0) {this.wait();}

​    num++;

​    System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕this.notifyAll();

  }

 

  // -1

  public synchronized void decrement() throws InterruptedException {// 判断等待while (num == 0) {this.wait();}

​    num--;

​    System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 -1 执行完毕this.notifyAll();

  }

}

Lock版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjRbCF1s-1606630223442)(C:\Users\14611\AppData\Roaming\Typora\typora-user-images\image-20201127132326544.png)]

通过Lock 找到 Condition

package com.marchsoft.juctest;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

public class LockCAP {

  public static void main(String[] args) {

​    Data2 data = new Data2();new Thread(() -> {for (int i = 0; i < 10; i++) {try {

​          data.increment();} catch (InterruptedException e) {

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

​          data.decrement();} catch (InterruptedException e) {

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

​          data.increment();} catch (InterruptedException e) {

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

​          data.decrement();} catch (InterruptedException e) {

​          e.printStackTrace();}}}, "D").start();

  }

}


class Data2 {

  private int num = 0;

  Lock lock = new ReentrantLock();

  Condition condition = lock.newCondition();

  // +1

  public void increment() throws InterruptedException {

​    lock.lock();try {// 判断等待while (num != 0) {

​        condition.await();}

​      num++;

​      System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕

​      condition.signalAll();}finally {

​      lock.unlock();}

  }

  // -1

  public void decrement() throws InterruptedException {

​    lock.lock();try {// 判断等待while (num == 0) {

​        condition.await();}

​      num--;

​      System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕

​      condition.signalAll();}finally {

​      lock.unlock();}


  }

}


任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充!

Condition的优势 精准的通知和唤醒的线程!

如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~

package com.marchsoft.juctest;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

/**

 \* Description:

 \* A 执行完 调用B

 \* B 执行完 调用C

 \* C 执行完 调用A

 *

 \* @author jiaoqianjin

 \* Date: 2020/8/11 9:58

 **/

 

public class ConditionDemo {

  public static void main(String[] args) {

​    Data3 data3 = new Data3();new Thread(() -> {for (int i = 0; i < 10; i++) {

​        data3.printA();}},"A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {

​        data3.printB();}},"B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {

​        data3.printC();}},"C").start();

  }

 

}

class Data3 {//资源类  lock

  private Lock lock = new ReentrantLock();

  private Condition condition1 = lock.newCondition();

  private Condition condition2 = lock.newCondition();

  private Condition condition3 = lock.newCondition();

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

 

  public void printA() {

​    lock.lock();try {// 业务代码 判断 -> 执行 -> 通知while (num != 1) {

​        condition1.await();}

​      System.out.println(Thread.currentThread().getName() + "==> AAAA" );

​      num = 2;
       // 唤醒,唤醒指定的人,B
​      condition2.signal();}catch (Exception e) {

​      e.printStackTrace();}finally {

​      lock.unlock();}

  }

  public void printB() {

​    lock.lock();try {// 业务代码 判断 -> 执行 -> 通知while (num != 2) {

​        condition2.await();}

​      System.out.println(Thread.currentThread().getName() + "==> BBBB" );

​      num = 3;
    // 唤醒,唤醒指定的人,C

​      condition3.signal();}catch (Exception e) {

​      e.printStackTrace();}finally {

​      lock.unlock();}

  }

  public void printC() {

​    lock.lock();try {// 业务代码 判断 -> 执行 -> 通知while (num != 3) {

​        condition3.await();}

​      System.out.println(Thread.currentThread().getName() + "==> CCCC" );

​      num = 1;

​      condition1.signal();}catch (Exception e) {

​      e.printStackTrace();}finally {

​      lock.unlock();}

  }

}

/*

A==> AAAA

B==> BBBB

C==> CCCC

A==> AAAA

B==> BBBB

C==> CCCC

...

*/

5、8锁现象

如何判断锁的是谁!锁到底锁的是谁?

锁会锁住:对象、Class

深刻理解我们的锁

问题1

两个同步方法,先执行发短信还是打电话

public class dome01 {

  public static void main(String[] args) {

​    Phone phone = new Phone();new Thread(() -> { phone.sendMs(); }).start();

​    TimeUnit.SECONDS.sleep(1);new Thread(() -> { phone.call(); }).start();

  }

}

class Phone {

  public synchronized void sendMs() {

​    System.out.println("发短信");

  }

  public synchronized void call() {

​    System.out.println("打电话");

  }

}
输出结果为

发短信

打电话

为什么? 如果你认为是顺序在前? 这个答案是错误的!

问题2

我们再来看:我们让发短信 延迟4s

public class dome01 {

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

​    Phone phone = new Phone();new Thread(() -> {try {

​        phone.sendMs();} catch (InterruptedException e) {

​        e.printStackTrace();}}).start();

​    TimeUnit.SECONDS.sleep(1);new Thread(() -> { phone.call(); }).start();

  }

}
 
class Phone {
    //synchronized 锁的对象是方法的调用者!、
    // 两个方法用的是同一个锁,谁先拿到谁执行!

  public synchronized void sendMs() throws InterruptedException {

​    TimeUnit.SECONDS.sleep(4);

​    System.out.println("发短信");

  }

  public synchronized void call() {

​    System.out.println("打电话");

  }

}

现在结果是什么呢?

结果:还是先发短信,然后再打电话!

why?

原因:并不是顺序执行,而是synchronized 锁住的对象是方法的调用者!对于两个方法用的是同一个锁,谁先拿到谁先执行,另外一个等待

问题三

加一个普通方法

public class dome01 {

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

​    Phone phone = new Phone();new Thread(() -> {try {

​        phone.sendMs();} catch (InterruptedException e) {

​        e.printStackTrace();}}).start();

​    TimeUnit.SECONDS.sleep(1);new Thread(() -> { phone.hello(); }).start();

  }

}

 

class Phone {

  public synchronized void sendMs() throws InterruptedException {

​    TimeUnit.SECONDS.sleep(4);

​    System.out.println("发短信");

  }

  public synchronized void call() {

​    System.out.println("打电话");

  }
// 这里没有锁!不是同步方法,不受锁的影响
  public void hello() {

​    System.out.println("hello");

  }

}

输出结果为
hello
发短信
原因:hello是一个普通方法,不受synchronized锁的影响,不用等待锁的释放

 

问题四

如果我们使用的是两个对象一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?

public class dome01 {

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

​    Phone phone1 = new Phone();

​    Phone phone2 = new Phone();new Thread(() -> {try {

​        phone1.sendMs();} catch (InterruptedException e) {

​        e.printStackTrace();}}).start();

​    TimeUnit.SECONDS.sleep(1);new Thread(() -> { phone2.call(); }).start();

  }

}

 

class Phone {

  public synchronized void sendMs() throws InterruptedException {

​    TimeUnit.SECONDS.sleep(4);

​    System.out.println("发短信");

  }

  public synchronized void call() {

​    System.out.println("打电话");

  }

  public void hello() {

​    System.out.println("hello");

  }

}

输出结果
打电话
发短信
原因:两个对象两把锁,不会出现等待的情况,发短信睡了4s,所以先执行打电话

问题五、六

5、 增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话? *

6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?

如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?

public class Test3 {
    public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
//锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
// 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone3{
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //
    public static synchronized void call(){
        System.out.println("打电话");
    }

    //小结
    //new this 具体的一个手机
    //static Class 唯一的一个模板
}

(1)我们先来使用一个对象调用两个方法!

答案是:先发短信,后打电话

(2)如果我们使用两个对象调用两个方法!

答案是:还是先发短信,后打电话

原因是什么呢? 为什么加了static就始终前面一个对象先执行呢!为什么后面会等待呢?

原因是:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的进程都需要等待!

问题七

如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?

package aaa;

import java.util.concurrent.TimeUnit;

public class test3 {
    public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
        Phone3 phone1 = new Phone3();

//锁的存在
        new Thread(() -> {
            phone1.sendSms();
        }, "A").start();
// 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone1.call();
        }, "B").start();
    }
}

// Phone3唯一的一个 Class 对象
class Phone3 {
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("短信");
    }

    //锁的是对象
    public synchronized void call() {
        System.out.println("电话");
    }
}


    //小结
    //new this 具体的一个手机
    //static Class 唯一的一个模板
}

//输出结果
//打电话
//发短信
//原因:因为一个锁的是Class类的模板,一个锁的是对象的调用者。所以不存在等待,直接运行。

问题八

如果我们使用一个静态同步方法、一个同步方法、两个对象调用顺序是什么?

public class dome01 {

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

​    Phone phone1 = new Phone();

​    Phone phone2 = new Phone();new Thread(() -> {try {

​        phone1.sendMs();} catch (InterruptedException e) {

​        e.printStackTrace();}}).start();

​    TimeUnit.SECONDS.sleep(1);new Thread(() -> { phone2.call(); }).start();

  }

}

 

class Phone {
// 静态的同步方法 锁的是 Class 类模板
  public static synchronized void sendMs() throws InterruptedException {

​    TimeUnit.SECONDS.sleep(4);

​    System.out.println("发短信");

  }
// 普通的同步方法 锁的调用者

  public synchronized void call() {

​    System.out.println("打电话");

  }

  public void hello() {

​    System.out.println("hello");

  }

}

输出结果
打电话
发短信
原因:两把锁锁的不是同一个东西

小解

new 出来的 this 是具体的一个对象,对象有多个

static Class 是唯一的一个模板

6.集合不安全

List 不安全

//java.util.ConcurrentModificationException 并发修改异常!

public class ListTest {

  public static void main(String[] args) {

​    List<Object> arrayList = new ArrayList<>();for(int i=1;i<=10;i++){new Thread(()->{

​        arrayList.add(UUID.randomUUID().toString().substring(0,5));

​        System.out.println(arrayList);},String.valueOf(i)).start();}
  }

}

会导致 java.util.ConcurrentModificationException 并发修改异常!

ArrayList 在并发情况下是不安全的

解决方案:

public class ListTest {

  public static void main(String[] args) {/**

​     \* 解决方案

​     \* 1. List<String> list = new Vector<>();

​     \* 2. List<String> list = Collections.synchronizedList(new ArrayList<>());

​     \* 3. List<String> list = new CopyOnWriteArrayList<>();

​     */

​    List<String> list = new CopyOnWriteArrayList<>();for (int i = 1; i <=10; i++) {new Thread(() -> {

​        list.add(UUID.randomUUID().toString().substring(0,5));

​        System.out.println(list);},String.valueOf(i)).start();}

  }

}

CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

CopyOnWriteArrayList比Vector厉害在哪里?

Vector底层是使用synchronized关键字来实现的:效率特别低下。

img

CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

img

set 不安全

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

解决方案还是两种:

使用Collections工具类的synchronized包装的Set类

使用CopyOnWriteArraySet 写入复制的JUC解决方案

public class SetTest {

  public static void main(String[] args) {/**

​     \* 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());

​     \* 2. Set<String> set = new CopyOnWriteArraySet<>();

​     */

//    Set<String> set = new HashSet<>();

​    Set<String> set = new CopyOnWriteArraySet<>();for (int i = 1; i <= 30; i++) {new Thread(() -> {

​        set.add(UUID.randomUUID().toString().substring(0,5));

​        System.out.println(set);},String.valueOf(i)).start();}

  }

}

HashSet底层是什么?

hashSet底层就是一个HashMap;

img

Map不安全

//map 是这样用的吗? 不是,工作中不使用这个

//默认等价什么? new HashMap<>(16,0.75);

Map<String, String> map = new HashMap<>();

//加载因子、初始化容量

默认加载因子是0.75,默认的初始容量是16

image-20200811150700927

同样的HashMap基础类也存在并发修改异常!

public class MapTest {

  public static void main(String[] args) {

​    //map 是这样用的吗? 不是,工作中不使用这个

​    //默认等价什么? new HashMap<>(16,0.75);

​    /**

​     \* 解决方案

​     \* 1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

​     \* Map<String, String> map = new ConcurrentHashMap<>();

​     */

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

​    //加载因子、初始化容量

​    for (int i = 1; i < 100; i++) {

​      new Thread(()->{

​        map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));

​        System.out.println(map);

​      },String.valueOf(i)).start();

​    }

  }

}

TODO:研究ConcurrentHashMap底层原理:

7.Callable

1、可以有返回值;

2、可以抛出异常;

3、方法不同,run()/call()

(Runnable不返回结果,不能抛出被检查的异常)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ee0TeqsJ-1606630223447)(C:\Users\14611\AppData\Roaming\Typora\typora-user-images\image-20201127154839799.png)]

public class CallableTest {

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

​    for (int i = 1; i < 10; i++) {

​      MyThread1 myThread1 = new MyThread1();
​      FutureTask<Integer> futureTask = new FutureTask<>(myThread1);

​      // 放入Thread中使用,结果会被缓存

​      new Thread(futureTask,String.valueOf(i)).start();

​      // 这个get方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信

​      int a = futureTask.get();

​      System.out.println("返回值:" + s);

​    }
  }
}

class MyThread1 implements Callable<Integer> {

 

  @Override

  public Integer call() throws Exception {

​    System.out.println("call()");

​    return 1024;

  }

}
细节:
1、有缓存
2、结果可能需要等待,会阻塞!


8.常用的辅助类

1)CountDownLatch

public class CountDownLatchDemo {

  public static void main(String[] args) throws InterruptedException {// 总数是6

​    CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <= 6; i++) {new Thread(() -> {

​        System.out.println(Thread.currentThread().getName() + "==> Go Out");

​        countDownLatch.countDown(); // 每个线程都数量 -1},String.valueOf(i)).start();}

​    countDownLatch.await(); // 等待计数器归零 然后向下执行

​    System.out.println("close door");

  }

}
//每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行!

主要方法:

countDown 减一操作;

await 等待计数器归零

await 等待计数器归零,就唤醒,再继续向下运行

2)CyclickBarrier

其实

public class CyclicBarrierDemo {

  public static void main(String[] args) {// 主线程

​    CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {

​      System.out.println("召唤神龙");});for (int i = 1; i <= 7; i++) {// 子线程int finalI = i;new Thread(() -> {

​        System.out.println(Thread.currentThread().getName() + "收集了第" + finalI + "颗龙珠");try {

​          cyclicBarrier.await(); // 加法计数 等待} catch (InterruptedException e) {

​          e.printStackTrace();} catch (BrokenBarrierException e) {

​          e.printStackTrace();}}).start();}

  }

}

3)Semaphore

Semaphore:信号量

public class SemaphoreDemo {

  public static void main(String[] args) {// 线程数量,停车位,限流

​    Semaphore semaphore = new Semaphore(3);for (int i = 0; i <= 6; i++) {new Thread(() -> {// acquire() 得到try {

​          semaphore.acquire();

​          System.out.println(Thread.currentThread().getName() + "抢到车位");

​          TimeUnit.SECONDS.sleep(2);

​          System.out.println(Thread.currentThread().getName() + "离开车位");}catch (Exception e) {

​          e.printStackTrace();}finally {

​          semaphore.release(); // release() 释放}}).start();}

  }

}
Thread-1抢到车位

Thread-0抢到车位

Thread-2抢到车位

Thread-0离开车位

Thread-2离开车位

Thread-1离开车位

Thread-5抢到车位

Thread-3抢到车位

Thread-4抢到车位

Thread-5离开车位

Thread-3离开车位

Thread-6抢到车位

Thread-4离开车位

Thread-6离开车位

原理:

semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!

semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!

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

9. 读写锁

public class ReadWriteLockDemo {

  public static void main(String[] args) {

​    MyCache myCache = new MyCache();int num = 6;for (int i = 1; i <= num; i++) {int finalI = i;new Thread(() -> {

 

​        myCache.write(String.valueOf(finalI), String.valueOf(finalI));},String.valueOf(i)).start();}for (int i = 1; i <= num; i++) {int finalI = i;new Thread(() -> {

 

​        myCache.read(String.valueOf(finalI));},String.valueOf(i)).start();}

  }

}

/**

 \* 方法未加锁,导致写的时候被插队

 */

class MyCache {

  private volatile Map<String, String> map = new HashMap<>();

 

  public void write(String key, String value) {

​    System.out.println(Thread.currentThread().getName() + "线程开始写入");

​    map.put(key, value);

​    System.out.println(Thread.currentThread().getName() + "线程写入ok");

  }

 

  public void read(String key) {

​    System.out.println(Thread.currentThread().getName() + "线程开始读取");

​    map.get(key);

​    System.out.println(Thread.currentThread().getName() + "线程写读取ok");

  }

}
2线程开始写入

2线程写入ok

3线程开始写入

3线程写入ok

1线程开始写入  # 插入了其他线程的写入,导致数据不一致

4线程开始写入

4线程写入ok

1线程写入ok

6线程开始写入

6线程写入ok

5线程开始写入

5线程写入ok

1线程开始读取

1线程写读取ok

2线程开始读取

2线程写读取ok

3线程开始读取

3线程写读取ok

4线程开始读取

4线程写读取ok

5线程开始读取

6线程开始读取

6线程写读取ok

5线程写读取ok

所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。

我们也可以采用synchronized这种重量锁轻量锁 lock去保证数据的可靠。

但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证

public class ReadWriteLockDemo {

  public static void main(String[] args) {

​    MyCache2 myCache = new MyCache2();

​    int num = 6;

​    for (int i = 1; i <= num; i++) {

​      int finalI = i;

​      new Thread(() -> {

 

​        myCache.write(String.valueOf(finalI), String.valueOf(finalI));

 

​      },String.valueOf(i)).start();

​    }

 

​    for (int i = 1; i <= num; i++) {

​      int finalI = i;

​      new Thread(() -> {

 

​        myCache.read(String.valueOf(finalI));

 

​      },String.valueOf(i)).start();

​    }

  }

 

}

class MyCache2 {

  private volatile Map<String, String> map = new HashMap<>();

  private ReadWriteLock lock = new ReentrantReadWriteLock();

 

  public void write(String key, String value) {

​    lock.writeLock().lock(); // 写锁

​    try {

​      System.out.println(Thread.currentThread().getName() + "线程开始写入");

​      map.put(key, value);

​      System.out.println(Thread.currentThread().getName() + "线程写入ok");

 

​    }finally {

​      lock.writeLock().unlock(); // 释放写锁

​    }

  }

 

  public void read(String key) {

​    lock.readLock().lock(); // 读锁

​    try {

​      System.out.println(Thread.currentThread().getName() + "线程开始读取");

​      map.get(key);

​      System.out.println(Thread.currentThread().getName() + "线程写读取ok");

​    }finally {

​      lock.readLock().unlock(); // 释放读锁

​    }

  }

}

1线程开始写入

1线程写入ok

6线程开始写入

6线程写入ok

3线程开始写入

3线程写入ok

2线程开始写入

2线程写入ok

5线程开始写入

5线程写入ok

4线程开始写入

4线程写入ok

1线程开始读取

5线程开始读取

2线程开始读取

1线程写读取ok

3线程开始读取

2线程写读取ok

6线程开始读取

6线程写读取ok

5线程写读取ok

4线程开始读取

4线程写读取ok

3线程写读取ok

10. 阻塞队列

img

img

1)BlockQueue

是Collection的一个子类

什么情况下我们会使用阻塞队列

多线程并发处理、线程池

img

BlockingQueue 有四组api

/**

   \* 抛出异常 add()

   */

  public static void test1(){//需要初始化队列的大小

​    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

​    System.out.println(blockingQueue.add("a"));

​    System.out.println(blockingQueue.add("b"));

​    System.out.println(blockingQueue.add("c"));//抛出异常:java.lang.IllegalStateException: Queue full

//    System.out.println(blockingQueue.add("d"));

​    System.out.println(blockingQueue.remove());

​    System.out.println(blockingQueue.remove());

​    System.out.println(blockingQueue.remove());//如果多移除一个//这也会造成 java.util.NoSuchElementException 抛出异常

​    System.out.println(blockingQueue.remove());

  }

=======================================================================================

/**

   \* 不抛出异常,有返回值  offer()

   */

  public static void test2(){

​    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

​    System.out.println(blockingQueue.offer("a"));

​    System.out.println(blockingQueue.offer("b"));

​    System.out.println(blockingQueue.offer("c"));//添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常

​    System.out.println(blockingQueue.offer("d"));

​    System.out.println(blockingQueue.poll());

​    System.out.println(blockingQueue.poll());

​    System.out.println(blockingQueue.poll());//弹出 如果没有元素 只会返回null 不会抛出异常

​    System.out.println(blockingQueue.poll());

  }

=======================================================================================

/**

   \* 等待 一直阻塞 put()

   */

  public static void test3() throws InterruptedException {

​    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);//一直阻塞 不会返回

​    blockingQueue.put("a");

​    blockingQueue.put("b");

​    blockingQueue.put("c");//如果队列已经满了, 再进去一个元素 这种情况会一直等待这个队列 什么时候有了位置再进去,程序不会停止

//    blockingQueue.put("d");

​    System.out.println(blockingQueue.take());

​    System.out.println(blockingQueue.take());

​    System.out.println(blockingQueue.take());//如果我们再来一个 这种情况也会等待,程序会一直运行 阻塞

​    System.out.println(blockingQueue.take());

  }

=======================================================================================

/**

   \* 等待 超时阻塞

   \* 这种情况也会等待队列有位置 或者有产品 但是会超时结束

   */

  public static void test4() throws InterruptedException {

​    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

​    blockingQueue.offer("a");

​    blockingQueue.offer("b");

​    blockingQueue.offer("c");

​    System.out.println("开始等待");

​    blockingQueue.offer("d",2, TimeUnit.SECONDS); //超时时间2s 等待如果超过2s就结束等待

​    System.out.println("结束等待");

​    System.out.println("===========取值==================");

​    System.out.println(blockingQueue.poll());

​    System.out.println(blockingQueue.poll());

​    System.out.println(blockingQueue.poll());

​    System.out.println("开始等待");

​    blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了

​    System.out.println("结束等待");

  }

2)SynchronousQueue同步队列

同步队列 没有容量,也可以视为容量为1的队列;

进去一个元素,必须等待取出来之后,才能再往里面放入一个元素

put方法 和 take方法;

SynchronizedQueue 和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

package com.marchsoft.queue; 

import java.util.concurrent.BlockingDeque;

import java.util.concurrent.BlockingQueue;

public class SynchronousQueue {

  public static void main(String[] args) {

​    BlockingQueue<String> synchronousQueue = new java.util.concurrent.SynchronousQueue<>();// 往queue中添加元素new Thread(() -> {try {

​        System.out.println(Thread.currentThread().getName() + "put 01");

​        synchronousQueue.put("1");

​        System.out.println(Thread.currentThread().getName() + "put 02");

​        synchronousQueue.put("2");

​        System.out.println(Thread.currentThread().getName() + "put 03");

​        synchronousQueue.put("3");} catch (InterruptedException e) {

​        e.printStackTrace();}}).start();// 取出元素new Thread(()-> {try {

​        System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());

​        System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());

​        System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());}catch (InterruptedException e) {

​        e.printStackTrace();}}).start();

  }
}
Thread-0put 01

Thread-1take1

Thread-0put 02

Thread-1take2

Thread-0put 03

Thread-1take3

11. 线程池(重点)

线程池:三大方式、七大参数、四种拒绝策略

池化技术

程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术

线程池、JDBC的连接池、内存池、对象池 等等。。。。

资源的创建、销毁十分消耗资源

池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

1)线程池的好处:

1、降低资源的消耗;

2、提高响应的速度;

3、方便管理;

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

2)线程池:三大方法

//工具类 Executors 三大方法;

public class Demo01 {

  public static void main(String[] args) {

​    ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
​    ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
​    ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的//线程池用完必须要关闭线程池try {for (int i = 1; i <=100 ; i++) {//通过线程池创建线程

​        threadPool.execute(()->{

​          System.out.println(Thread.currentThread().getName()+ " ok");});}} catch (Exception e) {

​      e.printStackTrace();} finally {

​      threadPool.shutdown();}

  }

}

3)七大参数

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;

}

阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。

手动创建一个线程池

public class PollDemo {

  public static void main(String[] args) {/**
    * new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
    */
   // 获取cpu 的核数int max = Runtime.getRuntime().availableProcessors();

​    ExecutorService service =new ThreadPoolExecutor(2,

​        max,3,

​        TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),

​        Executors.defaultThreadFactory(),new ThreadPoolExecutor..DiscardOldestPolicy());//队列满了,尝试去和最早的竞争,也不会抛出异常!try {
    // 最大承载:Deque + max
// 超过 RejectedExecutionExceptionfor (int i = 1; i <= 10; i++) {
    // 使用了线程池之后,使用线程池来创建线程

​        service.execute(() -> {

​          System.out.println(Thread.currentThread().getName() + "ok");});}}catch (Exception e) {

​      e.printStackTrace();}finally {
// 线程池用完,程序结束,关闭线程池
​      service.shutdown();}

  }

}

4)4种拒绝策略

  1. new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

超出最大承载,就会抛出异常:队列容量大小+maxPoolSize

  1. new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理

  2. new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。

  3. new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

5)如何设置线程池的大小

1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

// 获取cpu 的核数

​    int max = Runtime.getRuntime().availableProcessors();

​    ExecutorService service =new ThreadPoolExecutor(

​        2,

​        max,

​        3,

​        TimeUnit.SECONDS,

​        new LinkedBlockingDeque<>(3),

​        Executors.defaultThreadFactory(),

​        new ThreadPoolExecutor.AbortPolicy()

​    );

2、I/O密集型:

在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

12. 四大函数式接口(重点)

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aART7P7b-1606630223455)(C:\Users\14611\AppData\Roaming\Typora\typora-user-images\image-20201127163854720.png)]

1)Function 函数型接口

package com.kuang.function;
import java.util.function.Function;
/**
* Function 函数型接口, 有一个输入参数,有一个输出
* 只要是 函数型接口 可以 用 lambda表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
//
// Function<String,String> function = new Function<String,String>() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function<String,String> function = (str)->{return str;};
System.out.println(function.apply("asd"));
}
}

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

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

3)Suppier 供给型接口

import java.util.function.Supplier;
/** * Supplier 供给型接口 没有参数,只有返回值 */
public class Demo04 { public static void main(String[] args) { 
    // Supplier supplier = new Supplier() { 
    // @Override 
    // public Integer get() { 
    	// System.out.println("get()"); 
    	// return 1024; 
    // } 
    // }; 
    Supplier supplier = ()->{ return 1024; };              System.out.println(supplier.get()); } }

4)Consummer 消费型接口

import java.util.function.Consumer; 
/** * Consumer 消费型接口: 只有输入,没有返回值 */
public class Demo03 {
    public static void main(String[] args) { 
        // Consumer consumer = new Consumer() {
        // @Override 
        // public void accept(String str) {
        // System.out.println(str);
        // } 
        // };
        Consumer consumer = (str)->{System.out.println(str);};    					consumer.accept("sdadasd"); }
}

13. Stream 流式计算

/**

 \* Description:

 \* 题目要求: 用一行代码实现

 \* 1. Id 必须是偶数

 \* 2.年龄必须大于23

 \* 3. 用户名转为大写

 \* 4. 用户名倒序

 \* 5. 只能输出一个用户

 **/

 

public class StreamDemo {

  public static void main(String[] args) {

​    User u1 = new User(1, "a", 23);

​    User u2 = new User(2, "b", 23);

​    User u3 = new User(3, "c", 23);

​    User u4 = new User(6, "d", 24);

​    User u5 = new User(4, "e", 25);

 

​    List<User> list = Arrays.asList(u1, u2, u3, u4, u5);// lambda、链式编程、函数式接口、流式计算

​    list.stream().filter(user -> {return user.getId()%2 == 0;}).filter(user -> {return user.getAge() > 23;}).map(user -> {return user.getName().toUpperCase();}).sorted((user1, user2) -> {return user2.compareTo(user1);}).limit(1).forEach(System.out::println);

  }

}

14. ForkJoin

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:MapReduce 核心思想->把大任务拆分为小任务!

1)ForkJoin 特点: 工作窃取!

实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!

2)如何使用ForkJoin?

1、通过ForkJoinPool来执行

2、计算任务 execute(ForkJoinTask<?> task)

3、计算类要去继承ForkJoinTask;

**ForkJoin 的计算类

package com.kuang.forkjoin;

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;

/**
 * 求和计算的任务!
 * 3000 6000(ForkJoin) 9000(Stream并行流)
 * // 如何使用 forkjoin
 * // 1、forkjoinPool 通过它来执行
 * // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
 * // 3. 计算类要继承 ForkJoinTask
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start; // 1
    private Long end; // 1990900000
    // 临界值
    private Long temp = 10000L;

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

    // 计算方法
    @Override
    protected Long compute() {
        if ((end - start) < temp) {
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列

            return task1.join() + task2.join();
        }
    }
}
/**
 * 同一个任务,别人效率高你几十倍!
 */
public class Test {
    public static void main(String[] args) throws ExecutionException,
            InterruptedException {
// test1(); // 12224
// test2(); // 10038
// test3(); // 153
    }
    // 普通程序员
    public static void test1(){
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间:"+(end-start));
    }
    // 会使用ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间:"+(end-start));
    }
    public static void test3(){
        long start = System.currentTimeMillis();
// Stream并行流 () (]
        long sum = LongStream.rangeClosed(0L,
                10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        
                .
                        System.out.println("sum="+"时间:"+(end-start));
    }
}

15. 异步回调

Future 设计的初衷:对将来的某个事件结果进行建模!

其实就是前端 --> 发送ajax异步请求给后端

但是我们平时都使用CompletableFuture

(1)没有返回值的runAsync异步回调

package com.kuang.future;

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

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

        // 有返回值的 supplyAsync 异步回调
        // ajax,成功和失败的回调
        // 返回的是错误信息;
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
            int i = 10/0;
            return 1024;
        });

        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t); // 正常的返回结果
            System.out.println("u=>" + u); // 错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; // 可以获取到错误的返回结果
        }).get());

        /**
         * succee Code 200
         * error Code 404 500
         */
    }
}

16. JMM

1)对Volatile 的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

如何实现可见性

volatile变量修饰的共享变量在进行写操作的时候回多出一行汇编:

0x01a3de1d:movb $0×0,0×1104800(%esi);0x01a3de24**:lock** addl $0×0,(%esp);

Lock前缀的指令在多核处理器下会引发两件事情。

1)将当前处理器缓存行的数据写回到系统内存。

2)这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。

多处理器总线嗅探:

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址呗修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。

2)什么是JMM?

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存;

2、线程加锁前,必须读取主存中的最新值到工作内存中;

3、加锁和解锁是同一把锁;

线程中分为 工作内存、主内存

8种操作:

Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;

load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;

Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;

assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;

store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;

write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;

lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;

unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

JMM对这8种操作给了相应的规定:

不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write

不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

不允许一个线程将没有assign的数据从工作内存同步回主内存

一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作

一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

对一个变量进行unlock操作之前,必须把此变量同步回主内存

遇到问题:程序不知道主存中的值已经被修改过了!;

17. volatile

1)保证可见性

public class JMMDemo01 {

// 如果不加volatile 程序会死循环

// 加了volatile是可以保证可见性的

package com.kuang.tvolatile;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    // 不加 volatile 程序就会死循环!
    // 加 volatile 可以保证可见性
    private volatile static int num = 0;

    public static void main(String[] args) { // main

        new Thread(()->{ // 线程 1 对主内存的变化不知道的
            while (num==0){

            }
        }).start();

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

        num = 1;
        System.out.println(num);

    }
}

2)不保证原子性

原子性:不可分割;

线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

/**
 * 不保证原子性
 * number <=2w
 * 
 */
public class VDemo02 {

    private static volatile int number = 0;

    public static void add(){
        number++; 
        //++ 不是一个原子性操作,是两个~3个操作
        //
    }

    public static void main(String[] args) {
        //理论上number  === 20000

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }
}


如果不加lock和synchronized ,怎么样保证原子性?

使用原子类

public class VDemo02 {

    private static volatile AtomicInteger number = new AtomicInteger();

    public static void add(){
//        number++;
        number.incrementAndGet();  //底层是CAS保证的原子性
    }

    public static void main(String[] args) {
        //理论上number  === 20000

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }
}

这些类的底层都直接和操作系统挂钩!是在内存中修改值。

Unsafe类是一个很特殊的存在;

原子类为什么这么高级?

3)禁止指令重排

什么是指令重排?

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

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

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

int x=1; //1

int y=2; //2

x=x+5;  //3

y=x*x;  //4

 

//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324

//可不可能是 4123? 不可能的

1234567

可能在线程A中会出现,先执行b=1,然后再执行x=a;

在B线程中可能会出现,先执行a=2,然后执行y=b;

那么就有可能结果如下:x=2; y=1.

volatile可以避免指令重排:

volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

内存屏障:CPU指令。作用:

1、保证特定的操作的执行顺序;

2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

4)总结

volatile可以保证可见性;

不能保证原子性

由于内存屏障,可以保证避免指令重排的现象产生

面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式

18. 玩转单例模式

饿汉式、DCL懒汉式

1)饿汉式

/**

 \* 饿汉式单例

 */
public class Hungry {
  /**

   \* 可能会浪费空间

   */

  private byte[] data1=new byte[1024*1024];

  private byte[] data2=new byte[1024*1024];

  private byte[] data3=new byte[1024*1024];

  private byte[] data4=new byte[1024*1024];

  private Hungry(){
  }

  private final static Hungry hungry = new Hungry();
 
  public static Hungry getInstance(){return hungry;

  }


}
2,DCL懒汉式
package com.kuang.single;

public class lazyManli {
    private lazyManli() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    //加volatile关键字,防止指令重排
    private volatile static lazyManli lazyManli;
    //双重检测-锁
    public static lazyManli getInstance() {
        if (lazyManli == null) {
            synchronized (lazyManli.class) {
                if (lazyManli == null) {
                    lazyManli = new lazyManli();
                    //不是原子操作
                    /**
                     * 1,分配内存空间
                     * 2,执行构造方法,初始化对象
                     * 3,把这个对象指向这个空间
                     *
                     *
                     * 123正常执行顺序
                     * 132//指令重排后,拿到空对象
                     */
                }

            }
        }
        return lazyManli;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lazyManli.getInstance();
            }).start();
        }
    }
}

3)静态内部类

package com.kuang.single;

// 静态内部类
public class Holder {
    private Holder(){

    }

    public static Holder getInstace(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }

}

单例不安全, 因为反射

4)通过反射破坏

package com.kuang.single;

import com.sun.corba.se.impl.orbutil.CorbaResourceUtil;

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

// 懒汉式单例
// 道高一尺,魔高一丈!
public class LazyMan {
//加标志位
    private static boolean qinjiang = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (qinjiang == false){
                qinjiang = true;//标志位
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan lazyMan;

    // 双重检测锁模式的 懒汉式单例  DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    // 反射!
    public static void main(String[] args) throws Exception {
//        LazyMan instance = LazyMan.getInstance();
        //破坏标志位私有权限
        Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
        qinjiang.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();
//修改标志位的值
        qinjiang.set(instance,false);
        
        LazyMan instance2 = declaredConstructor.newInstance();
        
        System.out.println(instance);
        System.out.println(instance2);
    }

}

/**
 * 1. 分配内存空间
 * 2、执行构造方法,初始化对象
 * 3、把这个对象指向这个空间
 *
 * 123
 * 132 A
 *     B // 此时lazyMan还没有完成构造
 */
   

4)枚举(安全,因为枚举的构造函数只能由编译器调用,反射无效)

package com.kuang.single;

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

// enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }

}

class Test{

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        // NoSuchMethodException: com.kuang.single.EnumSingle.<init>()
        System.out.println(instance1);
        System.out.println(instance2);

    }

}

使用枚举,我们就可以防止反射破坏了。

img

19. 深入理解CAS

1)什么是CAS?

大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构

public class casDemo {

  //CAS : compareAndSet 比较并交换

  public static void main(String[] args) {

​    AtomicInteger atomicInteger = new AtomicInteger(2020);//boolean compareAndSet(int expect, int update)//期望值、更新值//如果实际值 和 我的期望值相同,那么就更新//如果实际值 和 我的期望值不同,那么就不更新

​    System.out.println(atomicInteger.compareAndSet(2020, 2021));

​    System.out.println(atomicInteger.get());//因为期望值是2020 实际值却变成了2021 所以会修改失败//CAS 是CPU的并发原语

​    atomicInteger.getAndIncrement(); //++操作

​    System.out.println(atomicInteger.compareAndSet(2020, 2021));

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

  }

}

Unsafe 类

img

img

2)总结

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

缺点:

循环会耗时;

cpu操作一次性只能保证一个共享变量的原子性;

它会存在ABA问题

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

线程1:期望值是1,要变成2;

线程2:两个操作:

1、期望值是1,变成3

2、期望是3,变成1

所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;

public class casDemo {

  //CAS : compareAndSet 比较并交换

  public static void main(String[] args) {

​    AtomicInteger atomicInteger = new AtomicInteger(2020);

 

​    System.out.println(atomicInteger.compareAndSet(2020, 2021));

​    System.out.println(atomicInteger.get());//boolean compareAndSet(int expect, int update)//期望值、更新值//如果实际值 和 我的期望值相同,那么就更新//如果实际值 和 我的期望值不同,那么就不更新

​    System.out.println(atomicInteger.compareAndSet(2021, 2020));

​    System.out.println(atomicInteger.get());//因为期望值是2020 实际值却变成了2021 所以会修改失败//CAS 是CPU的并发原语

//    atomicInteger.getAndIncrement(); //++操作

​    System.out.println(atomicInteger.compareAndSet(2020, 2021));

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

  }

}

20. 原子引用

解决ABA问题(一个小偷,把别人家的钱偷了之后又还了回来,还是原来的钱吗,你老婆出轨之后又回来,还是原来的老婆吗?ABA问题也一样,如果不好好解决就会带来大量的问题。最常见的就是资金问题,也就是别人如果挪用了你的钱,在你发现之前又还了回来。但是别人却已经触犯了法律。),对应的思想:就是使用了乐观锁~

带版本号的 原子操作!

Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

带版本号的原子操作

package com.marchsoft.lockdemo;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo {

  /**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题

   \* 正常在业务操作,这里面比较的都是一个个对象
   这里使用的是AtomicStampedReference的compareAndSet函数,这里面有四个参数:

compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)。

(1)第一个参数expectedReference:表示预期值。

(2)第二个参数newReference:表示要更新的值。

(3)第三个参数expectedStamp:表示预期的时间戳。

(4)第四个参数newStamp:表示要更新的时间戳。

   */

  static AtomicStampedReference<Integer> atomicStampedReference = new

​      AtomicStampedReference<>(1, 1);

 

  // CAS compareAndSet : 比较并交换!

  public static void main(String[] args) {new Thread(() -> {int stamp = atomicStampedReference.getStamp(); // 获得版本号

​      System.out.println("a1=>" + stamp);

​      

​      try {

​        TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {

​        e.printStackTrace();}// 修改操作时,版本号更新 + 1

​      atomicStampedReference.compareAndSet(1, 2,

​          atomicStampedReference.getStamp(),

​          atomicStampedReference.getStamp() + 1);

​      

​      System.out.println("a2=>" + atomicStampedReference.getStamp());// 重新把值改回去, 版本号更新 + 1

​      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(2);} catch (InterruptedException e) {

​        e.printStackTrace();}

​      System.out.println(atomicStampedReference.compareAndSet(1, 3,

​          stamp, stamp + 1));

​      System.out.println("b2=>" + atomicStampedReference.getStamp());}, "b").start();

  }

}

21. 各种锁的理解

1)公平锁,非公平锁

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

/**

 \* Creates an instance of {@code ReentrantLock}.

 \* This is equivalent to using {@code ReentrantLock(false)}.

 */

public ReentrantLock() {

  sync = new NonfairSync();

}

非公平锁:非常不公平,允许插队,可以改变顺序(默认都是非公平)

/**

 \* Creates an instance of {@code ReentrantLock} with the

 \* given fairness policy.

 *

 \* @param fair {@code true} if this lock should use a fair ordering policy

 */

public ReentrantLock(boolean fair) {

  sync = fair ? new FairSync() : new NonfairSync();

}

2)可重入锁

Synchonized 锁

public class Demo01 {

  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");

  }

}

Lock 锁

//lock

public class Demo02 {

 

  public static void main(String[] args) {

​    Phone2 phone = new Phone2();new Thread(()->{

​      phone.sms();},"A").start();new Thread(()->{

​      phone.sms();},"B").start();

  }

 

}

class Phone2{

 

  Lock lock=new ReentrantLock();

 

  public void sms(){

​    lock.lock(); //细节:这个是两把锁,两个钥匙//lock锁必须配对,否则就会死锁在里面try {

​      System.out.println(Thread.currentThread().getName()+"=> sms");call();//这里也有一把锁} catch (Exception e) {

​      e.printStackTrace();}finally {

​      lock.unlock();}

  }

  public void call(){

​    lock.lock();try {

​      System.out.println(Thread.currentThread().getName() + "=> call");}catch (Exception e){

​      e.printStackTrace();}finally {

​      lock.unlock();}

  }

}

lock锁必须配对,相当于lock和 unlock 必须数量相同;

在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

3)自旋锁

spinlock

package com.kuang.lock;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinlockDemo {

    // int   0
    // Thread  null
    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);
    }



}

package com.kuang.lock;

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

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        // 底层使用的自旋锁CAS
        SpinlockDemo lock = new SpinlockDemo();


        new Thread(()-> {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()-> {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },"T2").start();

    }
}

运行结果:

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。

4)死锁

package com.ogj.lock;

 

import java.util.concurrent.TimeUnit;

 

public class DeadLock {

  public static void main(String[] args) {

​    String lockA= "lockA";

​    String lockB= "lockB";new Thread(new MyThread(lockA,lockB),"t1").start();new Thread(new MyThread(lockB,lockA),"t2").start();

  }

}

 

class MyThread implements Runnable{

 

  private String lockA;

  private String lockB;

 

  public MyThread(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;

  }

 

  @Override

  public void run() {synchronized (lockA){

​      System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);try {

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

​        e.printStackTrace();}synchronized (lockB){

​        System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);}}

  }

}
如何解开死锁

1、使用 jps -l 定位进程号,jdk的bin目录下: 有一个jps

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

面试,工作中! 排查问题:

1、日志 9

2、堆栈 1

.currentThread().getName()+"=> sms");

​ call();//这里也有一把锁,自动拿到里面的锁

}

public synchronized void call(){

​ System.out.println(Thread.currentThread().getName()+"=> call");

}

}


Lock 锁

```java
//lock

public class Demo02 {

 

  public static void main(String[] args) {

​    Phone2 phone = new Phone2();

​    new Thread(()->{

​      phone.sms();

​    },"A").start();

​    new Thread(()->{

​      phone.sms();

​    },"B").start();

  }

 

}

class Phone2{

 

  Lock lock=new ReentrantLock();

 

  public void sms(){

​    lock.lock(); //细节:这个是两把锁,两个钥匙

​    //lock锁必须配对,否则就会死锁在里面

​    try {

​      System.out.println(Thread.currentThread().getName()+"=> sms");

​      call();//这里也有一把锁

​    } catch (Exception e) {

​      e.printStackTrace();

​    }finally {

​      lock.unlock();

​    }

  }

  public void call(){

​    lock.lock();

​    try {

​      System.out.println(Thread.currentThread().getName() + "=> call");

​    }catch (Exception e){

​      e.printStackTrace();

​    }

​    finally {

​      lock.unlock();

​    }

  }

}

lock锁必须配对,相当于lock和 unlock 必须数量相同;

在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

3)自旋锁

spinlock

package com.kuang.lock;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinlockDemo {

    // int   0
    // Thread  null
    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);
    }



}

package com.kuang.lock;

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

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        // 底层使用的自旋锁CAS
        SpinlockDemo lock = new SpinlockDemo();


        new Thread(()-> {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()-> {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },"T2").start();

    }
}

运行结果:

[外链图片转存中…(img-c1HMUkqi-1606630223473)]

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。

4)死锁

![外链图片转存中…(img-OP44lRSP-1606630223474)]

package com.ogj.lock;

 

import java.util.concurrent.TimeUnit;

 

public class DeadLock {

  public static void main(String[] args) {

​    String lockA= "lockA";

​    String lockB= "lockB";new Thread(new MyThread(lockA,lockB),"t1").start();new Thread(new MyThread(lockB,lockA),"t2").start();

  }

}

 

class MyThread implements Runnable{

 

  private String lockA;

  private String lockB;

 

  public MyThread(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;

  }

 

  @Override

  public void run() {synchronized (lockA){

​      System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);try {

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

​        e.printStackTrace();}synchronized (lockB){

​        System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);}}

  }

}
如何解开死锁

1、使用 jps -l 定位进程号,jdk的bin目录下: 有一个jps

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值