9-JUC并发编程最新版【狂神】
JUC-java.util.concurrent(并发)
线程和进程
进程:一个程序,QQ.exe
一个进程往往含多个进程,至少包含一个
JAVA默认有2个线程,main,GC线程
并发、并行
并发(多线程操作同一个资源)
--CPU一核,模拟出来多条线程,快速交替
并行(多个人一起行走)
--CPU多核,多个线程可以同时执行,线程池,任务管理器-逻辑处理器
package JUC;
public class Test01 {
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
线程状态
六个状态-新生、运行、阻塞、等待、超时等待、死亡
wait\sleep的区别
1、来自不同的类
2、关于锁的释放
wait会释放锁,sleep,不会释放
3、使用的范围是不同的
wait 必须在同步代码块 sleep 哪里都能用
4、是否需要捕获异常
wait 需要 sleep 需要
LOKC锁(重点)
传统方式用Synchronized锁
方式加了Synchronized锁,可以理解该线程直接拿了对象,独占该对象,执行了Synchronized方法后,才还出去对象,别人才能用。否则不加Synchronized方法时,就是对象是共享的,谁都能用,这样数据就容易错乱
package JUC;
/**
* 真正常的多线程开发,公司中的开发,降低耦合度,提高代码的可读性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1、属性、方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发,多线程操作同一个资源类,把资源丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface注解,定义一个函数式接口,jdk1.8后,Lambda表达式可以作为函数式接口的实现
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP
class Ticket {
//属性,方法
private int number=50;
//卖票方法
//synchronized关键字,本质就是排队,队列,锁
public synchronized void sale() {//传统就是用synchronized关键字
if(number>0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--)+"票,剩余:"+number);
}
}
}
LOCK
package JUC;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发,多线程操作同一个资源类,把资源丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface注解,定义一个函数式接口,jdk1.8后,Lambda表达式可以作为函数式接口的实现
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
//lock三部曲
//1、new ReentrantLock() 创建锁对象
//2、lock.lock() 获取锁
//3、finally lock.unlock() 释放锁
//资源类 OOP
class Ticket {
//属性,方法
private int number=50;
//公平锁:十分公平,可以先来先服务,先到先得
//非公平锁:先来后到,不公平,可能后到的线程先拿到锁,导致线程饥饿(默认)
Lock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if(number>0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--)+"票,剩余:"+number);
}
}catch (Exception e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
Synchronized和lock区别
1、Synchronized是内置的JAVA关键字,lock是一个JAVA类
2、Synchronized无法判断获取锁的状态,lock可以判断是否获取到锁
3、Synchronized会自动释放锁,lock要手动释放锁,如果不释放锁,会死锁
4、Synchronized线程1(获得锁,如果阻塞了,线程2只能傻傻地等,Lock锁可以不一定等待下去
5、Synchronized可重入锁,不可以中断的,非公平;lock可重入锁,可判断锁,非公平
6、Synchronized适合锁少量的代码同步问题,lock适合锁大量的同步代码
传统用法的生产者和消费者问题
package JUC;
/**
* 线程之间的通信问题,生产者和消费者问题,一个线程通知唤醒,一个线程等待唤醒
* 线程交替执行 A B 操作同一个变量 num-0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Date date=new Date();
new Thread(()->{
for(int i=0;i<10;i++){
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待、业务、通知-看到生产者消费者记住前面三个步骤
class Date{//数字,资源类
private int number=0;
//+1
public synchronized void increment() throws InterruptedException {
if(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+":"+number);
//通知其它线程,我+1完了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+":"+number);
//通知其它线程,我-1完了
this.notifyAll();
}
}
假设以下执行顺序:
1. 初始 `number = 0`。
2. **消费者线程1** 进入 `decrement`,发现 `number == 0`,调用 `wait()` 进入阻塞。
3. **生产者线程1** 进入 `increment`,发现 `number == 0`,执行 `number++`(变为 1),然后调用 `notifyAll()`。
4. **消费者线程1** 和 **消费者线程2** 同时被唤醒:
- **消费者线程1** 检查 `number == 1`,执行 `number--`(变为 0)。
- **消费者线程2** **未重新检查条件**(因为用 `if`),直接执行 `number--`(变为 **-1**)。
关键改进点
while代替if:- 线程被唤醒后必须重新检查条件,避免虚假唤醒导致逻辑错误。
理解一个点,wait时,这个线程就会停止,其它线程会执行,除非其它线程执行完,然后notifyAll时,又唤醒这个wait的,这时代码会在wait往下继续执行
Lock版的生产者消费者问题
package JUC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//Condition+LOCK 不同于Synchronized,可以和Synchronized一样随机通知线程,但能通过Condition实现精准通知线程
public class A {
public static void main(String[] args) {
Date date=new Date();
new Thread(()->{
for(int i=0;i<100;i++){
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<100;i++){
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i=0;i<100;i++){
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for(int i=0;i<100;i++){
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待、业务、通知-看到生产者消费者记住前面三个步骤
class Date{//数字,资源类
private int number=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();//通知唤醒使用,能精准通知需要的线程
//condition.await() 等待,直到被通知
//condition.signalAll() 通知所有等待线程,唤醒所有线程
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
while(number!=0){
//等待
condition.await();
}//当if条件成立时,整个线程就会进入等待状态,不会执行后面的代码,直到被notify或者notifyAll唤醒
number++;
System.out.println(Thread.currentThread().getName()+":"+number);
//通知其它线程,我+1完了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while(number==0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+":"+number);
//通知其它线程,我-1完了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
Condition类实现通知唤醒
package JUC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PC {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.printA();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.printB();
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.printC();
}
}, "C").start();
}
}
class Date {//资源类
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAA");
//唤醒指定的人
number = 2;
condition2.signal();//直接跳到condition2.await();,然后执行下面的方法,通过这种方式控制想跳到哪个线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBB");
//唤醒指定的人
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBBB");
//唤醒指定的人
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//A=>AAAAA
//B=>BBBBB
//C=>BBBBBB
//A=>AAAAA
//B=>BBBBB
//C=>BBBBBB
//在你的代码中,Date 类是一个资源类,它包含了三个条件对象。这些条件对象可以用来让不同的线程在特定条件下等待,直到被其他线程唤醒。
//
//例如,假设你有三个线程分别负责打印 "AAAAA", "BBBBB", 和 "CCCCC",你可以使用这三个条件对象来控制线程的执行顺序。具体来说:
//
//printA() 方法中使用 condition1.await() 让线程等待,直到 number 等于 1。
//当 printA() 方法完成后,你可以通过 condition2.signal() 唤醒等待 condition2 的线程,这个线程可能是负责打印 "BBBBB" 的线程。
//同理,printB() 方法完成后,可以通过 condition3.signal() 唤醒等待 condition3 的线程。
//最后,在 printC() 方法完成后,可以通过 condition1.signal() 唤醒等待 condition1 的线程,这样就形成了一个循环,三个线程按照顺序执行。
//总结 ,condition类似一个定位器,可以跳到指定的condition.await()所在的线程,然后继续执行。
八锁现象理解锁(八个锁的问题)
package JUC;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
//锁的存在,synchronized,一定要执行完,独占了对象,线程二才可以执行
new Thread(() -> {
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.call();
},"B").start();
}
}
class Phone {
public synchronized void sendSms() {
System.out.println("sendSms");
}
public synchronized void call() {
System.out.println("call");
}
}
ckage JUC;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、增加了一个普通方法,先执行了hello方法,然后才执行了sendSms方法,可以理解加了synchronized关键字,下一个sy方法只能等他放回堆,才能去堆(储物间)拿对象来执行,而普通方法可以直接去栈(厨师的台面)那里拿对象来执行,
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.hello();
},"B").start();
}
}
class Phone {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("sendSms");
}
public synchronized void call() {
System.out.println("call");
}
public void hello() {
System.out.println("hello");
}
}
//hello
//sendSms
ackage JUC;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、
*/
public class Test1 {
public static void main(String[] args) {
Phone phone1 = new Phone();//先call和sendSms
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone {
public synchronized void sendSms() {//加了这个方法,就只能去等别人厨师执行完这个方法,还完对象后,才能去拿对象执行方法
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("sendSms");
}
public synchronized void call() {
System.out.println("call");
}
public void hello() {
System.out.println("hello");
}
}
package JUC;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、增加两个静态的同步方法,只有一个对象,或者两个对象,其实加了static关键字,锁的是类对象-class对象,所以不过建了一个对象还是两个对象结果都是先sendSms后call
*/
public class Test1 {
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone1.call();
},"B").start();
}
}
class Phone {
public static synchronized void sendSms() {//加了static关键字,锁的是类对象-class对象,当使用该方法时,会自动锁定类对象
/**
* 这里sendSms方法被声明为static和synchronized,这意味着在调用sendSms方法时,它会锁定Phone.class这个类对象。因此,如果在程序的其他地方也使用synchronized关键字锁定同一个类对象(比如在另一个静态方法中使用synchronized(Phone.class)),或者调用其他被synchronized和static修饰的静态方法(如call方法在你的例子中并没有被synchronized和static修饰),那么这些方法的调用会被顺序执行,以确保线程安全。
* 总结来说,public static synchronized void sendSms()中的synchronized确实锁定了Phone类的类对象,而不是某个特定的实例对象。
*/
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("sendSms");
}
public static synchronized void call() {
System.out.println("call");
}
public void hello() {
System.out.println("hello");
}
}
为什么会自动锁定类对象呢,从类加载角度解释?
在Java中,当一个静态方法被声明为synchronized时,它会自动锁定该类的类对象(即Class对象)。从类加载的角度来解释这一行为,我们可以从以下几个方面考虑:
类加载机制:在Java中,类的加载是由类加载器完成的,当一个类被首次使用时,类加载器会将该类加载到内存中,并创建一个对应的Class对象。这个Class对象包含了类的信息,例如方法、字段、继承关系等。
静态方法与类对象关联:静态方法是属于类级别的,而不是某个特定的实例对象。因此,静态方法需要通过类对象来调用。由于静态方法与类对象直接相关,因此在设计上,静态方法的同步锁定的是类对象,这样可以保证在同一时间只有一个线程能够执行该类的所有静态同步方法。
同步原理:当一个线程调用一个synchronized方法时,它需要获取到该方法上的锁。对于静态同步方法,这个锁就是该类的类对象。如果类对象已经被锁定,其他尝试调用该静态同步方法的线程将被阻塞,直到锁被释放。
在你的例子中,sendSms和call方法都是静态同步方法,这意味着这两个方法在调用时会锁定同一个Class对象,即Phone.class。因此,当线程A调用sendSms方法时,它会锁定Phone.class对象,线程B在尝试调用call方法时也会尝试获取同一个锁。由于sendSms方法中使用了TimeUnit.SECONDS.sleep(4),线程A在4秒内持有锁,因此线程B必须等待直到线程A释放锁后才能执行call方法,从而实现顺序执行。
package JUC;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、1个静态的同步方法,1个普通同步方法
*/
public class Test1 {
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendSms();//默认加了static关键字,锁的是类对象-class对象,当使用该方法时,会自动锁定类对象
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone1.call();//这个还是普通同步方法,锁的是实例对象-对象锁,当使用该方法时,会自动锁定实例对象
},"B").start();
}
}
class Phone {
public static synchronized void sendSms() {//加了static关键字,锁的是类对象-class对象,当使用该方法时,会自动锁定类对象
/**
* 这里sendSms方法被声明为static和synchronized,这意味着在调用sendSms方法时,它会锁定Phone.class这个类对象。因此,如果在程序的其他地方也使用synchronized关键字锁定同一个类对象(比如在另一个静态方法中使用synchronized(Phone.class)),或者调用其他被synchronized和static修饰的静态方法(如call方法在你的例子中并没有被synchronized和static修饰),那么这些方法的调用会被顺序执行,以确保线程安全。
* 总结来说,public static synchronized void sendSms()中的synchronized确实锁定了Phone类的类对象,而不是某个特定的实例对象。
*/
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("sendSms");
}
public synchronized void call() {
System.out.println("call");
}https://support.seeyon.com/
public void hello() {
System.out.println("hello");
}
}
集合类不安全
package JUC.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
//并发下ArrayList不安全
/**
* 解决方案
* 1。List<String> list = new Vector<>();本质还是用synchronized关键字,效率低下
* 2。List<String> list = Collections.synchronizedList(new ArrayList<>());
*3.List<String> list=new CopyOnWriteArrayList<String>();
* COW,写入时复制,计算机程序设计领域的一种优化策略,是一种多线程编程技术。
* 它通过在写操作时将整个列表复制一份,然后再对其进行修改,从而保证线程安全。
**/
//List<String> list=new CopyOnWriteArrayList<String>();
List<String> list = new ArrayList<>() ;
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();
}
}
}
CopyOnWriteArrayList 的线程安全机制流程如下(结合你代码中的多线程添加元素场景):
初始状态:
所有线程共享同一个 volatile 数组引用(假设初始为 array v1)
写操作流程(以 add() 为例):
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); // 写操作开始前获取独占锁
try {
Object[] elements = getArray(); // 获取当前数组引用(比如 v1)
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // 创建副本 v2
newElements[len] = e; // 在副本上修改
setArray(newElements); // 原子操作:将数组引用指向新副本 v2
return true;
} finally {
lock.unlock(); // 释放锁
}
}
DiffCopyInsert
多线程并发执行时的控制流程:
当 Thread-1 和 Thread-2 同时调用 add() 时: ① Thread-1 先获得锁(其他线程被阻塞) ② 复制当前数组 v1 创建 v2 ③ 修改 v2 并原子替换引用为 v2 ④ Thread-1 释放锁 ⑤ Thread-2 获得锁时,此时数组引用已是 v2 ⑥ 复制 v2 创建 v3 并修改,最终引用指向 v3
读操作流程(如 System.out.println):
public void forEach(Consumer<? super E> action) {
Object[] elements = getArray(); // 直接获取当前数组引用(无锁)
// 遍历这个快照数组
for (int i = 0; i < elements.length; i++) {
// 即使此时数组被替换,遍历的仍是旧引用
}
}
DiffCopyInsert
你的测试代码中关键点: 当 10 个线程并发执行时:
每个线程的 add() 操作都会创建新数组副本
最终的 list 会包含所有线程添加的元素(但遍历时可能看到中间状态)
System.out.println 可能输出不同版本的数据快照,但不会出现数组越界等并发异常
这种设计实现了: √ 写操作的互斥性(通过 ReentrantLock) √ 读操作的完全无锁 √ 数据版本的原子切换(volatile 变量保证可见性) √ 每个写操作都基于最新的数据版本
代价是: ❗ 内存消耗(频繁复制数组) ❗ 数据弱一致性(读取可能不是最新版本)
建议将 ArrayList 替换为 CopyOnWriteArrayList 后观察输出结果,可以看到不会出现 ConcurrentModificationException,但每次打印的列表长度可能不一致(这正是 COW 的特性体现)。
package JUC.unsafe;
//ArraySet
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
package JUC.unsafe;
//ArraySet
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
public class MapTest {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Callable
1,比runnalbe,它可以有返回值
2,可以抛出异常
3,方法不同
package JUC.unsafe;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest{
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread()).start();传统方式
//怎么启动Callable线程
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>(callable)).start();
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask,"A").start();
Integer o=(Integer)futureTask.get();//获得callable的返回值,这个方法可能会产生阻塞,一般放到最后,或者用异步通信的方式
System.out.println(o);
//Hello World
//1024
}
}
/**class MyThread implements Runnable{
@Override
public void run() {
}
}
**/
class MyThread implements Callable<Integer> { // 修正拼写
@Override
public Integer call() {
System.out.println("Hello World");
return 1024;
}
}
常用辅助类
countDownLatch
package JUC;
import java.util.concurrent.CountDownLatch;
//计数器
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() + "走了");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,然后再向下执行
System.out.println("所有人都走了");
}
}
//原理,每次有线程调用countDown()时,数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行
CyclicBarrier
package JUC;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
//计数器
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException {
//集7颗珠
//召唤龙珠主线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("集齐7颗珠");
});
for (int i = 0; i < 7; i++) {
final int index = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集"+index+"颗珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
Semaphore
抢车位,
4个车位,6个车
package JUC;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
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 (InterruptedException e) {
e.printStackTrace();
}finally{
semaphore.release();//释放
}
}).start();
}
}
}
读写锁
package JUC;
//读写锁
//读-读(共存)
//写-写(互斥)
//读-写(互斥)
//写-读(互斥)
//独占锁(写锁)一次只能被一个线程占有
//共享锁(读锁)可以被多个线程共有
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache cache = new MyCache();
//写入线程
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(() -> {
cache.put(temp+"", temp+"");
},String.valueOf(i)).start();
}
//读取线程
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(() -> {
cache.get(temp+"");
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
//加锁的
class MyCache {
private volatile Map<Object, Object> map=new HashMap<>();
//读写锁
private ReadWriteLock lock=new ReentrantReadWriteLock();
//存,写//只有一个线程能写
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 写入 " + key );
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入完成 " );
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
//取,读//可以有多个线程读
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 读取 " + key );
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + " 读取完成 " );
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
}
阻塞队列
写入:如果队列满了,就要阻塞等待
取:如果队列空了,就要阻塞等待生产

学会队列:添加、移除
四组API
1、抛出异常
2、不会抛出异常
3、阻塞等待
4、超时等待
package JUC;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Bq {
public static void main(String[] args) throws InterruptedException {
//collection
//list
//set
//queue-和上面同级,只是是阻塞队列,BlockingQueue
//什么情况下会使用阻塞队列?多线程,线程池
test3();
}
/**
* 抛出异常(报红)
*/
public static void test1(){
//队列在大小
ArrayBlockingQueue blockQueue = new ArrayBlockingQueue<String>(3);
System.out.println(blockQueue.add("a"));
System.out.println(blockQueue.add("b"));
System.out.println(blockQueue.add("c"));
System.out.println(blockQueue.add("d"));
System.out.println(blockQueue.element());//检测队首元素
//抛出异常Queue full
//System.out.println(blockQueue.add("d"));
System.out.println(blockQueue.remove());
}
/**
* 不会抛出异常,有返回值
*/
public static void test2(){
ArrayBlockingQueue blockQueue = new ArrayBlockingQueue<String>(3);
System.out.println(blockQueue.offer("a"));
System.out.println(blockQueue.offer("b"));
System.out.println(blockQueue.offer("c"));
System.out.println(blockQueue.peek());//检测队首元素,不会删除元素
//不会抛出异常,返回false
//System.out.println(blockQueue.offer("d"));
System.out.println(blockQueue.poll());
System.out.println(blockQueue.poll());
System.out.println(blockQueue.poll());
}
/**
* 等待,阻塞(一直阻塞)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockQueue = new ArrayBlockingQueue<String>(3);
//一直阻塞
blockQueue.put("a");
blockQueue.put("b");
blockQueue.put("c");
//blockQueue.put("d");//队列没位置了,会一直阻塞等
System.out.println(blockQueue.take());
System.out.println(blockQueue.take());
System.out.println(blockQueue.take());
System.out.println(blockQueue.take());//没有元素了,会一直阻塞等
}
/**
* 等待,阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockQueue = new ArrayBlockingQueue<String>(3);
blockQueue.offer("a");
blockQueue.offer("b");
blockQueue.offer("c");
//blockQueue.offer("d", 1, TimeUnit.SECONDS);//阻塞了超时1秒就退出
blockQueue.poll();
blockQueue.poll();
blockQueue.poll();
//blockQueue.poll(1, TimeUnit.SECONDS);//阻塞了超时1秒就退出
}
}
同步队列SynchronousQueue
没有容量,进去一个元素,一定要等待取出来之后,才能往里面放入一个元素
package JUC.unsafe;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列示例
* put了一个元素后,一定要从里面take出来,否则队列会一直阻塞。
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
// 创建同步队列
SynchronousQueue<String> queue = new SynchronousQueue<>();
// 生产者线程
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " put1");
queue.put("1");
System.out.println(Thread.currentThread().getName() + " put2");
queue.put("2");
System.out.println(Thread.currentThread().getName() + " put3");
queue.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "producer").start();
// 消费者线程
new Thread(() ->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + queue.take());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "consumer").start();
}
}
线程池
程序的运行:本质 就是占用系统资源,优化资源的使用--池化技术
线程池、连接池、内存池、对象池//创建、销毁十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完还给我
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复用、可以控制最大并发数,管理线程
三大方法
package pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors 工具类 3大方法
//使用了线程池之后,使用线程池来创建线程
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程,
ExecutorService threadPool2 = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小,最多5个线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i < 10; i++) {
threadPool3.execute(() -> {
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
threadPool3.shutdown();//线程池用完要关闭
}
}
}
7大参数及自定义线程池



package pool;
import java.util.concurrent.*;
public class Demo01 {
public static void main(String[] args) {
//自定义线程池,工作一般用ThreadPoolExecutor,基本不用Executors
ThreadPoolExecutor threadPool1 = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//最后一个是拒绝策略,一共有四种拒绝策略,这个是银行满了,还有进来,不处理直接抛出异常
//主要只有2个线程,最大5个线程,另外3个超过3秒没人就释放,LinkedBlockingDeque是队列
//补充另外3个拒绝策略new ThreadPoolExecutor.CallerRunsPolicy()-哪来的就回哪
//new ThreadPoolExecutor.DiscardPolicy()-丢弃任务,不处理,不会抛出异常
//new ThreadPoolExecutor.DiscardOldestPolicy()-丢弃队列里最旧的任务,然后执行当前任务
try {
//最多提交8个任务,9个会抛出异常
for (int i = 0; i < 9; i++) {
threadPool1.execute(() -> {
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
threadPool1.shutdown();//线程池用完要关闭
}
}
}
package pool;
import java.util.concurrent.*;
public class Demo01 {
public static void main(String[] args) {
//自定义线程池,工作一般用ThreadPoolExecutor,基本不用Executors
ThreadPoolExecutor threadPool1 = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//最后一个是拒绝策略,一共有四种拒绝策略,这个是银行满了,还有进来,不处理直接抛出异常
//主要只有2个线程,最大5个线程,另外3个超过3秒没人就释放,LinkedBlockingDeque是队列
//补充另外3个拒绝策略new ThreadPoolExecutor.CallerRunsPolicy()-哪来的就回哪
//new ThreadPoolExecutor.DiscardPolicy()-丢弃任务,不处理,不会抛出异常
//new ThreadPoolExecutor.DiscardOldestPolicy()-丢弃队列里最旧的任务,然后执行当前任务
//最大线程如何定义,
//CPU密集型:几核就定义几线程,理论上几核就说明几个线程可以同时并行,保持CPU的效率最高
//IO密集型:判断你程序中十分耗IO的部分,比如网络请求,数据库查询等,如果耗IO的线程占用了所有线程,则CPU效率会降低,所以要根据实际情况定义线程数,一般IO密集型线程数可以设置为CPU核数的两倍
try {
//最多提交8个任务,9个会抛出异常
for (int i = 0; i < 9; i++) {
threadPool1.execute(() -> {
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
threadPool1.shutdown();//线程池用完要关闭
}
}
}
四大函数式接口(必需掌握)
新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
//超级多这种类型
//简化编程模型,在新版本的框架底层大量应用
Function
package function;
import java.util.function.Function;
//Function 函数型接口,有一个目标输入参数,有一个输出
//只要是函数式接口,就可以用lambda表达式来表示
public class Demo01 {
public static void main(String[] args) {
Function function = new Function<String,String>() {
@Override
public String apply(String str) {
return str;
}
};
//Function function = (str) -> {return str;};//apply方法已经是默认方法,可以省略
System.out.println(function.apply("hello"));//hello
}
}
Predicate
package function;
import java.util.function.Predicate;
//断定型接口:有一个输入参数,返回值只能是boolean类型。
public class Demo02 {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>(){
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
//Predicate<String> predicate = s -> s.isEmpty();
System.out.println(predicate.test("hello"));
}
}
Consumer
package function;
import java.util.function.Consumer;
//Consumer 消费型接口,只有输入参数,没有返回值
public class Demo03 {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
//Consumer<String> consumer = (s) -> System.out.println(s);
consumer.accept("Hello");
}
}
Supplier
package function;
import java.util.function.Supplier;
//Supplier 供给型接口 没有参数 只有返回值
public class Demo04 {
public static void main(String[] args) {
Supplier supplier = new Supplier<Integer>(){
@Override
public Integer get() {
return 100;
}
};
System.out.println(supplier.get());
}
}
Stream流式计算
大数据:存储+计算
集合、MySQL本质就是存储
计算应该交给流来操作


ForkJoin
分支合并
并行执行任务,提高效率,大数据量
大数据:MAP Reduce(大任务拆分为小任务)

特点:工作窃取
B线程如果执行完,会把A线程未完成的工作偷过来执行
package forkjoin;
//如何使用forkjoin
//1.forkjoinPool 通过他来执行
//2.计算任务 forkjoinPool.execute(ForkJoinTask task)
public class ForkJoinDemo {
}
案例太乱了,
异步回调
Future设计的

package forkjoin;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
//异步调用:Ajax(服务器客户端),这里是用ComletableFuture来实现异步调用
//异步执行
//成功回调
//失败回调
public class FutureDemo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发起一个请求
/** CompletableFuture
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
});
System.out.println("111111");
completableFuture.get(); //获得执行结果,由于阻塞了会先输出111111,然后才输出runAsync=>Void
**/
//上面是没有返回值的,下面是有返回值的异步回调
//ajax,有成功和失败的回调
//失败返回的是错误信息
CompletableFuture
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
return 1024;
});
completableFuture.whenComplete((t ,u) -> {
System.out.println("t=>"+t);
System.out.println("u=>"+u);
}).exceptionally(e -> {
System.out.println(e.getMessage());
return 233;
}).get();
//ForkJoinPool.commonPool-worker-1supplyAsync=>Integer
//t=>1024
//u=>null
}
}
JMM
volatile是JAVA虚拟机提供轻量级的同步机制
1、保证可见性
import java.util.concurrent.TimeUnit;
public class JMMDemo {
//加了volatile关键字,可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(() -> {//线程1 对主内存的变化是不知道的,要在静态变量上加上volatile关键字
while(num==0){
}
}).start();
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num=1;
System.out.println("num="+num);
}
}
补充:
1. 计算机的多级缓存架构
现代 CPU 为了提高性能,设计了多级缓存(L1、L2、L3 缓存)和寄存器,而不是直接操作主内存(RAM)。
- 工作流程:
- 线程读取变量时,会从主内存加载到 CPU 缓存。
- 线程修改变量时,先修改缓存中的副本,之后可能延迟写回主内存。
- 问题:
如果线程 A 修改了缓存中的值,但未及时同步到主内存,线程 B 读取到的仍是旧值。

