JUC基础【万字篇】

article/2025/10/12 8:34:19

JUC

1、什么是JUC

image-20210828155426978

JUC:指的是java.util三个并发编程工具包

  1. java.util.concurrent
  2. java.util.concurrent.atomic
  3. java.util.concurrent.locks

实现多线程的四种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池

业务:普通的线程代码 Thread

Runnable 没有返回值、效率相比于Callable相对较低!

Runnable接口和Callable接口区别:

  1. 是否有返回值:Runnable无返回值,Callable有返回值
  2. 是否抛出异常:call方法计算一个结果,如果不能这样做,就会抛出异常
  3. 实现方法名称不同,Runnable接口是run方法,Callable接口是call方法

2、线程和进程

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单元。

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单元。

进程:是一个程序,一个进程包含多个线程,且至少包含一个线程。

Java默认有两个线程:main 和 GC。

Java能开启线程吗? start方法开启线程

 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 thenit will be passed up the call stack */}}}//本地方法,底层C++,Java无法操作硬件,由操作系统决定是否创建线程,是否立即创建线程private native void start0();

Java是不能开启线程的,底层是调用start0()是一个native方法,由底层的C++方法编写。java无法直接操作硬件。

线程的几种状态

Thread.State

 public enum State {NEW,//新建     RUNNABLE,//准备就绪       BLOCKED, //阻塞        WAITING,//一直等待TIMED_WAITING,//超时等待,过时不候TERMINATED;//终止}

wait/sleep区别

1.来自不同的类

wait => Object,任何对象实例都能调用

sleep => Thread,Thread的静态方法

2.关于锁的释放

wait会释放锁;sleep不会释放锁,它也不需要占用锁

3.使用范围、捕获异常不同

wait:必须在同步代码块中使用,不需要捕获异常

sleep:可以在任何地方使用,必须要捕获异常

并发、并行

并发编程:并发、并行

并发: 同一时刻多个线程访问同一个资源(多线程共享资源)

​ 例如:春运抢票、电商秒杀

并行: 多项工作一起执行,之后再汇总

​ 例如:泡方便面,电水壶烧水的同时,拆开泡面调料倒入桶中

 System.out.println(Runtime.getRuntime().availableProcessors());//获取cpu的核数

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

管程

Monitor 监视器(就是平常说的锁)

是一种同步机制,保证同一时间内,只有一个线程访问被保护的数据或者代码

jvm同步基于进入和退出,使用管程对象实现的

用户线程和守护线程

用户线程:自定义线程(new Thread())

守护线程:后台中一种特殊的线程,比如垃圾回收

image-20220928232336843

主线程结束了,用户线程还在运行,jvm存活

image-20220928232600747

没有用户线程了,都是守护线程,jvm结束

3、Lock锁(重点)

多线程编程步骤:

  1. 创建资源类,在资源类创建属性和操作方法
  2. 创建多个线程,调用资源类的操作方法

案例:三个售票员同时卖30张票。

传统 synchronized

public class SaleTicketDemo1 {public static void main(String[] args) {//并发:多个线程操作同一个资源类,把资源类丢入线程Ticket ticket = new Ticket();//Runnable接口 -》 函数式接口,lambda表达式:函数式接口的实例new Thread(() -> {for (int i = 0; i < 30; i++) {ticket.sale();}},"A").start();new Thread(() -> {for (int i = 0; i < 30; i++) {ticket.sale();}},"B").start();new Thread(() -> {for (int i = 0; i < 30; i++) {ticket.sale();}},"C").start();}
}/*** 资源类*/
class Ticket {//属性private int num = 30;//synchronized 本质:线程串行化,排队,锁public synchronized void sale() {if (num > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + (num--) + "张票,剩余" + num + "张");}}}

Lock接口

image-20210828171909681

实现类

image-20210828172055385

image-20210828172743204

公平锁:十分公平,先来后到(阳光普照,效率相对低一些)

非公平锁:十分不公平,可以插队(默认,可能会造成线程饿死,但是效率高)

public class SaleTicketDemo2 {public static void main(String[] args) {//并发:多个线程操作同一个资源类,把资源类丢入线程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.new ReentrantLock();* 2.lock.lock(); //加锁* 3.finally  -》 lock.unlock();//解锁**/
class Ticket2 {//属性private int num = 30;// 创建可重入锁Lock lock = new ReentrantLock();public void sale() {lock.lock(); //加锁try {//业务代码if (num > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + (num--) + "张票,剩余" + num + "张");}} finally {lock.unlock();//解锁}}}

synchronized 和 Lock区别

1.synchronized 是内置的Java关键字,Lock是Java的一个接口

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

3.synchronized 会自动释放锁,Lock必须手动释放锁!如果不释放锁,会造成死锁

4.synchronized 线程一在获得锁的情况下阻塞了,第二个线程就只能傻傻的等着;Lock就不一定会等待下去

5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平/公平(可以自己设置,默认非公平锁)

6.synchronized 适合锁少量的同步代码;Lock适合锁大量同步代码!

7.Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时(即大量线程同时竞争),此时Lock的性能要远远优于synchronized

4、线程间通信

生产者和消费者问题

面试:单例模式、排序算法、生产者消费者问题、死锁

资源类操作步骤:判断等待 -》执行业务-》唤醒通知

生产者和消费者问题 synchronized 版

Object类中方法

image-20221002231551301

/*** 线程之间通信问题:生产者和消费者问题!  等待唤醒,通知唤醒* 线程之间交替执行 A B 操作同一个变量 num = 0* A num+1* B num-1*/
public class ProducerAndConsumer {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;//+1public synchronized void increment() throws InterruptedException {if (num != 0) {//等待this.wait();}num++;System.out.println(Thread.currentThread().getName() + "=>" + num);//通知其他线程我+1 完毕了this.notifyAll();}//-1public synchronized void decrement() throws InterruptedException {if (num == 0) {//等待this.wait();}num--;System.out.println(Thread.currentThread().getName() + "=>" + num);//通知其他线程我-1 完毕了this.notifyAll();}
}

image-20221002231247476

问题存在, A B C D 四个线程 虚假唤醒

image-20210828182933101

public class ProducerAndConsumer {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();}
}/*** 资源类* <p>* 判断等待,业务,通知*/
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();}
}

image-20221002233855349

为什么会出现虚假唤醒问题呢?

主要是因为wait方法是从哪里等待就从哪里唤醒(而且wait是不会持有锁的,别的线程直接能拿到锁),if只会判断一次,只要第一次满足条件,等待唤醒就会直接执行下面+1/-1操作,所以才会出现>1或者<0的数甚至出现死锁现象,这些都是虚假唤醒导致的。

防止虚假唤醒问题:if 改为 while

/*** 线程之间通信问题:生产者和消费者问题!  等待唤醒,通知唤醒* 线程之间交替执行 A B 操作同一个变量 num = 0* A num+1* B num-1*/
public class ProducerAndConsumer {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;//+1public synchronized void increment() throws InterruptedException {while (num != 0) {//等待this.wait();}num++;System.out.println(Thread.currentThread().getName() + "=>" + num);//通知其他线程我+1 完毕了this.notifyAll();}//-1public synchronized void decrement() throws InterruptedException {while (num == 0) {//等待this.wait();}num--;System.out.println(Thread.currentThread().getName() + "=>" + num);//通知其他线程我-1 完毕了this.notifyAll();}
}

JUC版的生产者和消费者问题

通过Lock找到Condition(官方文档)image-20210828183902401

代码实现

public class ProducerAndConsumer2 {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();//condition.await(); // 等待//condition.signalAll(); // 唤醒全部//+1public void increment() throws InterruptedException {lock.lock();try {//业务代码while (num != 0) {//等待condition.await();}num++;System.out.println(Thread.currentThread().getName() + "=>" + num);//通知其他线程我+1 完毕了condition.signalAll();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}//-1public void decrement() throws InterruptedException {lock.lock();try {while (num == 0) {//等待condition.await();}num--;System.out.println(Thread.currentThread().getName() + "=>" + num);//通知其他线程我-1 完毕了condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}

多线程编程完整步骤

第一步:创建资源类,在资源类中创建属性和操作方法

第二步:资源类操作方法

(1)判断等待

(2)业务逻辑

(3)通知唤醒

第三步:创建多个线程,调用资源类的操作方法

第四步:防止虚假唤醒

线程间定制通信

线程按约定顺序执行

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

Condition精准的通知和唤醒线程

image-20210828191750451

代码实现:三个线程顺序执行

/*** A B C三个线程顺序执行*/
public class C {public static void main(String[] args) {Resource resource = new Resource();new Thread(() -> {for (int i = 0; i < 10; i++) {resource.printA();}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {resource.printB();}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {resource.printC();}}, "C").start();}
}/*** 资源类 Lock*/
class Resource {private final Lock lock = new ReentrantLock();private final Condition condition1 = lock.newCondition();private final Condition condition2 = lock.newCondition();private final Condition condition3 = lock.newCondition();private int num = 1; // 1A 2B 3Cpublic void printA() {lock.lock();try {//业务 判断 -》 执行 -》 通知while (num != 1) {//等待condition1.await();}num = 2;System.out.println(Thread.currentThread().getName() + "=>AAA");//唤醒,唤醒指定线程Bcondition2.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() + "=>BBB");num = 3;//唤醒线程Ccondition3.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() + "=>CCC");num = 1;//唤醒线程Acondition1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}

5、八锁现象

锁是什么?如何判断锁是谁?

任何类的对象、类对象

深刻理解锁

 * 八锁,就是关于锁的八个问题* 1、标准情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话* 2sendSms()延迟4s情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话*/
public class Test1 {public static void main(String[] args) {Phone phone = new Phone();//锁的存在new Thread(() -> {phone.sendSms();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}},"A").start();new Thread(() -> {phone.call();},"B").start();}
}class Phone{//synchronized 锁的对象是方法的调用者!//两个方法用的是同一把锁,谁先拿到谁执行!public synchronized void sendSms(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}
}
/*** 3、增加了一个普通方法后,先打印发短信还是hello? hello* 4、两个对象,两个同步方法 两个线程先打印 发短信 还是 打电话? 1/打电话 2/发短信*/
public class Test2{public static void main(String[] args) {//两个对象,两个不同的对象,两把锁!Phone2 phone1 = new Phone2();Phone2 phone2 = new Phone2();//锁的存在new Thread(() -> {phone1.sendSms();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}},"A").start();new Thread(() -> {phone2.call();},"B").start();}
}class Phone2{//synchronized 锁的对象是方法的调用者!//两个方法用的是同一把锁,谁先拿到谁执行!public synchronized void sendSms(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}//没有锁,不是同步方法,不受锁的影响public void hello(){System.out.println("hello");}
}
/*** 5.增加两个静态的同步方法 一个对象 两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话* 6.两个对象 两个静态的同步方法 两个线程先打印 发短信 还是 打电话?    1/发短信 2/打电话*/
public class Test3 {public static void main(String[] args) {//两个不同的对象 但是类只会加载一次 共用一把锁Phone3 phone1 = new Phone3();Phone3 phone2 = new Phone3();//锁的存在new Thread(() -> {phone1.sendSms();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}},"A").start();new Thread(() -> {phone2.call();},"B").start();}
}class Phone3{//static静态方法 锁的是类对象 Phone3.classpublic 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("打电话");}
}
/*** 7.一个静态同步方法一个普通同步方法 一个对象 两个线程先打印 发短信 还是 打电话? 打电话 发短信* 8.一个静态同步方法一个普通同步方法 两个对象 两个线程先打印 发短信 还是 打电话? 打电话 发短信*/
public class Test4 {public static void main(String[] args) {Phone4 phone1 = new Phone4();Phone4 phone2 = new Phone4();//锁的存在new Thread(() -> {phone1.sendSms();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}},"A").start();new Thread(() -> {phone2.call();},"B").start();}
}class Phone4{//static静态同步方法 锁的是类对象 Phone3.classpublic static synchronized void sendSms(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}//普通同步方法 锁的是方法调用者public synchronized void call(){System.out.println("打电话");}}

小结:

  • 同步方法,谁先拿到锁谁先执行,同一把锁顺序执行。
  • 普通同步方法锁的是 this 当前对象(即方法的调用者)。
  • 静态同步方法锁的是类对象,类只会加载一次,所以静态同步方法永远锁的都是类对象(XXX.class)。

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。

具体表现为一下3种形式。

  1. 对于普通同步方法,锁是当前实例对象。
  2. 对于静态同步方法,锁是当前类的Class对象。
  3. 对于同步方法块,锁是synchronized括号里配置的对象。

6、集合类不安全

List不安全

//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {public static void main(String[] args) {//并发下 ArrayList不安全/*解决方案1、List<String> list = new Vector<>();2、List<String> list = Collections.synchronizedList(new ArrayList<>());3、List<String> list = new CopyOnWriteArrayList<>();*///CopyOnWrite 写入时复制  COW 计算机程序设计领域的一种优化策略;//多个线程操作的时候 list 读取的时候 固定的 写入(覆盖)//再写入的时候避免覆盖,造成数据问题!//读写分离//CopyOnWriteArrayList 比 Vector 好在哪里?//Vector 底层是synchronized实现效率较低 ; CopyOnWriteArrayList 底层是ReentrantLock实现 效率更高 灵活性也更高List<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i < 10; i++) {new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);}, String.valueOf(i)).start();}}
}

Set不安全

import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;//java.util.ConcurrentModificationException
public class SetTest {public static void main(String[] args) {//HashSet<String> set = new HashSet<>();//解决方案一://Set<String> set = Collections.synchronizedSet(new HashSet<>());//解决方案二:Set<String> set = new CopyOnWriteArraySet<>();for (int i = 0; i < 30; i++) {new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0,5));System.out.println(set);},String.valueOf(i)).start();}}
}

HashSet底层是什么?

HashSet底层是HashMap

public HashSet() {map = new HashMap<>();
}
// add()  set 本质就是map 中的key, key是不可重复的!
public boolean add(E e) {return map.put(e, PRESENT)==null;
}// PRESENT 不变的值!
private static final Object PRESENT = new Object();

Map不安全

回顾HashMap底层

image-20210828213421678

public class MapTest {public static void main(String[] args) {/*并发下 HashMap线程不安全解决方案:1.Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());2.Map<String, Object> map = new ConcurrentHashMap<>();*///HashMap<String, Object> map = new HashMap<>();//Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());Map<String, Object> map = new ConcurrentHashMap<>();//加载因子,初始容量for (int i = 0; i < 30; i++) {new Thread(() -> {map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));System.out.println(map);}, String.valueOf(i)).start();}}
}

7、Callable

image-20210828214714670

1.可以有返回值

2.可以抛出异常

3.方法不同, run() -> call()

代码测试

public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {new Thread(new MyThread()).start();/*本身Callable接口和Runnable接口毫无关系通过一个Runnable接口的实现类FutureTask,Callable接口与Runnable接口构建了关系,便可以启动线程*///适配类   FutureTask 是 Runnable接口的实现类   构造器 FutureTask(Callable<V> callable)MyThread1 t1 = new MyThread1();FutureTask<Integer> futureTask = new FutureTask<>(t1); //泛型是线程返回值类型/*启动两个线程,只会打印一个`call()...`*/new Thread(futureTask,"A").start(); //怎么启动Callablenew Thread(futureTask,"B").start(); //结果会被缓存,效率高Integer i = futureTask.get(); //获取线程返回值  get()可能会产生阻塞!把他放到最后 或者 使用异步通信来处理!System.out.println(i);}
}class MyThread implements Runnable{@Overridepublic void run() {}
}/*** 泛型是返回值类型*/
class MyThread1 implements Callable<Integer>{@Overridepublic Integer call(){System.out.println("call()...");//耗时的操作return 1024;}
}

注意:

1.有缓存

2.获取结果可能需要等待,会阻塞!

8、常用的辅助类(必会)

8.1 CountDownLatch(减法计数器)

image-20210828222231364

/*** 计数器*/
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {//倒计时 起始为6 必须要执行任务的时候,再使用!CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 0; 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!");}
}

原理:

countDownLatch.countDown(); // 数量-1

countDownLatch.await(); // 等待计数器归0,再向下执行

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

8.2 CyclicBarrier(加法计数器)

image-20210828224252023

public class CyclicBarrierDemo {public static void main(String[] args) {/*集齐7颗龙珠召唤神龙*///召唤神龙的线程CyclicBarrier barrier = new CyclicBarrier(7, () -> System.out.println("成功召唤神龙!"));for (int i = 1; i <= 7; i++) {final int temp = i;new Thread(() -> {System.out.println(Thread.currentThread().getName() + "收集了" + temp + "个龙珠");try {barrier.await(); //等待} catch (Exception e) {e.printStackTrace();}}).start();}}
}

8.3 Semaphore(信号量)

image-20210828225726951

抢车位!

6车 - 3个停车位

public class SemaphoreDemo {public static void main(String[] args) {//线程数量:停车位! 限流!Semaphore semaphore = new Semaphore(3);for (int i = 1; i <= 6; i++) {new Thread(() -> {try {//acquire() 获得semaphore.acquire();System.out.println(Thread.currentThread().getName() + "抢到车位");TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "离开车位");} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(); //release() 释放}},String.valueOf(i)).start();}}
}

原理:

semaphore.acquire(); //获得,如果已经满了,等待,等待被释放为止!

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

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

9、读写锁

写锁:独占锁

读锁:共享锁

读写锁:一个资源可以被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读是共享的

ReadWriteLock

image-20210829160712148

public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 1; i <= 5; i++) {final int temp = i;//写入new Thread(() -> {myCache.put(temp + "", temp + "");}, String.valueOf(i)).start();}for (int i = 1; i <= 5; i++) {final int temp = i;//读取new Thread(() -> {System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + ""));}, String.valueOf(i)).start();}}
}/*** 自定义缓存*/
class MyCache {private volatile Map<String, Object> map = new HashMap<>();/*** 存 写*/public void put(String key, Object value) {System.out.println(Thread.currentThread().getName() + "写入" + key);try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}map.put(key, value);System.out.println(Thread.currentThread().getName() + "写入OK");}/*** 取 读*/public Object get(String key) {System.out.println(Thread.currentThread().getName() + "读取" + key);Object o = map.get(key);System.out.println(Thread.currentThread().getName() + "读取OK");return o;}
}

运行结果:

image-20221007182311404

结果分析:由于是多线程操作,多个线程同时开启。写线程写入数据可能花费一些时间,此时数据还没写入完成,读线程就开始读数据导致读取不到任何数据,这种情况需要加锁控制。

加读写锁:

/*** 独占锁(写锁) 一次只能被一个线程占有* 共享锁(读锁)  多个线程可以同时占有* * ReadWriteLock* 读-读  可以共存!* 读-写  不可共存!* 写-写  不可共存!*/
public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 1; i <= 5; i++) {final int temp = i;//写入new Thread(() -> {myCache.put(temp + "", temp + "");}, String.valueOf(i)).start();}for (int i = 1; i <= 5; i++) {final int temp = i;//读取new Thread(() -> {System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + ""));}, String.valueOf(i)).start();}}
}/*** 自定义缓存*/
class MyCache {private volatile Map<String, Object> map = new HashMap<>();private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();/*** 存 写*/public void put(String key, Object value) {readWriteLock.writeLock().lock();System.out.println(Thread.currentThread().getName() + "写入" + key);try {TimeUnit.MILLISECONDS.sleep(200);map.put(key, value);} catch (InterruptedException e) {e.printStackTrace();} finally {readWriteLock.writeLock().unlock();}System.out.println(Thread.currentThread().getName() + "写入OK");}/*** 取 读*/public Object get(String key) {readWriteLock.readLock().lock();Object o = null;try {System.out.println(Thread.currentThread().getName() + "读取" + key);o = map.get(key);System.out.println(Thread.currentThread().getName() + "读取OK");} catch (Exception e) {e.printStackTrace();} finally {readWriteLock.readLock().unlock();}return o;}
}

此时结果是正确的

image-20221007183216136

结果分析:由于加了读写锁,写锁是独占锁,读锁是共享锁。因此写的过程中不允许有任何操作,当写操作写完之后,可以多个线程共享读。

锁降级:写锁会降级为读锁,读锁不能升级为写锁。

10、阻塞队列

BlockingQueue
image-20210829164117604

image-20210829163923267

image-20210829170254467

什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!

学会使用队列

添加、移除

四组API

方式抛出异常不抛出异常阻塞等待超时等待
添加add()offer()put()offer(E e, long timeout, TimeUnit unit)
移除remove()poll()take()poll(long timeout, TimeUnit unit)
获取队首元素element()peek()--
    /*** 抛出异常*/public static void test1() {//队列的大小ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.add("a"));System.out.println(arrayBlockingQueue.add("b"));System.out.println(arrayBlockingQueue.add("c"));//java.lang.IllegalStateException: Queue full 抛出异常//System.out.println(arrayBlockingQueue.add("d"));System.out.println("**************");System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());//java.util.NoSuchElementException 抛出异常//System.out.println(arrayBlockingQueue.remove());}
    /***有返回值,不抛出异常*/public static void test2(){ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.offer("a"));System.out.println(arrayBlockingQueue.offer("b"));System.out.println(arrayBlockingQueue.offer("c"));//System.out.println(arrayBlockingQueue.offer("d")); // false 不抛出异常!System.out.println("*****");System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll()); // null 不抛出异常!}
    /***等待,阻塞(一直阻塞)*/public static void test3() throws InterruptedException {ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);//一直阻塞arrayBlockingQueue.put("a");arrayBlockingQueue.put("b");arrayBlockingQueue.put("c");//arrayBlockingQueue.put("d"); //队列没有位置了,一直阻塞System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); //没有数据了,一直阻塞}
 /***等待,阻塞(等待超时)*/public static void test4() throws InterruptedException {ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);arrayBlockingQueue.offer("a");arrayBlockingQueue.offer("b");arrayBlockingQueue.offer("c");arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS); //等待两秒,超时退出System.out.println("******");System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());arrayBlockingQueue.poll(2, TimeUnit.SECONDS); //等待两秒,超时退出}

SynchronousQueue 同步队列

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

put()、take()

/*** 同步队列* SynchronousQueue 和 BlockingQueue 不一样,SynchronousQueue 不存储元素* put一个元素,必须从里面take取出来,否则不能再put进去值!(存一个,取一个,循环)*/
public class SynchronousQueueDemo {public static void main(String[] args) {//存一个元素,取一个元素 循环SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + "->put 1");synchronousQueue.put("1");System.out.println(Thread.currentThread().getName() + "->put 2");synchronousQueue.put("2");System.out.println(Thread.currentThread().getName() + "->put 3");synchronousQueue.put("3");} catch (InterruptedException e) {e.printStackTrace();}}, "T1").start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take());} catch (InterruptedException e) {e.printStackTrace();}}, "T2").start();}
}

11、线程池(重点)

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

池化技术

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

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

创建、销毁,十分浪费资源。

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

线程池的好处

1、降低资源的消耗;

2、提高响应的速度;

3、方便线程管理。

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

image-20210829182216583

线程池:三大方法

/*** Executors 工具类:创建线程池  3大方法*/
public class Demo1 {public static void main(String[] args) {//ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程的线程池//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程的线程池ExecutorService threadPool = Executors.newCachedThreadPool();//创建可伸缩线程池try {for (int i = 0; i < 10; i++) {//使用了线程池之后,用线程池来创建线程threadPool.execute(() -> {System.out.println(Thread.currentThread().getName() + " OK");});}} catch (Exception e) {e.printStackTrace();} finally {//线程池用完,程序结束,关闭线程池threadPool.shutdown();}}}

源码分析:七大参数

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>());
}//本质: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;}

手动创建一个线程池

/*** Executors 工具类:创建线程池  3大方法** 4大拒绝策略:* new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里!* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!**/
public class Demo2 {public static void main(String[] args) {//工具类创建//ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程的线程池//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程的线程池//ExecutorService threadPool = Executors.newCachedThreadPool();//创建可伸缩线程池//手动创建线程池 ThreadPoolExecutorExecutorService threadPool = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试和最早的竞争,也不会抛出异常!try {//最大承载:Queue + max//超出最大承载抛出RejectedExecutionException 异常 (默认拒绝策略)for (int i = 0; i < 9; i++) {//使用了线程池之后,用线程池来创建线程threadPool.execute(() -> {System.out.println(Thread.currentThread().getName() + " OK");});}} catch (Exception e) {e.printStackTrace();} finally {//线程池用完,程序结束,关闭线程池threadPool.shutdown();}}}

四种拒绝策略

image-20210830095348955

 * 4大拒绝策略:* new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里!* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!

小结与扩展

线程池最大线程数如何设置?

了解:IO密集型,CPU密集型(调优)

public class Demo1 {public static void main(String[] args) {//手动创建线程池 ThreadPoolExecutor//最大线程到底如何定义?//1、CPU 密集型  电脑处理器数是几,就是几,可以保证CPU的效率最高!//2、IO 密集型   大于 程序中十分耗IO的线程数   ---> 程序中 15个大型任务 io十分占用资源! =》 30//获取CPU核数 电脑处理器数System.out.println(Runtime.getRuntime().availableProcessors());ExecutorService threadPool = new ThreadPoolExecutor(2,Runtime.getRuntime().availableProcessors(),3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试和最早的竞争,也不会抛出异常!try {//最大承载:Queue + max//超出最大承载 RejectedExecutionException (默认拒绝策略)for (int i = 0; i < 9; i++) {//使用了线程池之后,用线程池来创建线程threadPool.execute(() -> {System.out.println(Thread.currentThread().getName() + " OK");});}} catch (Exception e) {e.printStackTrace();} finally {//线程池用完,程序结束,关闭线程池threadPool.shutdown();}}}

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

image-20210830104308970

新时代程序员:lambda表达式(本质就是函数式接口的实例)、链式编程、函数式接口、Stream流式计算

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

//此注解用来判断该接口是否是函数式接口
@FunctionalInterface
public interface Runnable {public abstract void run();
}//简化编程模型,在新版本的框架底层大量应用!
Function函数型接口
image-20210830105803298

代码测试

/*** Function 函数型接口 有一个输入,有一个输出* 只要是 函数式接口 可以用 lambda表达式简化*/
public class Demo01 {public static void main(String[] args) {//输出输入的值
//        Function<String ,String > fun = new Function<String ,String >() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };Function<String ,String > fun = (str) -> str; //lambda表达式System.out.println(fun.apply("123"));}
}
Predicate断定型接口
image-20210830111028318

代码测试

/*** Predicate 断定型接口 有一个输入值 返回值是布尔值!*/
public class Demo02 {public static void main(String[] args) {//判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String str) {
//                return "".equals(str);
//            }
//        };Predicate<String> predicate = str -> "".equals(str);System.out.println(predicate.test(""));System.out.println(predicate.test("123"));}
}
Consumer消费型接口
image-20210830112359042

代码测试

/*** Consumer 消费型接口 只接收参数,不返回值*/
public class Demo03 {public static void main(String[] args) {//接收参数,将其打印出来
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };Consumer<String> consumer = str -> System.out.println(str);consumer.accept("hello");}
}
Supplier供给型接口
image-20210830112516994

代码测试

/*** Supplier 供给型接口 不需参数,有返回值*/
public class Demo04 {public static void main(String[] args) {
//        Supplier<String> supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "world";
//            }
//        };Supplier<String> supplier = () -> "world";System.out.println(supplier.get());}
}

13、Stream流式计算

什么是Stream流式计算

大数据:存储 + 计算

存储:集合、MySQL

计算:流式计算~

image-20210830142933764

public class Test {public static void main(String[] args) {User user1 = new User(1, "a", 21);User user2 = new User(2, "b", 22);User user3 = new User(3, "c", 23);User user4 = new User(4, "d", 24);User user5 = new User(5, "e", 25);User user6 = new User(6, "f", 26);//存储交给集合List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);//计算交给Stream流//lambda表达式、链式编程、函数式接口、Stream流式计算list.stream().filter(user -> user.getId() % 2 == 0) //找出id为偶数的用户.filter(user -> user.getAge() > 23)  //年龄大于23岁.map(user -> user.getName().toUpperCase()) //用户名 转为大写.sorted((u1, u2) -> -u1.compareTo(u2)) //用户名字母到着排序.limit(1) //只输出一个用户.forEach(System.out::println);}
}

14、ForkJoin

什么是ForkJoin

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

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

img

ForkJoin 特点: 工作窃取!

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

img

Class ForkJoinPool的使用

image-20210830155359296

ForkJoinTask

image-20210830155929025

ForkJoin的计算类:

/*** 求和计算的任务** 如何使用 ForkJoin?* 1.ForkJoinPool 通过它来执行* 2.计算任务 forkJoinPool.execute(ForkJoinTask<?> task)* 3.计算类要继承ForkJoinTask*/
public class ForkJoinDemo extends RecursiveTask<Long> {private Long start;private Long end;//临界值private Long temp = 10000L;public ForkJoinDemo(Long start, Long end) {this.start = start;this.end = end;}//计算方法@Overrideprotected Long compute() {if ((end - start) < temp) {long sum = 0L;for (Long i = start; i <= end; i++) {sum += i;}return sum;} else {//分支合并计算Long middle = (start + end) / 2;//中间值ForkJoinDemo task1 = new ForkJoinDemo(start, middle);task1.fork(); //拆分任务,把线程任务压入线程队列ForkJoinDemo task2 = new ForkJoinDemo(middle, end);task2.fork(); //拆分任务,把线程任务压入线程队列//结果汇总return task1.join() + task2.join();}}
}

测试类:

/*** 三六九等 三 六(ForkJoin) 九(Stream并行流)*/
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {//test1(); // 419//test2();test3();//234}//普通程序员public static void test1() {long sum = 0L;long start = System.currentTimeMillis();for (long i = 0L; i <= 10_0000_0000; i++) {sum += i;}long end = System.currentTimeMillis();System.out.println("sum = " + sum + " 时间:" + (end - start));}//会使用forkJoinpublic 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 = " + sum + " 时间:" + (end - start));}
}

.parallel().reduce(0, Long::sum) 使用一个并行流去计算,提高效率。(并行计算归约求和)

15、异步回调

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

image-20210830174618430

线程异步调用通常使用CompletableFuture类

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

        //没有返回值的 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("11111");completableFuture.get(); //阻塞,获取执行结果

(2)有返回值的 supplyAsync 异步回调

/*** 类似异步调用:Ajax** 异步调用:CompletableFuture* 成功回调* 失败回调*/
public class Demo02 {public static void main(String[] args) throws ExecutionException, InterruptedException {//有返回值的 supplyAsync 异步回调//成功和失败回调//返回的是错误信息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());}
}

16、JMM

请你谈谈你对 volatile 的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

什么是JMM

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

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

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

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

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

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

8种操作

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

img

img

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操作之前,必须把此变量同步回主内存

问题:程序A不知道主内存中的值发生了变化

img

17、volatile

1)保证可见性

public class JMMDemo {// 如果不加volatile 程序会死循环// 加了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在执行任务的时候,是不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

/*** 不保证原子性*/
public class VDemo2 {// volatile 不保证原子性private volatile static int num = 0;public static void add() {num++;}public static void main(String[] args) {// 20个线程,每个线程调用100次  理论值 2万for (int i = 0; i < 20; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {add();}}).start();}while (Thread.activeCount() > 2) { // main gcThread.yield();}System.out.println(Thread.currentThread().getName() + " " + num);}
}

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

img

使用原子类,解决原子性问题

img

public class VDemo2 {// volatile 不保证原子性// 原子类的 Integerprivate volatile static AtomicInteger num = new AtomicInteger();public static void add() {//num++; //不是原子性操作num.getAndIncrement(); // +1 操作 底层是CAS保证的原子性}public static void main(String[] args) {// 20个线程,每个线程调用100次  理论值 2万for (int i = 0; i < 20; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {add();}}).start();}while (Thread.activeCount() > 2) { // main gcThread.yield();}System.out.println(Thread.currentThread().getName() + " " + num);}
}

原子类的底层都直接和操作系统挂钩!在内存中修改值!

Unsafe类是一个很特殊的存在

3)禁止指令重排

指令重排

什么是指令重排?

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

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

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

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4//我们期望的执行顺序是 1234  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的

可能造成的影响结果:前提:a b x y这四个值 默认都是0

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

正常结果: x = 0; y =0;

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

可能造成的诡异结果:x = 2; y = 1;(概率极低)

volatile可以避免指令重排:

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

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

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

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

img

总结:

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 static final Hungry HUNGRY = new Hungry();private Hungry(){}public static Hungry getInstance(){return HUNGRY;}
}

2)DCL懒汉式

/*** 懒汉式单例* 道高一尺,魔高一丈!*/
public class LazyMan {private volatile static LazyMan lazyMan;private static boolean flag = false;private LazyMan() {synchronized (LazyMan.class) {if (!flag) {flag = true;} else {throw new RuntimeException("不要试图使用反射破坏异常!");}}}//双重检测锁模式的 懒汉式单例 DCL懒汉式public static LazyMan getInstance() {if (null == lazyMan) {synchronized (LazyMan.class) {if (null == lazyMan) {lazyMan = new LazyMan(); // 不是一个原子性操作}}}return lazyMan;}//不加 synchronized 多线程情况下,不一定是单例public static void main(String[] args) throws Exception {
//        for (int i = 0; i < 10; i++) {
//            new Thread(() -> {
//                LazyMan.getInstance();
//            }).start();
//        }//反射!//LazyMan instance = LazyMan.getInstance();Field flag = LazyMan.class.getDeclaredField("flag");flag.setAccessible(true);Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);constructor.setAccessible(true);LazyMan instance = constructor.newInstance();flag.set(instance,false);LazyMan instance1 = constructor.newInstance();System.out.println(instance == instance1);}/*创建对象的步骤:1.分配内存空间2.执行构造方法,初始化对象3.把这个对象指向这个空间123132 线程A线程B // 此时lazyMan还没有完成构造*/
}

3)静态内部类

/***  静态内部类*/
public class Holder {private Holder(){}public static class InnerClass{private static final Holder HOLDER = new Holder();}public static Holder getInstance(){return InnerClass.HOLDER;}
}

单例不安全, 因为反射

4)枚举类

/*** enum 是什么? 本身也是一个Class类*/
public enum EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}
}class Test{public static void main(String[] args) throws Exception {EnumSingle instance1 = EnumSingle.INSTANCE;//EnumSingle instance2 = EnumSingle.INSTANCE;Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);constructor.setAccessible(true);EnumSingle instance2 = constructor.newInstance(); //java.lang.NoSuchMethodException: com.lkl.singleton.EnumSingle.System.out.println(instance1);System.out.println(instance2);}
}

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

image-20210830223705420

枚举类型的最终反编译源码:

public final class EnumSingle extends Enum
{public static EnumSingle[] values(){return (EnumSingle[])$VALUES.clone();}public static EnumSingle valueOf(String name){return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);}private EnumSingle(String s, int i){super(s, i);}public EnumSingle getInstance(){return INSTANCE;}public static final EnumSingle INSTANCE;private static final EnumSingle $VALUES[];static {INSTANCE = new EnumSingle("INSTANCE", 0);$VALUES = (new EnumSingle[] {INSTANCE});}
}

19、深入理解CAS

什么是CAS

大厂必须要研究底层!有所突破!修内功,基础不牢,地动山摇 操作系统、计算机网络原理

public class CASDemo {//CAS  compareAndSet:比较并交换!public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2021);//期望、更新//public final boolean compareAndSet(int expect, int update)//如果我期望的值达到了,就更新,否则,就不更新,CAS 是CPU的并发原语!System.out.println(atomicInteger.compareAndSet(2021, 2022));System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(2021, 2022));System.out.println(atomicInteger.get());}
}

Unsafe类

img

atomicInteger.getAndIncrement();
img

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

缺点:

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

CAS:ABA(狸猫换太子)

img

主内存中 A=1

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

线程2:两个操作:

  1. 期望值是1,变成3
  2. 期望是3,变成1

所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;线程1不知道A的值发生了修改!

public class CASDemo2 {//CAS  compareAndSet:比较并交换!public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2021);//期望、更新//public final boolean compareAndSet(int expect, int update)//如果我期望的值达到了,就更新,否则,就不更新,CAS 是CPU的并发原语!// ======================= 捣乱的线程 ==============================System.out.println(atomicInteger.compareAndSet(2021, 2022));System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(2022, 2021));System.out.println(atomicInteger.get());// ======================= 捣乱的线程 ==============================//======================== 期望的线程 ==============================System.out.println(atomicInteger.compareAndSet(2021, 6666));System.out.println(atomicInteger.get());}
}

20、原子引用(AtomicReference)


解决ABA问题,引入原子引用!对应的思想:乐观锁

带版本号的原子操作!

public class CASDemo3 {//CAS  compareAndSet:比较并交换!public static void main(String[] args) {//AtomicStampedReference 泛型如果使用包装类,注意对象引用问题//正常在业务操作,这里泛型都是一个个对象AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1, 1);new Thread(() -> {int stamp = stampedReference.getStamp(); //获得版本号System.out.println(Thread.currentThread().getName() + " 1 -> " + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(stampedReference.compareAndSet(1, 2,stampedReference.getStamp(), stampedReference.getStamp() + 1));System.out.println(Thread.currentThread().getName() + " 2 -> " + stampedReference.getStamp());System.out.println(stampedReference.compareAndSet(2, 1,stampedReference.getStamp(), stampedReference.getStamp() + 1));System.out.println(Thread.currentThread().getName() + " 3 -> " + stampedReference.getStamp());}, "a").start();new Thread(() -> {int stamp = stampedReference.getStamp(); //获得版本号System.out.println(Thread.currentThread().getName() + " 1 -> " + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(stampedReference.compareAndSet(1, 6,stampedReference.getStamp(), stampedReference.getStamp() + 1));System.out.println(Thread.currentThread().getName() + " 2 -> " + stampedReference.getStamp());}, "b").start();try {TimeUnit.SECONDS.sleep(3);System.out.println("**************");System.out.println(stampedReference.getReference());} catch (InterruptedException e) {e.printStackTrace();}}
}

注意:

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

整型包装类
image-20210831110834295

21、各种锁的理解

1、公平锁、非公平锁

公平锁:非常公平,不能插队,必须先来后到!(效率可能较低)

非公平锁:非常不公平,可以插队(默认都是非公平,效率较高)

    public ReentrantLock() {sync = new NonfairSync();}

带参构造器,可以修改公平状态

    public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

2、可重入锁

可重入锁(递归锁):拿到外边的锁后,会自动拿到里面的锁(synchronized【隐式】和Lock【显式】都是可重入锁)

image-20221005165339882

synchronized版

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版

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{private final Lock lock = new ReentrantLock();public void sms(){lock.lock(); //Lock锁必须配对,有加锁就必须有解锁! 否则就会死锁!try {System.out.println(Thread.currentThread().getName() + " sms");call();} finally {lock.unlock();}}public void call(){lock.lock();try {System.out.println(Thread.currentThread().getName() + " call");} finally {lock.unlock();}}
}

3、自旋锁

spinlock

image-20210831143626881

自定义自旋锁

public class spinlock {// int 0// Thread nullAtomicReference<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);}
}

测试自定义自旋锁:

public class TestSpinLock {public static void main(String[] args) {
//        Lock lock = new ReentrantLock();
//        lock.lock();
//        lock.unlock();// 底层使用的自旋锁CASspinlock spinlock = new spinlock();new Thread(() -> {spinlock.myLock();try {TimeUnit.SECONDS.sleep(4);} catch (Exception e) {e.printStackTrace();} finally {spinlock.myUnLock();}}, "T1").start();new Thread(() -> {spinlock.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();} finally {spinlock.myUnLock();}}, "T2").start();}
}
输出结果
image-20210831150358948

结果分析:

两个线程共同操作一把锁,谁先拿到锁谁先执行。T1线程先拿到锁加锁,其次是T2线程,先输入第一行再输出第二行;T1线程4s后释放锁,随之T2线程拿到锁加锁进行操作,3s后释放锁。故:先输入第一二行,4s后输出第三行,3s后输出第四行。

4、死锁

什么是死锁?两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法在执行下去。

两个线程拿着自己锁不放的同时,试图获取对方的锁,就会造成死锁。

img

死锁测试,怎么排除死锁!

/*** 死锁样例*/
public class DeadLockDemo {static Object a = new Object();static Object b = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (a) {System.out.println(Thread.currentThread().getName() + " 获取到锁a,试图获取锁b");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (b) {System.out.println("获取到锁b");}}}, "A").start();new Thread(() -> {synchronized (b) {System.out.println(Thread.currentThread().getName() + " 获取到锁b,试图获取锁a");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (a) {System.out.println("获取到锁a");}}}, "B").start();}
}

image-20221006000918655

产生死锁的原因:

第一:系统资源不足

第二:进程运行推进顺序不合适

第三:资源分配不当

如何定位死锁,解决问题?

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

命令:jps -l

image-20210831153959567

2、使用jstack 进程进程号 找到死锁信息(jstack是jvm中自带的堆栈跟踪工具)

命令:jstack 进程号

image-20210831154245165

面试,工作中!如何排查问题?

1、日志 90%

2、堆栈信息 10%


http://chatgpt.dhexx.cn/article/FdGScMSa.shtml

相关文章

java--JUC快速入门(彻底搞懂JUC)

java–JUC快速入门&#xff08;彻底搞懂JUC&#xff09; 文章目录 java--JUC快速入门&#xff08;彻底搞懂JUC&#xff09;1、学习多线程之前需要知道的一些概念。2、JUC的结构3、Lock锁(重点)4、集合类不安全5、Callable()6、常用的辅助类7、读写锁8、阻塞队列9、线程池 1、学…

Dbeaver做数据迁移

1、选择源头数据库的表、鼠标右击、选择导出数据 2、在数据转化弹框中&#xff0c;双击 ‘数据库&#xff0c;数据表’ 那一栏 3、选择目标数据库&#xff0c;调整字段类型映射关系 4、调整字段的映射关系 目前遇到的字段类型&#xff0c;只有 int&#xff0c;bigint 转 num…

dbeaver工具连接达梦数据库

、一 概述 DBeaver 是一个基于 Java 开发&#xff0c;免费开源的通用数据库管理和开发&#xff0c;DBeaver 采用 Eclipse 框架开发&#xff0c;支持插件扩展&#xff0c;并且提供了许多数据库管理工具&#xff1a;ER 图、数据导入/导出、数据库比较、模拟数据生成等&#xff0…

DBeaver 格式化sql

有时候我们拿到了一条sql语句是长长的&#xff0c;非常不容易阅读&#xff0c;这时我们就想说哪里可以格式下sql代码。 方法有很多种&#xff0c;这里我就用Dbeaver来格式化sql。 ①打开Dbeaver ②复制sql代码到SQL编辑器中&#xff0c;并选中 ③按ctrlshiftF&#xff0c;即…

【DBeaver】常用自定义设置

文章目录 背景一、用户界面设置1.1、22.3.4版本1.1.1、SQL编辑器-字体设置1.1.2、查询结果-字体设置 1.2、23.0.0版本1.2.1、应用字体&#xff08;导航栏等&#xff09;1.2.2、文本字体&#xff08;SQL输出、文本编辑器等&#xff09; 二、常规设置2.1、连接类型设置/环境设置 …

DBeaver导入Excel数据

目录 前言 导入准备 ​导入步骤 1.选中数据库表&#xff0c;右键&#xff0c;然后点击导入数据 2.双击CSV,选择待导入的文件 3.修改编码格式&#xff08;可选&#xff0c;不乱码不用&#xff09; 4.点击下一步&#xff0c;修改列的类型 5.一直下一步&#xff0c;点击…

Dbeaver基本使用

1&#xff1a;与plsql相比&#xff0c;Dbeaver没有右击直接查看表注释的功能&#xff0c;但是Dbeaver提供了一个“打开声明”的功能&#xff0c;里面可以查看一些比较实用的内容&#xff1a;表列注释、创建该表的create语句&#xff1a; 2&#xff1a;在一般开发的情况下&#…

【大数据】Hive可视化工具dbeaver

Hive可视化工具dbeaver 1、dbeaver基本介绍 dbeaver是一个图形化的界面工具&#xff0c;专门用于与各种数据库的集成&#xff0c;通过dbeaver我们可以与各种数据库进行集成通过图形化界面的方式来操作我们的数据库与数据库表&#xff0c;类似于我们的sqlyog或者navicat。 2、…

DBeaver安装及使用手册

一、DBeaver安装 1、在[DBeaver官网](https://dbeaver.io/download)进行数据库工具下载&#xff0c;下载好后双击运行2、选择语言后&#xff0c;点击OK 3、点击下一步 4、接受许可 5、选择可使用者&#xff0c;然后点击下一步 6、选择组件&#xff0c;一般选择默认即可 7…

DBeaver-Driver-All ( DBeaver驱动包,所有JDBC驱动整合包)

DBeaver-Driver-All DBeaver-Driver-All ( DBeaver驱动包 )整合所有DBeaver的JDBC驱动包&#xff0c;供DBeaver使用&#xff0c;无需每次都搜索和下载&#xff0c;只需clone本项目即可&#xff0c;一个包包含几乎所有的驱动&#xff0c;如果有缺漏的驱动欢迎提Issue补充。 DBe…

DBeaver 下载安装

1 下载地址(我下载的 Windows版本&#xff0c;根据系统需要选择版本) Releases dbeaver/dbeaver GitHubFree universal database tool and SQL client. Contribute to dbeaver/dbeaver development by creating an account on GitHub.https://github.com/dbeaver/dbeaver/rel…

DBeaver 安装

DBeaver 目录 DBeaver1、介绍2、发展史3、版本介绍4、下载与安装5、DBeaver 连接数据库&#xff08;MySql&#xff09;6、DBeaver 连接数据库&#xff08;Hive&#xff09;7、DBeaver 功能简介 1、介绍 DBeaver是一种通用数据库管理工具&#xff0c;适用于需要以专业方式使用数…

DBeaver驱动安装

最近打算用DBeaver它来查看SQLite文件&#xff0c;需要安装驱动&#xff0c;总是安装不上有点苦恼 下载驱动的时候可能会出现如下提示&#xff1a; Can’t create driver instance Error creating driver ‘SQLite’ instance. Most likely required jar files are missing. …

使用 DBeaver 导入数据

如何上传数据 在开始使用 DBeaver 之前&#xff0c;用户 以 excel 格式收集了有关电视剧的信息。他的文件已经包含所有必要的列&#xff0c;但我们的英雄不想将其中一些列传输到数据库&#xff0c;因为它们是为他个人使用而创建的&#xff0c;与站点访问者无关。要仅快速加载网…

Dbeaver配置迁移

换电脑了,希望Dbeaver中的连接也一起带过去 但在Dbeaver中找了一圈,没找到可以导出配置的功能 搜了一圈资料并自己尝试,发现配置文件保存在两个文件中,只要把老电脑的这两个文件换到新电脑中就能迁移数据库连接配置

DBeaver 超级详细的安装与使用

一、下载DBeaver DBeaver是一种通用数据库管理工具&#xff0c;适用于需要以专业方式使用数据的每个人&#xff1b;适用于开发人员&#xff0c;数据库管理员&#xff0c;分析师和所有需要使用数据库的人员的免费(DBeaver Community) 的多平台数据库工具。 DBeaver支持80多个数据…

DBeaver安装与使用教程(超详细安装与使用教程)

文章预览&#xff1a; DBeaver安装与使用教程&#xff08;超详细安装与使用教程&#xff09;一、DBeaver安装教程①下载地址②图文安装教程 二、DBeaver使用教程①mysql数据库为例1>填写数据库信息2>常用基本功能 DBeaver安装与使用教程&#xff08;超详细安装与使用教程…

DBeaver下载安装教程

一、简单介绍 DBeaver是一款很好用的数据库连接工具&#xff0c;免费的&#xff0c;支持100多种数据库&#xff08;基本上看得见的数据库不管是关系型还是非关系型的都可用&#xff09; 不过可能需要下载驱动&#xff0c;如果驱动下载不了&#xff0c;连接失败&#xff0c;则…

dbeaver(下载、安装图文过程)

能够帮助你们解决问题是博主的荣幸&#xff0c;你们的支持是我创作的最大动力&#xff01;:)欢迎关注点赞 文章目录 一、dbeaver下载二、dbeaver安装总结 一、dbeaver下载 提示&#xff1a;安装之前需要先确认好自己需要哪个版本的maven&#xff0c;避免浪费时间。 官网下载&am…

DBeaver连接mysql数据库图文教程

文章目录 前言一、DBeaver连接mysql数据库二、文档下载地址 前言 DBeaver是免费、开源、通用数据库工具&#xff0c;是许多开发开发人员和数据库管理员的所选。下面详细介绍Dbeaver连接mysql数据库的过程。 一、DBeaver连接mysql数据库 1、 打开Dbeaver后&#xff0c;按下图…