2. Java 内存模型(JMM)的抽象
Java 定义了线程的 工作内存(Working Memory) 和 主内存(Main Memory),这是一种抽象模型(不直接对应物理硬件):
- 工作内存:每个线程私有的内存区域,缓存了需要操作的共享变量副本。
- 主内存:所有线程共享的内存区域,存储共享变量的最终值。
线程间通信流程:
- 线程 A 修改共享变量时,先修改自己的工作内存中的副本。
- 工作内存的修改可能不会立即同步到主内存。
- 线程 B 读取共享变量时,从主内存(或自己的缓存)获取的可能是旧值。

3. 问题的具体表现
示例代码
public class VisibilityProblem {
private boolean flag = false; // 共享变量(未用 volatile)
public void start() {
// 线程1:循环等待 flag 变为 true
new Thread(() -> {
while (!flag) { // 可能永远无法退出循环!
// 空循环
}
System.out.println("Flag is now true!");
}).start();
// 线程2:修改 flag
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true; // 修改 flag
System.out.println("Flag set to true.");
}).start();
}
public static void main(String[] args) {
new VisibilityProblem().start();
}
}
可能的结果
- 线程2将
flag设为true,但线程1看不到更新,导致循环无法退出。 - 原因:线程1的本地缓存中
flag仍是false,未从主内存读取最新值。
4. 如何解决可见性问题?
方案 1:使用 volatile 关键字
private volatile boolean flag = false; // 强制读写直接操作主内存
- 作用:
- 确保线程每次读取
volatile变量时,都从主内存获取最新值。 - 确保线程每次写入
volatile变量时,立即刷新到主内存。
- 确保线程每次读取
2、不保证原子性(原子性操作)
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能分割,要么同时成功,要么同时失败
package forkjoin;
//不保证原子性
public class VDemo02 {
//volatile加了也不是等于2W,只有方法加了synchronized才是原子性
private static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上是2W
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
}
}
package forkjoin;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
////如果不用Lock和synchronized
////一般会使用原子类,解决原子性问题
/// 原子类的Integer
private volatile static AtomicInteger num = new AtomicInteger(0);
public static void add() {
//num++;
num.getAndIncrement();//AtomicInteger+1方法,CAS
}
public static void main(String[] args) {
//理论上是2W
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("num="+num.get());
}
}
这些类的底层是直接和操作系统挂钩,在内存中修改值
3、禁止指令重排
什么是指令重振:你写的程序,计算机并不是按照你写的那样去执行的
源代码》编译器优化后的重排》指令并行也可能会重排》内存系统也会重排》执行
处理器在进行指令重振的时候,会考虑数据之间的依赖性




JMM:JAVA内存模型,不存在的东西,概念,约定
关于JMM一些同步的约定:
1、线程解锁前,必须把共享变量立即刷回主存
2、线程回锁前,要读取主存中最新值到工作内存
3、加锁和解锁是同一把锁

单例模式
package single;
//饿汉式单例模式
public class Hungry {
private Hungry() {
}
private static Hungry HUNGRY= new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
package single;
//懒汉式单例模式
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "懒汉式单例模式创建实例");//构造方法私有化
}
private volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if(lazyMan == null) {
synchronized(LazyMan.class) {
if(lazyMan == null) {
lazyMan = new LazyMan();//不是原子性操作
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* 可能会出现指令重排序,导致线程不安全,所以一定要加volatile关键字
*/
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) {
for(int i=0;i<10;i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
深入理解CAS

package cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compare and set:比较并交换
//CAS是CPU的并发原语
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//public final boolean compareAndSet(int expectedValue, int newValue)
//期望、新值
//如果我期望的值达到了,那么就更新,否则就不更新
atomicInteger.compareAndSet(2020, 2021);
System.out.println(atomicInteger.get());//2021
}
}
补充
import java.util.concurrent.atomic.AtomicInteger;
public class CASThreadExample {
// 共享的账户余额,初始为100,使用AtomicInteger保证原子性
private static AtomicInteger account = new AtomicInteger(100);
public static void main(String[] args) throws InterruptedException {
// 线程1:尝试转出50元
Thread thread1 = new Thread(() -> {
// 记录操作前的值
int prev = account.get();
System.out.println("线程1 - 准备转账,当前余额: " + prev);
// 模拟网络延迟,让线程2有机会读取旧值
try { Thread.sleep(50); }
catch (InterruptedException e) { e.printStackTrace(); }
// 尝试CAS操作:期望值100 → 新值50
boolean success = account.compareAndSet(prev, prev - 50);
System.out.println("线程1 - 转账50元" + (success ? "成功" : "失败"));
});
// 线程2:尝试转出30元(但读取的是旧值100)
Thread thread2 = new Thread(() -> {
// 立即读取账户余额(此时可能还是100)
int prev = account.get();
System.out.println("线程2 - 准备转账,当前余额: " + prev);
// 尝试CAS操作:期望值100 → 新值70
boolean success = account.compareAndSet(prev, prev - 30);
System.out.println("线程2 - 转账30元" + (success ? "成功" : "失败"));
});
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
thread1.join(); // 等待线程1结束
thread2.join(); // 等待线程2结束
System.out.println("最终余额: " + account.get());
}
}
代码逐行解析
java
// 共享的账户余额,初始为100,使用AtomicInteger保证原子性
private static AtomicInteger account = new AtomicInteger(100);
关键点:所有线程操作同一个AtomicInteger对象
java
// 线程1逻辑
Thread thread1 = new Thread(() -> {
int prev = account.get(); // 1️⃣ 读取当前值(可能是100)
System.out.println("线程1 - 准备转账,当前余额: " + prev);
try { Thread.sleep(50); } // ⏳ 人为制造延迟
// 线程暂停50ms,让线程2有机会抢先执行CAS
boolean success = account.compareAndSet(prev, prev - 50);
// 3️⃣ 此时真实值可能已被修改,CAS会失败
});
java
// 线程2逻辑
Thread thread2 = new Thread(() -> {
int prev = account.get(); // 2️⃣ 在线程1休眠时读取值(此时仍是100)
System.out.println("线程2 - 准备转账,当前余额: " + prev);
boolean success = account.compareAndSet(prev, prev - 30);
// 🔄 抢先执行CAS:100 → 70成功
});
执行结果分析(可能的一种情况)
线程2 - 准备转账,当前余额: 100
线程1 - 准备转账,当前余额: 100
线程2 - 转账30元成功
线程1 - 转账50元失败
最终余额: 70
线程2抢先完成CAS:账户从100→70
线程1的CAS失败:因为当前值已经不是100
关键细节说明
竞态条件:两个线程几乎同时读取初始值100
线程2无延迟:比线程1更快执行CAS操作
原子性保证:即使线程1在操作前暂停,CAS仍能检测到值的变化
无锁设计:通过硬件级别的原子指令实现,比synchronized更高效
通过这个案例可以清晰看到CAS如何在多线程环境下确保数据一致性。
CAS:比较当前工作内存的值和主内存中的值,如果这个值是期望的,那么执行操作!如果不是,就一直循环
缺点:1,循环会耗时 2,一次性只能保证一个共享变量的原子性
3,ABA问题(狸猫换太子)
B线程改了两次又变回1,但是A不知

import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compare and set:比较并交换
//CAS是CPU的并发原语
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//public final boolean compareAndSet(int expectedValue, int newValue)
//期望、新值
//如果我期望的值达到了,那么就更新,否则就不更新
//====捣乱的线程===
atomicInteger.compareAndSet(2020, 2021);
System.out.println(atomicInteger.get());//2021
atomicInteger.compareAndSet(2021, 2020);
System.out.println(atomicInteger.get());
// //====正常的线程===
atomicInteger.compareAndSet(2020, 2025);
System.out.println(atomicInteger.get());
}
}
原子引用
带版本号的原子操作
解决ABA问题,就要引入原子引用
package cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
//乐观锁的原理,通过版本号来判断是否有其他线程修改了变量,
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2020);
//如果泛型是一个包装类,注意对象的引用类型
//正常业务操作,里面比较都是个个对象
AtomicStampedReference<Integer> atomicStampedReference= new AtomicStampedReference<>(1, 1);
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1 " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
atomicStampedReference.compareAndSet(1 ,2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);//比较并交换
System.out.println("a2 " + atomicStampedReference.getStamp());
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("b " + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
atomicStampedReference.compareAndSet(1, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);//比较并交换
System.out.println("b " + atomicStampedReference.getStamp());
}, "b").start();
}
}
//a1 1
//b 1
//a2 2
//a3 3
//b 4引入版本号,就能通过版本号来判断是否有其他线程修改了变量,如果有,则不更新变量,如果没有,则更新变量。
补充:
场景说明
ABA问题发生:线程1修改共享变量时,其他线程偷偷将其改回原值,导致CAS误判无变化
解决方案:使用AtomicStampedReference增加版本号跟踪历史操作
1. ABA问题发生案例(未加版本号)
java
import java.util.concurrent.atomic.AtomicInteger;
public class SimpleABA {
public static void main(String[] args) throws InterruptedException {
AtomicInteger value = new AtomicInteger(100);
// 线程1:先修改值,中间被其他线程恢复后再次操作
Thread t1 = new Thread(() -> {
// 第一次修改:100 → 50
value.compareAndSet(100, 50);
System.out.println("线程1 - 第一次修改成功:50");
// 中间其他线程偷偷将值改回100(模拟ABA)
// 尝试二次修改:100 → 80(成功!但实际上发生过变化)
boolean success = value.compareAndSet(100, 80);
System.out.println("线程1 - 第二次修改:" + (success ? "成功 (危险)" : "失败"));
});
// 线程2:偷偷恢复数据
Thread t2 = new Thread(() -> {
// 等待线程1第一次修改完成
try { Thread.sleep(10); } catch (Exception e) {}
// 恢复原值:50 → 100
value.compareAndSet(50, 100);
System.out.println("线程2 - 偷偷恢复为100");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果:" + value.get()); // 输出80,逻辑错误
}
}
输出结果:
线程1 - 第一次修改成功:50
线程2 - 偷偷恢复为100
线程1 - 第二次修改:成功 (危险)
最终结果:80
问题:虽然值被改回100,但中间存在隐藏操作,结果不符合预期(本应失败)
2. 使用AtomicStampedReference解决ABA
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class SimpleABASolution {
public static void main(String[] args) throws InterruptedException {
// 初始值100,版本号0
AtomicStampedReference<Integer> value =
new AtomicStampedReference<>(100, 0);
Thread t1 = new Thread(() -> {
int stamp = value.getStamp();
// 第一次修改:100→50,版本号0→1
value.compareAndSet(100, 50, stamp, stamp+1);
System.out.println("线程1 - 第一次修改成功:50,版本号1");
// 中间其他线程偷偷恢复并更新了版本号
// 错误地使用旧版本号0修改(真实版本号已是2)
boolean success = value.compareAndSet(100, 80, 0, 1);
System.out.println("线程1 - 第二次修改:" + (success ? "成功" : "失败"));
});
Thread t2 = new Thread(() -> {
// 等待线程1第一次修改完成
try { Thread.sleep(10); } catch (Exception e) {}
// 恢复100并升级版本号:50→100,版本号1→2
int[] stamp = new int[1];
int current = value.get(stamp);
value.compareAndSet(current, 100, stamp[0], stamp[0]+1);
System.out.println("线程2 - 恢复为100,版本号2");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果:" + value.getReference());
}
}
输出结果:
线程1 - 第一次修改成功:50,版本号1
线程2 - 恢复为100,版本号2
线程1 - 第二次修改:失败
最终结果:100
解决效果:第二次操作因版本号不匹配失败,避免逻辑错误
代码核心差异说明
功能点 ABA问题案例 ABA解决方案
数据结构 AtomicInteger(单值存储) AtomicStampedReference(值+版本号)
修改逻辑 仅比较数值是否相同 同时校验数值和版本号的匹配性
安全性 可能因中间操作导致错误 通过版本号彻底杜绝ABA问题
通过对比可得:版本号机制让系统能感知历史变化,即使数值恢复原值依然能够识别风险。
各种锁的理解
乐观锁,悲观锁
在 Java 中,乐观锁和悲观锁是用于处理多线程并发访问共享资源时的两种不同策略,它们的区别如下:
原理
乐观锁:
乐观地认为在多线程环境下,对共享资源的并发访问很少会发生冲突。
它假设多个线程可以同时访问和修改共享资源,在更新数据时才会检查是否有其他线程在期间对该资源进行了修改。
悲观锁:
悲观地认为在多线程环境下,对共享资源的并发访问很容易发生冲突。
所以在访问共享资源之前,就先获取锁,以阻止其他线程同时访问该资源,避免数据冲突。
实现方式
乐观锁:
通常通过版本号机制或 CAS(Compare and Swap)算法来实现。
版本号机制是在共享资源中添加一个版本号字段,每次更新资源时,版本号递增。线程在更新资源前,先比较当前版本号与资源的版本号,若一致则进行更新,并更新版本号;不一致则说明资源已被其他线程修改,可选择重试或其他处理方式。
CAS 算法则是通过比较内存中的值与预期值是否相等,若相等则将内存中的值替换为新值,这个过程是原子操作。例如AtomicInteger类就是利用 CAS 实现的乐观锁,它可以在不使用锁的情况下实现对整数的原子更新。
悲观锁:
在 Java 中,悲观锁通常通过 synchronized关键字或ReentrantLock等锁机制来实现。
synchronized关键字可以用于修饰方法或代码块,当一个线程进入同步方法或同步代码块时,会获取对象的锁,其他线程则需要等待锁的释放。
ReentrantLock是可重入锁,它提供了更灵活的锁获取和释放方式,通过lock()方法获取锁,unlock()方法释放锁。
性能
乐观锁:
由于没有锁的获取和释放操作,在并发度高且冲突较少的情况下,性能较好,能提高系统的吞吐量。
但在冲突频繁的情况下,会导致大量的重试操作,反而降低性能。
悲观锁:
在获取和释放锁时会有一定的性能开销。
在并发度高的情况下,容易导致线程阻塞,从而降低系统的并发性能,但在并发度低或冲突频繁的场景中,能有效保证数据的一致性,性能相对稳定。
适用场景
乐观锁:
适用于多读少写的场景,如缓存系统、读多写少的数据库表等。
例如,在一个电商系统中,商品信息的查询操作远远多于修改操作,就可以使用乐观锁来保证数据的一致性,提高系统的并发性能。
悲观锁:
适用于写操作比较频繁,或者对数据一致性要求很高的场景,如银行转账、库存管理等。
例如,在银行转账业务中,为了确保转账过程中账户余额的准确性和一致性,使用悲观锁可以防止其他线程在转账过程中修改账户余额。
公平锁,非公平锁
公平锁:不能够插队,必须先来后到
非公平负:可以插队(默认是非公平的):例如有些线程就3秒,有些线程得半个小时,肯定要让3秒的执行完
public RentrantLock(){
sync=new NonfairSync();
}
public ReentrantLock(boolean fair){
sync=fair?new FairSync():new NonfairSyn();
}
可重入锁
可重入锁(递归锁):拿到外面的锁,就可以拿到里面的锁
package lock;
import single.Hungry;
//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() + " is sending SMS");
call();//这里也有锁
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + " is calling");
}
}
自旋锁
package lock;
//定义一个自旋锁类
import javax.swing.plaf.PanelUI;
import java.util.concurrent.atomic.AtomicReference;
//
public class SpinlockDemo {
//int 0
//thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " is trying to lock");
//自旋锁
while (!atomicReference.compareAndSet(null, thread)) {
}
}
//解锁
public void unlock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " is trying to unlock");
atomicReference.compareAndSet(thread, null);
}
}
package lock;
import java.util.concurrent.TimeUnit;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
//底层使用自旋旋CAS
SpinlockDemo lock = new SpinlockDemo();
new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t2").start();
}
}
死锁
两个线程要互相抢对方的资源
public class DeadlockExample {
// 创建两个对象作为锁
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
// 创建第一个线程
Thread thread1 = new Thread(() -> {
// 线程 1 先获取 resource1 的锁
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1...");
try {
// 模拟一些操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2...");
// 尝试获取 resource2 的锁
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and 2...");
}
}
});
// 创建第二个线程
Thread thread2 = new Thread(() -> {
// 线程 2 先获取 resource2 的锁
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2...");
try {
// 模拟一些操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 1...");
// 尝试获取 resource1 的锁
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1 and 2...");
}
}
});
// 启动两个线程
thread1.start();
thread2.start();
}
}
代码解释
resource1 和 resource2 是两个不同的对象,作为锁使用。
thread1 先获取 resource1 的锁,然后尝试获取 resource2 的锁。
thread2 先获取 resource2 的锁,然后尝试获取 resource1 的锁。
当 thread1 持有 resource1 并等待 resource2,而 thread2 持有 resource2 并等待 resource1 时,就会发生死锁。
解决问题
1、使用jps -l定位进程号(IDEA的终端)
20692 lock.tt
2、使用jstack 查看死锁信息

排查问题:
1、日志
2、堆栈信息

浙公网安备 33010602011771号