java util concurrent 简称, java并发编程
源码+官网文档 jdk1.8在线文档
先回顾点基础知识:
进程:大, 一个程序 , 程序的集成
一个进程可以包含多个线程,至少包含一个!!!
java 默认有几个线程?2个 main、GC
线程:开了一个Typora进程,写字,自动保存(线程负责的)
Thread、Runnable、Callable
java真的可以开启线程吗? 开不了 new Thread().start() 源码中是调用native start0()本地方法,底层中的C++, java 无法直接操作硬件
并发编程:并发、并行
并发:(多线程操作同一资源)
并行:(多个人一起行走)
/*** 代码获取CPU核数** @author sdm* @version 1.0* @date 2023/6/19 15:03*/
public class test1 {public static void main(String[] args) {// 获取CPU的核数// CPU密集型、IO密集型System.out.Runtime().availableProcessors()); //8核}}
并发编程本质 充分利用CPU的资源
public enum State {// 1.新生NEW,// 2.运行RUNNABLE,// 3.阻塞BLOCKED,// 4.等待(死死的等)WAITING,// 5.超时等待(等3年,三年后等不下去了)TIMED_WAITING,// 6.终止TERMINATED;
}
1.来自不同的类
wait => Object
sleep => Thread
2.关于锁的释放
wait:会释放锁,是醒着等
sleep:睡觉了,抱着锁睡,不会释放!!!
3.使用的范围不同
wait:必选在同步代码块中
sleep:可以在任何地方睡
4.是否需要捕获异常
wait:必须需要捕获异常
sleep:必须要捕获异常,
因为可能会有超时等待 throws InterruptedException
Java 8 中文版 - 在线API中文手册 - 码工具 (matools)
传统Sychronize锁
package com.kuang.demo01;/*** 基本的卖票例子 不使用JUC(urrent.locks 三个接口: 1.Condition 2.Lock 3.ReadWriteLock)** @author sdm* @version 1.0* @date 2023/6/19 17:27*/
public class SaleTicketDemo01 {// 1. 准备资源类(属性、方法(sychnorized加锁,本质:队列、锁),对共享资源加锁,控制并发)// 2. 创建多线程// 3. 将资源类抛进去// 参考文章:Java线程池的正确使用方式——不要再new Thread了 static void main(String[] args) {Ticket ticket = new Ticket(); // 并发:多线程操作同一资源类,将资源类丢入线程new Thread().start();new Thread(new Runnable() { // @FunctionalInterface 函数式接口public void run() {// 匿名内部类,new Runnable(重写run方法)}}).start();new Thread(()->{ }, "线程名").start(); // jdk1.8之后,简化为Lambda表达式// 三个线程操作同一资源,三个线程同时卖票new Thread(()->{ for (int i = 0; i < 10; i++) { ticket.sale();}}, "A").start(); // 线程A卖60次new Thread(()->{ for (int i = 0; i < 10; i++) { ticket.sale();}}, "B").start(); // 线程B卖60次new Thread(()->{ for (int i = 0; i < 10; i++) { ticket.sale();}}, "C").start(); // 线程C卖60次// 期望出现结果:/*A卖出了第30票,剩余:29A卖出了第29票,剩余:28A卖出了第28票,剩余:27A卖出了第27票,剩余:26A卖出了第26票,剩余:25A卖出了第25票,剩余:24A卖出了第24票,剩余:23A卖出了第23票,剩余:22A卖出了第22票,剩余:21A卖出了第21票,剩余:20B卖出了第20票,剩余:19B卖出了第19票,剩余:18B卖出了第18票,剩余:17B卖出了第17票,剩余:16B卖出了第16票,剩余:15B卖出了第15票,剩余:14B卖出了第14票,剩余:13B卖出了第13票,剩余:12B卖出了第12票,剩余:11B卖出了第11票,剩余:10B卖出了第10票,剩余:9B卖出了第9票,剩余:8B卖出了第8票,剩余:7B卖出了第7票,剩余:6B卖出了第6票,剩余:5C卖出了第5票,剩余:4C卖出了第4票,剩余:3C卖出了第3票,剩余:2C卖出了第2票,剩余:1C卖出了第1票,剩余:0*/// 实际出现结果, 买票方法需要加锁,排队 public synchronized void sale()/*A卖出了第30票,剩余:29A卖出了第29票,剩余:28A卖出了第28票,剩余:27B卖出了第27票,剩余:26A卖出了第26票,剩余:25B卖出了第25票,剩余:24A卖出了第24票,剩余:23B卖出了第23票,剩余:22A卖出了第22票,剩余:21B卖出了第21票,剩余:20A卖出了第20票,剩余:19B卖出了第19票,剩余:18A卖出了第18票,剩余:17B卖出了第17票,剩余:16A卖出了第16票,剩余:15C卖出了第14票,剩余:13C卖出了第12票,剩余:11C卖出了第11票,剩余:10C卖出了第10票,剩余:9C卖出了第9票,剩余:8C卖出了第8票,剩余:7C卖出了第7票,剩余:6C卖出了第6票,剩余:5C卖出了第5票,剩余:4B卖出了第15票,剩余:14C卖出了第4票,剩余:3A卖出了第13票,剩余:12B卖出了第3票,剩余:2B卖出了第2票,剩余:1B卖出了第1票,剩余:0*/}
}// 资源类OOP 属性+方法
class Ticket {// 票数private int number = 30;// 卖票:每卖一次,票数减一public synchronized void sale(){if (number > 0){System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);}}
}
Lock接口
公平锁、非公平锁区别:
公平锁:先来后到
非公平锁:可以插队(默认非公平锁)
package com.kuang.demo01;import urrent.locks.Lock;
import urrent.locks.ReentrantLock;/*** 基本的卖票例子 使用JUC(urrent.locks 三个接口: 1.Condition 2.Lock 3.ReadWriteLock,使用Lock)** @author sdm* @version 1.0* @date 2023/6/19 17:27*/
public class SaleTicketDemo02 {/*公平锁、非公平锁区别:公平锁:先来后到非公平锁:可以插队(默认非公平锁)public ReentrantLock() {sync = new ReentrantLock.NonfairSync(); // 默认非公平锁}public ReentrantLock(boolean fair) {sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();}sychnorized 和 lock锁使用区别:sychnorized:手动挡,需要手动创建线程lock锁:自动挡,走lock三部曲就行1. new ReentrantLock(), new可重入锁2. Lock.lock()加锁,3. finally {Lock.unlock()} 解锁*/public static void main(String[] args) {// 并发:多线程操作同一资源类,将资源类丢入线程Ticket1 ticket1 = new Ticket1();new Thread(()->{for (int i = 0; i < 15; i++) ticket1.sale();}, "线程A").start(); // A线程卖15张票new Thread(()->{for (int i = 0; i < 15; i++) ticket1.sale();}, "线程B").start(); // B线程卖15张票new Thread(()->{for (int i = 0; i < 15; i++) ticket1.sale();}, "线程C").start(); // C线程卖15张票/*线程A卖出了第30票,剩余:29线程A卖出了第29票,剩余:28线程C卖出了第28票,剩余:27线程C卖出了第27票,剩余:26线程C卖出了第26票,剩余:25线程C卖出了第25票,剩余:24线程C卖出了第24票,剩余:23线程C卖出了第23票,剩余:22线程C卖出了第22票,剩余:21线程C卖出了第21票,剩余:20线程C卖出了第20票,剩余:19线程C卖出了第19票,剩余:18线程C卖出了第18票,剩余:17线程C卖出了第17票,剩余:16线程C卖出了第16票,剩余:15线程C卖出了第15票,剩余:14线程C卖出了第14票,剩余:13线程B卖出了第13票,剩余:12线程B卖出了第12票,剩余:11线程B卖出了第11票,剩余:10线程B卖出了第10票,剩余:9线程B卖出了第9票,剩余:8线程B卖出了第8票,剩余:7线程B卖出了第7票,剩余:6线程B卖出了第6票,剩余:5线程B卖出了第5票,剩余:4线程B卖出了第4票,剩余:3线程B卖出了第3票,剩余:2线程B卖出了第2票,剩余:1线程B卖出了第1票,剩余:0*/}
}class Ticket1 {// new一个可重入锁private final ReentrantLock lock = new ReentrantLock();private int number = 30;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(); // 释放锁}}
}
sychnorized:自动挡,不需要手动创建锁、释放锁
lock锁:手动挡,需要手动创建锁,释放锁,走lock三部曲就行
生产者和消费者 synorchized版 实现效果:交替打印A=>1、B=>0
package com.kuang.demo02;/*** 线程之间的通信问题:生产者消费者 (一个窗口,一次放一碗饭饭,阿姨这边打饭,同学这边拿饭)* 线程交替执行 A B 线程操作同一个变量 等待唤醒 wait,通知唤醒 notify* A num+1* B num-1** @author sdm* @version 1.0* @date 2023/6/24 11:12*/
public class Test {public static void main(String[] args) {// 1.准备资源类Goods goods = new Goods();// 2.创建多线程, 3.将资源类抛进去new Thread(() -> {for (int i = 0; i < 10; i++) {try {goods.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start(); // A线程+1new Thread(() -> {for (int i = 0; i < 5; i++) {try {goods.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start(); // B线程-1// 预期结果:A=>1 B=>0 A=>1 B=>0 .../*为什么出现交替打印结果?A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1线程A,判断number是否等于0,等于0,干活+1,然后唤醒B,不等于0,等着线程B,判断number是否等于1,等于1,干活-1,然后唤醒A,不等于0,等着*/}
}/*资源类:饭 一个窗口生产者消费者问题:六字真言: (判断)等待、业务、通知*/
class Goods{private int number = 0;//+1 生产了一件物品public synchronized void increment() throws InterruptedException {if (number != 0){ //O的时候,干活,不是0的时候,等待// 等待 窗口有一碗饭时,等会this.wait();}// 业务number ++;System.out.println(Thread.currentThread().getName() + "=>" + number);// 通知其他线程,我+1完毕了ifyAll(); // 通知唤醒其他线程}//-1 消费了一件物品public synchronized void decrement() throws InterruptedException {if (number == 0){ //1的时候,干活,不是1的时候,等待// 等待 窗口没有饭时,等会this.wait();}// 业务number --;System.out.println(Thread.currentThread().getName() + "=>" + number);// 通知其他线程,我-1完毕了ifyAll(); // 通知唤醒其他线程}
}
问题存在,A B C D 4个线程,还安全吗?
实现效果:交替打印 1、0 ,但是A B C D 不会精准交替
答:不安全 存在if判断等待时,发生虚假唤醒,需要替换为while判断等待
package com.kuang.demo02;/*** A B C D 线程交替打印*/
public class ThreadDemoABCD {public static void main(String[] args) {// 1.准备资源类Goods1 goods = new Goods1();// 2.创建多线程, 3.将资源类抛进去new Thread(() -> {for (int i = 0; i < 10; i++) {try {goods.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start(); // A线程+1new Thread(() -> {for (int i = 0; i < 10; i++) {try {goods.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start(); // B线程-1new Thread(() -> {for (int i = 0; i < 10; i++) {try {goods.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start(); // C线程+1new Thread(() -> {for (int i = 0; i < 10; i++) {try {goods.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "D").start(); // D线程-1/*打印结果:...B=>0 C=>1A=>2C=>3B=>2B=>1B=&存在这种打印结果,为什么?因为if判断,判断一次,会造成虚假唤醒(线程被唤醒,不会接到通、中断或超时,即虚假唤醒,所以唤醒条件防范,如果条件不满足,继续等待,等待应该总是出现在循环汇总), 解决办法:while循环判断 替换后,打印结果:1 0 交替*/}
}class Goods1{private int number = 0;//+1 生产了一件物品public synchronized void increment() throws InterruptedException {while (number != 0){ //O的时候,干活,不是0的时候,等待// 等待 窗口有一碗饭时,等会this.wait();}// 业务number ++;System.out.println(Thread.currentThread().getName() + "=>" + number);// 通知其他线程,我+1完毕了ifyAll(); // 通知唤醒其他线程}//-1 消费了一件物品public synchronized void decrement() throws InterruptedException {while (number == 0){ //1的时候,干活,不是1的时候,等待// 等待 窗口没有饭时,等会this.wait();}// 业务number --;System.out.println(Thread.currentThread().getName() + "=>" + number);// 通知其他线程,我-1完毕了ifyAll(); // 通知唤醒其他线程}
}
Sychronized : Object
Lock:JUC
实现效果:A B C D 线程精准唤醒,交替打印
package com.kuang.demo02;import urrent.locks.Condition;
import urrent.locks.Lock;
import urrent.locks.ReentrantLock;/*** A B C D 线程通信 JUC Lock+Condition** @author sdm* @version 1.0* @date 2023/6/24 15:23*/
public class ProductConsumerConditionDemoABCDJUC {public static void main(String[] args) {// 获取资源类Goods2 goods2 = new Goods2();// 创建多线程,将资源类抛进去new Thread(()->{for (int i = 0; i < 10; i++) {try {goods2.increment();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {goods2.decrement();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {goods2.increment();} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {goods2.decrement();} catch (InterruptedException e) {e.printStackTrace();}}},"D").start();/* 打印结果:A B C D 线程无序交替打印1、0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0*/}}// 等待,业务,通知
class Goods2{private int number = 0;Lock lock = new ReentrantLock();Condition condition = wCondition();public void increment() 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();}}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();}}
}
package com.kuang.demo02;import urrent.locks.Condition;
import urrent.locks.Lock;
import urrent.locks.ReentrantLock;
import java.util.zip.DataFormatException;/*** A B C D 线程通信 JUC Lock+Condition 精准通知打印:循环执行A->B->C** @author sdm* @version 1.0* @date 2023/6/24 15:23*/
public class ProductConsumerConditionDemoABCDJUCOrderly {public static void main(String[] args) {// 获取资源类Goods3 goods3 = new Goods3();// 创建多线程,将资源类抛进去new Thread(()->{for (int i = 0; i < 10; i++) {try {goods3.printA();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {goods3.printB();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {goods3.printC();} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();/* 实现效果:A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行A=>线程A执行B=>线程B执行C=>线程C执行*/}}// 等待,业务,通知
class Goods3{ //资源类Lock lock = new ReentrantLock();Condition condition1 = wCondition(); // 同步监视器:一个监视器只监视一个Condition condition2 = wCondition(); // 同步监视器:一个监视器只监视一个Condition condition3 = wCondition(); // 同步监视器:一个监视器只监视一个private int number = 1; // 1A 3B 5Cpublic void printA() throws InterruptedException {lock.lock();try {// 业务 等待,执行,通知while (number != 1){condition1.await();}number = 2;System.out.println(Thread.currentThread().getName() + "=>线程A执行");// 唤醒指定的人,Bcondition2.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public void printB() throws InterruptedException {lock.lock();try {// 业务 等待,执行,通知while (number != 2){condition2.await();}number = 3;System.out.println(Thread.currentThread().getName() + "=>线程B执行");// 唤醒指定的人,Ccondition3.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public void printC() throws InterruptedException {lock.lock();try {// 业务 等待,执行,通知while (number != 3){condition3.await();}number = 1;System.out.println(Thread.currentThread().getName() + "=>线程C执行");// 唤醒指定的人,Acondition1.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}
package com.kuang.lock8;import urrent.TimeUnit;public class Test1 {public static void main(String[] args) {//Q1:无锁时,先发消息?先打电话?//Phone1 phone1 = new Phone1();////new Thread(phone1::sendMessage, "A").start();//new Thread(phone1::call, "B").start();/*sendMessagecall*///Q2: sychronized锁类中的方法,先发消息?先打电话?// 有锁,锁了对象的调用者 phone2 ,所以肯定先 发短信,再打电话,// 验证:发短信里面延迟4s,依然是先 发短信,再 打电话,因为 phone2 在4s内都被锁死了,B线程不能执行// 两个方法用的同一把锁。谁先拿到谁先执行//Phone2 phone2 = new Phone2();//new Thread(phone2::sendMessage,"A").start(); 延迟1s//try {// TimeUnit.SECONDS.sleep(1);//} catch (InterruptedException e) {// e.printStackTrace();//}//new Thread(phone2::call,"B").start();/*发短信打电话*///Q3:发短信有锁sychronized,hello无锁 先发消息?先hello?//Phone2 phone2 = new Phone2();//new Thread(phone2::sendMessage,"A").start();//new Thread(phone2::hello,"B").start();/*hello发短信*///Q4:两个对象分别调用sychronized方法 哪个先发消息?//Phone2 phone21 = new Phone2();//Phone2 phone22 = new Phone2();//new Thread(()->{// for (int i = 0; i < 10; i++) {// phone21.sendMessage();// }//},"A").start();//new Thread(()->{// for (int i = 0; i < 10; i++) {// phone22.sendMessage();// }//},"B").start();/*...发短信-A 没有规则顺序,因为调用的不是一个对象,所以锁没有什么作用发短信-B发短信-B发短信-A发短信-B...*///Q5:静态synchronized方法,先发消息?先打电话? 坑:线程中间需要加睡眠,否则锁没有效果//Phone3 phone3 = new Phone3();//new Thread(()->{phone3.sendMessage();},"A").start();//new Thread(()->{phone3.call();},"B").start();/*发短信-A打电话-B*///Phone3 phone31 = new Phone3();//Phone3 phone32 = new Phone3();//new Thread(()->{phone31.sendMessage();},"A").start();//new Thread(()->{phone32.sendMessage();},"B").start();/*发短信-A打电话-B*///new Thread(()->{// for (int i = 0; i < 100; i++) {// phone31.sendMessage();// }//},"A").start();//new Thread(()->{// for (int i = 0; i < 100; i++) {// phone32.sendMessage();// }//},"B").start();/*...发短信-A发短信-B发短信-A发短信-A...为什么存在这种现象?既然静态锁的对象是类的模板,不应该是 发完消息,再打电话?猜测:会不会是执行完方法后就释放模板锁,在这个间隙,B线程获取到了模板锁,然后执行了?原因:参考文章:—在线程run方法中是否需要当前线程睡眠一段时间。要了解sleep方法,那么首先得了解到它的原理及其使用方法。使用简介:在这里以JAVA的API为例(当然其它语言亦可以),JAVA的API中是这么描述的,public static void sleep(long millis,int nanos)throws InterruptedException在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。参数: millis - 以毫秒为单位的休眠时间。 nanos - 要休眠的另外 0-999999 纳秒。 抛出: IllegalArgumentException - 如果 millis 值为负或 nanos 值不在 0-999999 范围内。 InterruptedException - 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除 API中说的很明确,这个方法的目的就是让线程休眠,并且这个操作其实是没有释放锁的。那么问题来了,sleep到底什么时候用呢?我们在使用多线程的时候会发现,有时候需要程序运行时间特别长了会经常出现一些问题,或者当前我们开启了多个线程它们分别执行几个任务,但是因为执行的任务时间非常短,有时候cpu切换时候会出现一系列的问题,那么这时候可能的原因就有是否因为cpu一直在执行一个线程或者其他的原因呢。当我们设置sleep时,等于告诉cpu,当前的线程不再运行,持有当前对象的锁。那么这个时候cpu就会切换到另外的线程了。这种操作有些时候是非常好的。那么回归标题,究竟应不应该使用sleep呢,LZ认为还是具体业务具体分析,看是否需要添加此方法。*///new Thread(()->{// for (int i = 0; i < 100; i++) {// phone31.sendMessage();// }//},"A").start();//try {// TimeUnit.SECONDS.sleep(5); // 这里为什么要睡一下,才能打印出预想结果?// // 猜测:// // A调用方法的时候,持有锁,循环下一次的间隙中,CPU会想要切换执行线程B// // 如果不加睡眠,线程B会在这个间隙获取锁、获取CPU执行,这样就会有问题,锁失去了意义// // 所以,在想开启线程B的时候加个睡眠,等待A下次循环调用发消息方法时唤醒A线程,且让其获取锁,这样保证A继续持有CPU,避免B线程执行// // A执行完后,睡5秒,5秒内A没有循环调用持有锁了,就开始执行线程B,B获取锁// // 原因:// // 因为锁在for循环里面,所以锁会被释放,可以把锁放在for循环外面,如Test2,这样就不会有这个问题了//} catch (InterruptedException e) {// e.printStackTrace();//}//new Thread(()->{// for (int i = 0; i < 100; i++) {// phone32.sendMessage();// }//},"B").start();//Q5:1对象分别调用static sychronized发消息, sychronized打电话,哪个先执行?//Phone4 phone4 = new Phone4();//new Thread(Phone4::sendMessage,"A").start();// 锁Class对象//new Thread(phone4::call,"B").start();// 锁调用者/*打电话-B 锁的不是一个东西,发消息睡眠1s,就先打印出打电话了发短信-A*///Q6:2对象分别调用static sychronized发消息, sychronized打电话,哪个先执行?Phone4 phone5 = new Phone4();Phone4 phone6 = new Phone4();new Thread(Phone4::sendMessage,"A").start();// 锁Class对象new Thread(phone6::call,"B").start();// 锁调用者/*打电话-B 锁的不是一个东西,发消息睡眠1s,就先打印出打电话了发短信-A*/}
}/*无锁*/
class Phone1{public void sendMessage(){System.out.println("sendMessage");}public void call(){System.out.println("call");}
}/*sychronized锁锁的对象是方法的调用者*/
class Phone2{public synchronized void sendMessage() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信-"+Thread.currentThread().getName());}public synchronized void call(){System.out.println("打电话-"+Thread.currentThread().getName());}public void hello(){System.out.println("hello-"+Thread.currentThread().getName());}
}/*static sychronized锁锁的对象是类的模板类一加载就有了Class对象Phone3只有唯一的Class对象 Class<Phone3> phone3Class = Phone3.class;sendMessage()和call()方法都被static修饰时,用的是同一个锁*/
class Phone3{public static synchronized void sendMessage(){System.out.println("发短信-"+Thread.currentThread().getName());}public static synchronized void call(){System.out.println("打电话-"+Thread.currentThread().getName());}
}/*1个static sychronized锁1个sychronized锁*/
class Phone4{// 锁的Class类模板public static synchronized void sendMessage(){try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信-"+Thread.currentThread().getName());}// 锁的调用者public synchronized void call(){System.out.println("打电话-"+Thread.currentThread().getName());}
}
package com.kuang.lock8;import javax.swing.plaf.synth.SynthCheckBoxMenuItemUI;
import java.util.Objects;
import urrent.TimeUnit;/*** 实现Runnable接口,重写run方法** Java多线程实现-Runnable接口 */
class Person implements Runnable {private String id;private String name;private String loginType;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getLoginType() {return loginType;}public void setLoginType(String loginType) {this.loginType = loginType;}@Overridepublic void run() {// 类锁,锁类的Class对象(模板)synchronized (Person.class){for (int i = 0; i < 1000; i++) {System.out.println(Thread.currentThread().getName()+"===========");}}// for循环放在锁外面,线程之间就需要加睡眠,否则会出现CPU多次切换线程的情况//synchronized (Person.class){// System.out.println(Thread.currentThread().getName()+"===========");//}}@Overridepublic boolean equals(Object obj) {if (obj == null){return false;}if (obj == this || getClass() == Class()){return true;}if (obj instanceof Person){Person person = (Person) obj;return this.id.Id()) && this.name.Name());}return false;}@Overridepublic int hashCode() {return Objects.hash(id,name);}@Overridepublic String toString() {return "Person{" +"id='" + id + ''' +", name='" + name + ''' +", loginType='" + loginType + ''' +'}';}public static void main(String[] args) {Person personA = new Person();Person personB = new Person();new Thread(personA).start();new Thread(personB).start();//for循环放在锁外面,线程之间就需要加睡眠,否则会出现CPU多次切换线程的情况//new Thread(()->{// for (int i = 0; i < 1000; i++) {// personA.run();// }//}).start();//try {// TimeUnit.SECONDS.sleep(1);//} catch (InterruptedException e) {// e.printStackTrace();//}//new Thread(()->{// for (int i = 0; i < 1000; i++) {// personB.run();// }//}).start();}
}
List、单线程下是安全的,并发情况下不安全。
并发情况下,有些集合类会报错:并发修改异常 ConcurrentModificationException
package com.kuang.unsafe;import java.util.*;
import urrent.CopyOnWriteArrayList;/*** List 单线程下是安全的,并发情况下不安全。* 并发情况下,有些集合类会报错:并发修改异常 ConcurrentModificationException*/
public class ListTest {public static void main(String[] args) {/*** 并发下ArrayList不安全** 解决方案:* 1. Vector List<String> list = new Vector<>();* 2. Collections.synchronizedList List<String> list = Collections.synchronizedList(new ArrayList<>());* 3. CopyOnWriteArrayList() List<String> list = new CopyOnWriteArrayList<>();* 写入时复制,cow,计算机程序设计领域的一种优化策略* 多个线程调用的时候,list, 读取的时候,固定的,写入(覆盖)* 在写入的时候避免重复,造成数据问题!* 读写分类* CopyOnWriteArrayList比 Vector 好在哪里? vector 用的 sychronized,效率低一点,CopyOnWriteArrayList比 用了lock锁,效率搞一点**/// 单线程没有问题//List<String> list = new ArrayList<>();//for (int i = 0; i < 1000; i++) {// list.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(list);//}//出现并发修改异常//List<String> list = new ArrayList<>();//for (int i = 0; i < 1000; i++) {// new Thread(()->{// list.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(list);// }).start();//}// 方式1 Vector//List<String> list = new Vector<>();//for (int i = 0; i < 1000; i++) {// new Thread(()->{// list.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(list);// }).start();//}// 方式2 将list抛入Collections.synchronizedList()//List<String> list = Collections.synchronizedList(new ArrayList<>());//for (int i = 1; i <= 10; i++) {// new Thread(()->{// list.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(list);// }, String.valueOf(i)).start();//}// 方式3 new一个写入时复制list, CopyOnWriteArrayList()List<String> list = new CopyOnWriteArrayList<>();for (int i = 1; i <= 10; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);}, String.valueOf(i)).start();}}
}
package com.kuang.unsafe;import java.util.Map;
import java.util.UUID;
import urrent.ConcurrentHashMap;/*** Map 单线程下是安全的,并发情况下不安全。* 并发情况下,有些集合类会报错:并发修改异常 ConcurrentModificationException** 解决办法:* 方式1 集合工具类+同步锁 Collections.synchronizedMap* 方式2 并发哈希Map ConcurrentHashMap*/
public class MapTest {public static void main(String[] args) {// 单线程下安全//Map<String, String> map = new HashMap<>();加载因子,初始化容量 new HashMap<>(16, 0.75);//for (int i = 0; i < 1000; i++) {// map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));// System.out.println(map);//}// 多线程下,发生并发修改异常//Map<String, String> map = new HashMap<>();加载因子,初始化容量 new HashMap<>(16, 0.75);//for (int i = 0; i < 100; i++) {// new Thread(()->{// map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));// System.out.println(map);// }).start();//}// 方式1 集合工具类+同步锁 Collections.synchronizedMap//Map<String, String> map = Collections.synchronizedMap(new HashMap<>());加载因子,初始化容量 new HashMap<>(16, 0.75);//for (int i = 0; i < 100; i++) {// new Thread(()->{// map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));// System.out.println(map);// }).start();//}// 方式2 并发哈希Map ConcurrentHashMapMap<String, String> map = new ConcurrentHashMap<>();// 加载因子,初始化容量 new HashMap<>(16, 0.75);for (int i = 0; i < 100; i++) {new Thread(()->{map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));System.out.println(map);}).start();}}
}
package com.kuang.unsafe;import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import urrent.CopyOnWriteArraySet;/*** Set 单线程下是安全的,并发情况下不安全。* 并发情况下,有些集合类会报错:并发修改异常 ConcurrentModificationException** 解决办法:* 方式1 工具类Collections.synchronizedSet* 方式2 写入时复制 new CopyOnWriteArraySet<>()*** hashSet底层时什么?* public HashSet() {* map = new HashMap<>();* }
* // set.add set本质是map,key无法重复!
* public boolean add(E e) {
* return map.put(e, PRESENT)==null;
* }
* // 不变的值
* private static final Object PRESENT = new Object();*/
public class SetTest {public static void main(String[] args) {// 单线程下,没有问题//HashSet<Object> set = new HashSet<>();//for (int i = 0; i < 1000; i++) {// set.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(set);//}// 多线程下,并发修改异常//Set<Object> set = new HashSet<>();//for (int i = 0; i < 1000; i++) {// new Thread(()->{// set.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(set);// }, String.valueOf(i)).start();//}// 方式1 工具类Collections.synchronizedSet//Set<Object> set = Collections.synchronizedSet(new HashSet<>());//for (int i = 0; i < 1000; i++) {// new Thread(()->{// set.add(UUID.randomUUID().toString().substring(0,5));// System.out.println(set);// }, String.valueOf(i)).start();//}// 方式2 写入时复制 new CopyOnWriteArraySet<>()Set<Object> set = new CopyOnWriteArraySet<>();for (int i = 0; i < 1000; i++) {new Thread(()->{set.add(UUID.randomUUID().toString().substring(0,5));System.out.println(set);}, String.valueOf(i)).start();}}}
与Runnable区别:
Callable有返回值(类型就是参数类型);Runnable无返回值
package com.kuang.callable;import urrent.Callable;
import urrent.FutureTask;/*** Callable实现模板**/
public class CallableTest {public static void main(String[] args) throws Exception {//启动callableMyThread thread = new MyThread();//适配类:FutureTaskFutureTask futureTask = new FutureTask(thread);new Thread(futureTask,"A").start();new Thread(futureTask,"B").start();/*这里两个线程的时候,不会打印2个call这里测试过了,实际上不是缓存,是只执行了一次,也就是两个线程后面进入的会覆盖前面的,也有可能是后面进入的被抛弃了,暂时没有更细化的研究这个并不是缓存,是由于JVM第二次再调用FutrueTask对象所持有的线程,此时FutrueTask的state此时已非NEW状态(各个状态,这边不做详细解释)Possible state transitions:NEW -> COMPLETING -> NORMALNEW -> COMPLETING -> EXCEPTIONALNEW -> CANCELLEDNEW -> INTERRUPTING -> INTERRUPTEDprivate volatile int state;private static final int NEW = 0;private static final int COMPLETING = 1;private static final int NORMAL = 2;private static final int EXCEPTIONAL = 3;private static final int CANCELLED = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED = 6;从这些中间状态到最终状态的转换使用更便宜的有序/延迟写入,因为值是唯一的,不能进一步修改。可能的状态转换:新建->完成->正常新建->完成->异常新建->取消新建->中断->中断*///获取Callable的返回结果Integer o = (Integer) ();//这个get方法可能会产生阻塞!//或者使用异步通信来处理!System.out.println(o);}
}
class MyThread implements Callable<Integer>{@Overridepublic Integer call() throws Exception {System.out.println("call");//耗时的操作return 1024;}
}
减法计数器
package com.kuang.helperclass;import urrent.CountDownLatch;/*** 倒计时计数器(减法计数器)** 理解:* 就像保安锁门,需要等所有人都走完再锁门,如果还有人,就不能锁门** 原理:* untDown();数量减一* countDownLatch.await();等待计数器归0* 每次有线程调用 countDown()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行**/
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 总数是6CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <= 6; i++) {final int finalI = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+ "线程走了,i="+ finalI);untDown();//每走一个线程,数量-1}).start();}countDownLatch.await(); //等待计数器归0,再向下执行,本质理解:await()执行时,会判断上面线程是否执行完,没有完的话,不会切换CPU给主线程System.out.println(Thread.currentThread().getName()+"保安要等6个线程都执行完了,主线程关门");/*Thread-0线程走了,i=1Thread-4线程走了,i=5Thread-5线程走了,i=6Thread-3线程走了,i=4Thread-2线程走了,i=3Thread-1线程走了,i=2main保安要等6个线程都执行完了,主线程关门*/}
}
加法计数器
package com.kuang.helperclass;import urrent.BrokenBarrierException;
import urrent.CyclicBarrier;/*** 加法计数器** 理解:* 就相当于集齐7个龙珠,才能召唤神龙!**/
public class CyclicBarrierDemo {public static void main(String[] args) {// 召唤神龙主线程CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { // 如果这里需要收集8颗龙珠,那线程就会一直等待System.out.println("7颗龙珠收集成功,神龙出世!!!");});// 收集7龙珠子线程for (int i = 1; i <= 7; i++) {final int finalI = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"收集龙珠"+finalI+"号");try {cyclicBarrier.await(); //等待,等这个线程走完,才能收集下一个, 直到收集到第7个,唤醒主线程} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}).start();}/*Thread-0收集龙珠1号Thread-4收集龙珠5号Thread-3收集龙珠4号Thread-2收集龙珠3号Thread-1收集龙珠2号Thread-6收集龙珠7号Thread-5收集龙珠6号7颗龙珠收集成功,神龙出世!!!*/}}
信号量
package com.kuang.helperclass;import urrent.Semaphore;
import urrent.TimeUnit;/*** Semaphore 信号量* 限流的时候使用!流量入口就这么大,不可能一次进来很多!* Semaphore: 同一时间只能有指定数量个得到线程* CyclicBarrier : 指定个数线程执行完毕再执行操作*** 理解:* 抢车位!6个车-3个停车位* 123 3走了,5进来 -> 125 2走了,6进来 -> 165*** 原理:* semaphore.acquire(); 获得,假设如果已经满了,等待,等待被释放为止!* lease(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程!* 作用:* 多个共享资源互斥的使用!* 并发限流,控制最大的线程数!*/
public class SemaphoreDemo {public static void main(String[] args) {// 线程数量入口限流!停车位3Semaphore semaphore = new Semaphore(3);// 6个线程访问!6个车抢停车位for (int i = 1; i <= 6; i++) {new Thread(()->{try {// acquire() 得到semaphore.acquire();System.out.println(Thread.currentThread().getName() + "抢到车位");TimeUnit.SECONDS.sleep(5);System.out.println(Thread.currentThread().getName() + "离开车位");} catch (InterruptedException e) {e.printStackTrace();} finally {lease(); // 释放}},String.valueOf(i)).start();}/*1抢到车位2抢到车位4抢到车位(========停顿5s========)1离开车位4离开车位2离开车位5抢到车位3抢到车位6抢到车位(========停顿5s========)6离开车位3离开车位5离开车位*/}
}
ReadWriteLock
package com.kuang.rwlock;import java.util.HashMap;
import java.util.Map;
import urrent.locks.ReentrantReadWriteLock;/*** ReadWriteLock 读写锁* 读-读 可以共存!* 读-写 可以共存!* 写-写 不能共存!* 独占锁(写锁):一次只能被一个线程占有!* 共享锁(读锁):多个线程可以同时占用!** 需求:* 想要写的时候,只有一个线程写,读的时候,可以多个线程读(写的时候,希望是原子性操作)* 使用 sychronized 锁的是方法的调用者, 使用 static sychronized 锁的是Class模板,调用这些方法都会锁住,* 但是我们想要更细力度控制:写的时候,只有一个线程写,读的时候,可以多个线程读,* 所以,使用读写锁*****/
public class ReadWriteLockDemo {public static void main(String[] args) {// 无锁时//MyCache myCache = new MyCache(); 写入//for (int i = 1; i <= 5; i++) {// final int finalI = i;// new Thread(()->{// myCache.put(finalI+"", "值");// },"写线程-"+i).start();//} 读取//for (int i = 1; i <= 5; i++) {// final int finalI = i;// new Thread(()->{// (finalI+"");// },"读线程-"+i).start();//}// 读写锁时MyCacheWithReadWriteLock myCache = new MyCacheWithReadWriteLock();// 写入for (int i = 1; i <= 5; i++) {final int finalI = i;new Thread(()->{myCache.put(finalI+"", "值");},"写线程-"+i).start();}// 读取for (int i = 1; i <= 5; i++) {final int finalI = i;new Thread(()->{(finalI+"");},"读线程-"+i).start();}/*实现效果:写线程原子性操作,读线程随意读取,所有人都可以读,可以随意插队写线程-1写入key=1,value=值写线程-1写入ok写线程-4写入key=4,value=值写线程-4写入ok写线程-5写入key=5,value=值写线程-5写入ok写线程-2写入key=2,value=值写线程-2写入ok写线程-3写入key=3,value=值写线程-3写入ok读线程-2读取2读线程-2读取ok值读线程-3读取3读线程-1读取1读线程-5读取5读线程-1读取ok值读线程-3读取ok值读线程-4读取4读线程-5读取ok值读线程-4读取ok值*/}}/*自定义缓存 读写锁*/
class MyCacheWithReadWriteLock{private volatile Map<String, Object> map = new HashMap<>();//private Lock lock = new ReentrantLock();// 读写锁:更细粒度控制加锁private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();// 存,写入的时候,只希望同时只有一个线程写!!!public void put(String key, Object value){// 写锁加锁readWriteLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + "写入key=" + key + ",value=" + value);map.put(key, value);System.out.println(Thread.currentThread().getName() + "写入ok");}catch (Exception e){e.printStackTrace();}finally {// 读写锁释放锁readWriteLock.writeLock().unlock();}}// 取,读的时候,希望所有人都能读!!!public void get(String key){// 读锁加锁adLock().lock();try {System.out.println(Thread.currentThread().getName() + "读取" + key);Object o = (key);System.out.println(Thread.currentThread().getName() + "读取ok"+o);}catch (Exception e){e.printStackTrace();}finally {adLock().unlock();}}
}/*自定义缓存 无锁的*/
class MyCache{private volatile Map<String, Object> map = new HashMap<>();// 存,写public void put(String key, Object value){System.out.println(Thread.currentThread().getName() + "写入key=" + key + ",value=" + value);map.put(key, value);System.out.println(Thread.currentThread().getName() + "写入ok");}// 取,读public void get(String key){System.out.println(Thread.currentThread().getName() + "读取" + key);Object o = (key);System.out.println(Thread.currentThread().getName() + "读取ok"+o);}/*预期出现:1写入,1写入完成, 2写入,2写入完成,...出现结果:1还没有写入oK,5写入,写的时候会被插队这样会有什么问题吗? 同时写入,CPU切换过于频繁,你想要的效果是,一个写入完成后写入下一个,所以没有达到预期效果写线程-1写入key=1value=值写线程-5写入key=5value=值写线程-5写入ok写线程-4写入key=4value=值写线程-3写入key=3value=值写线程-2写入key=2value=值写线程-3写入ok写线程-4写入ok写线程-1写入ok写线程-2写入ok读线程-1读取1读线程-2读取2读线程-1读取ok读线程-2读取ok读线程-3读取3读线程-3读取ok读线程-4读取4读线程-5读取5读线程-4读取ok读线程-5读取ok*/
}
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞,等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
检测队首元素 | element | peek | - | - |
package com.kuang.blockqueue;import dia.sound.SoftTuning;import urrent.ArrayBlockingQueue;
import urrent.BlockingQueue;
import urrent.LinkedBlockingDeque;
import urrent.TimeUnit;/*** 阻塞队列: BlockingQueue* 下面有:* ArrayBlockingQueue (这个)* LinkedBlockingDeque* 什么情况下对列发生阻塞?* FIFO先进先出* 写入:如果队列满了,就必须阻塞等待* 取:如果是队列为空,必须阻塞等待生产* 什么情况下使用阻塞队列?* 多线程并发处理,线程池!* 阻塞队列有哪些操作?* 添加、移除两个操作* 四组API,实现不同效果:* 1.抛出异常* 检查队首元素:element()* add() remove() 组合处理添加、移除操作* 插入成功返回true,队列满了,插入失败返回异常* 非法状态异常:IllegalStateException Exception:Queue full* 2.不会抛出异常* 检查队首元素:peek()* offer() poll() 组合处理添加、移除操作* 插入成功返回true,队列满了,插入失败返回false* 如果队列满了,再添加时,不会抛出异常* 3.阻塞等待* put() task() 组合处理添加、移除操作* 无返回值* 如果队列满了,再添加时,会一直阻塞等待,不结束* 4.超时等待* offer(,,) poll(,) 组合处理添加、移除操作* 如果队列满了,再添加时,会一直阻塞等待,直到超多一定时间,就不等待了*/
public class Test {public static void main(String[] args) throws InterruptedException {//test1();//test2();//test3();test4();}/*1、抛出异常*/public static void test1(){// 队列大小3ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);// add 存,返回true|falseSystem.out.println(blockingQueue.add("a"));System.out.println(blockingQueue.add("b"));System.out.println(blockingQueue.add("c"));// 查看队首元素System.out.println(blockingQueue.element());System.out.println("========多余的开始进入=======");System.out.println(blockingQueue.add("d"));// remove 取,返回true|falseSystem.out.ve("a"));System.out.ve("b"));System.out.ve("c"));/*truetruetruea========多余的开始进入=======Exception:IllegalStateException非法状态异常 Queue full*/}/*2、不会抛出异常*/public static void test2(){// 队列大小3ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);// offer 存,返回true|falseSystem.out.println(blockingQueue.offer("a"));System.out.println(blockingQueue.offer("b"));System.out.println(blockingQueue.offer("c"));// 查看队首元素System.out.println(blockingQueue.peek());System.out.println("========多余的开始进入=======");System.out.println(blockingQueue.offer("d"));// poll 取,返回true|falseSystem.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());/*truetruetruea========多余的开始进入=======falseabc*/}/*3、阻塞等待*/public static void test3() throws InterruptedException {// 队列大小3ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);// offer 存,无返回值blockingQueue.put("a");blockingQueue.put("b");blockingQueue.put("c");System.out.println("========多余的开始进入=======");blockingQueue.put("d");// poll 取,无返回值blockingQueue.take();blockingQueue.take();blockingQueue.take();/*========多余的开始进入=======一直阻塞,不结束*/}/*4、超时等待*/public static void test4() throws InterruptedException {// 队列大小3ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);// offer 存,无返回值blockingQueue.offer("a");System.out.println("放入a");blockingQueue.offer("b");System.out.println("放入b");blockingQueue.offer("c");System.out.println("放入c");System.out.println("========多余的开始进入=======");blockingQueue.offer("d",5, TimeUnit.SECONDS); // 等待超过5秒,还没有位置,就结束等待// poll 取,无返回值blockingQueue.poll();System.out.println("取出a");blockingQueue.poll();System.out.println("取出b");blockingQueue.poll();System.out.println("取出c");System.out.println("========开始取出队列已经没有的=======");blockingQueue.poll(5,TimeUnit.SECONDS); // 等待超过2秒,还没有东西可以拿,就结束等待,不拿了,下面没有执行语句了,就退出/*放入a放入b放入c========多余的开始进入=======取出a取出b取出c========开始取出队列已经没有的=======等待5s后,没有进入的可以取出,退出*/}}
package com.kuang.blockqueue;import urrent.BlockingQueue;
import urrent.SynchronousQueue;
import urrent.TimeUnit;/*** 同步队列* 和其他 BlockingQueue 不一样,SychronousQueue 不存储元素* 没有容量,进去一个元素,必须等待取出来之后,才能再往里放一个元素!* put、take*/
public class SynchronousQueueTest {public static void main(String[] args) {// 同步队列 SynchronousQueue mplements BlockingQueue//SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();BlockingQueue<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()+"=>"+synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());} catch (InterruptedException e) {e.printStackTrace();}},"T2:").start();}/*T1:put 1T2:=>1T1:put 2T2:=>2T1:put 3T2:=>3*/
}
核心线程数2;最大线程数5;阻塞队列:候客区;拒绝进入:拒绝策略
超时释放:
代码实例:
第一个拒绝策略:
1个人进去
5个人进去(3个人在候客厅)
8个人进去(5个窗口都开放+3个在候客厅)
9个人进去(多1个人,被拒绝进入,抛出异常)
第二个拒绝策略:
第三种拒绝策略:
第四种拒绝策略:
池的最大线程的大小如何设置?
package com.kuang.threadpool;import urrent.ExecutorService;
import urrent.Executors;/*** 如果创建线程池?* 线程池不允许使用Executors直接创建,需要通过ThreadPoolExecutor创建,避免资源耗尽;* 原因1:固定线程池(FixedThreadPool)和单例线程池(SingleThreadPool)允许的 请求队列 长度为Integer.MAX_VALU,约21亿,可能会堆积大量请求,导致OOM内存溢出* 原因2:可变线程池(CachedThreadPool)和调度线程池(ScheduledThreadPool)允许的 创建线程数量 为Integer.MAX_VALUE,可能会创建大量线程,导致OOM* Executors 工具类,3大方法* wSingleThreadExecutor(); //单个线城池* wFixedThreadPool(5); //固定的线程池大小* wCachedThreadPool(); //可伸缩的*/
public class Demo01 {public static void main(String[] args) {// 1.单个线城池ExecutorService singleThread = wSingleThreadExecutor();// 2.创建一个固定的线程池大小ExecutorService fixedThreadPool = wFixedThreadPool(5);// 3.可伸缩的,遇强则强,遇弱则弱ExecutorService cachedThreadPool = wCachedThreadPool();// 1.单线程池;创建100个线程//try{// for (int i = 1; i < 100; i++) {// ute(()->{// System.out.println(Thread.currentThread().getName() + ":ok");// });// }//}catch (Exception e){// e.printStackTrace();//}finally {// // 线程池用完,程序结束,关闭线程池// singleThread.shutdown();//}/*...pool-1-thread-1:ok 使用的都是线程池1中的线程1pool-1-thread-1:*/// 2.固定的线程池;创建100个线程//try{// for (int i = 1; i < 100; i++) {// ute(()->{// System.out.println(Thread.currentThread().getName() + ":ok");// });// }//}catch (Exception e){// e.printStackTrace();//}finally {// // 线程池用完,程序结束,关闭线程池// singleThread.shutdown();//}/*...pool-2-thread-2:ok 使用线程池2中的线程,最大5pool-2-thread-5:okpool-2-thread-4:okpool-2-thread-1:okpool-2-thread-3:okpool-2-thread-1:okpool-2-thread-4:okpool-2-thread-2:*/// 3.可伸缩的线程池;创建100个线程try{for (int i = 1; i < 100; i++) {ute(()->{System.out.println(Thread.currentThread().getName() + ":ok");});}}catch (Exception e){e.printStackTrace();}finally {// 线程池用完,程序结束,关闭线程池singleThread.shutdown();}/*...pool-3-thread-12:ok 使用线程池3中的线程pool-3-thread-16:okpool-3-thread-18:okpool-3-thread-16:okpool-3-thread-8:okpool-3-thread-14:okpool-3-thread-9:okpool-3-thread-8:*/}}
package com.kuang.threadpool;import urrent.*;/*** 创建线程池的3大方法,都有7大参数,参数含义是什么?* corePoolSize, // 核心线程池大小* maximumPoolSize, // 最大核心线程池大小* keepAliveTime, // 保持存活时长* unit, // 超时释放单位* workQueue, // 阻塞队列:默认最大21亿* Executors.defaultThreadFactory(), // 线程工厂,创建线程的,一般不动* defaultHandler); // 拒绝策略:默认AbortPolicy()堕胎政策** 结合业务理解线程池及参数含义:* 银行办理业务:* 银行大厅 --> 线程池* 柜台:5个柜台:2个开设柜台,3个备用柜台 --> 最大核心线程数:5,核心线程数:2,* 候客区:3个座位 --> 阻塞队列* 1个人进去,直接办理* 5个人进去(3个人在候客厅)* 8个人进去(5个窗口都开放+3个在候客厅)* 9个人进去(多1个人,被拒绝进入,抛出异常)* 1个小时后,只剩2个窗口在服务,3个备用柜台就关闭释放*/
public class Demo02 {// 1.单个线城池ExecutorService singleThread = wSingleThreadExecutor();// 2.创建一个固定的线程池大小ExecutorService fixedThreadPool = wFixedThreadPool(5);// 3.可伸缩的,遇强则强,遇弱则弱ExecutorService cachedThreadPool = wCachedThreadPool();// 本质:ThreadPoolExecutor 源码分析//public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小// int maximumPoolSize, // 最大核心线程池大小// long keepAliveTime, // 超时了没有人调用就会释放// TimeUnit unit, // 超时释放单位// BlockingQueue<Runnable> workQueue) { // 阻塞队列 链接阻塞队列LinkedBlockingQueue// 返回内容:// this(corePoolSize, // 核心线程池大小// maximumPoolSize, // 最大核心线程池大小// keepAliveTime, // 保持存活时长// unit, // 超时释放单位// workQueue, // 阻塞队列:默认最大21亿// Executors.defaultThreadFactory(), // 线程工厂,创建线程的,一般不动// defaultHandler); // 拒绝策略:默认AbortPolicy()堕胎政策////public ThreadPoolExecutor(int corePoolSize,// int maximumPoolSize,// long keepAliveTime,// TimeUnit unit,// BlockingQueue<Runnable> workQueue,// ThreadFactory threadFactory,// RejectedExecutionHandler handler)//}public static void main(String[] args) {// 创建线程池ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());ThreadPoolExecutor threadPool1 = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3));try {// 使用线程池for (int i = 1; i < 100; i++) {ute(()->{System.out.println(Thread.currentThread().getName() + ":ok");});}}catch (Exception e){e.printStackTrace();}finally {// 线程池用完,线程结束,关闭线程池threadPool.shutdown();}}
}
package com.kuang.threadpool;import urrent.*;/*** 结合业务理解线程池及参数含义:* 银行办理业务:* 银行大厅 --> 线程池* 柜台:5个柜台:2个开设柜台,3个备用柜台 --> 最大核心线程数:5,核心线程数:2,* 候客区:3个座位 --> 阻塞队列* 1个人进去,直接办理* 5个人进去(3个人在候客厅)* 8个人进去(5个窗口都开放+3个在候客厅)* 9个人进去(多1个人,被拒绝进入,抛出异常)* 1个小时后,只剩2个窗口在服务,3个备用柜台就关闭释放** 四种拒绝策略: RejectedExecutionHandler* AbortPolicy() 默认 堕胎策略 第九个进来时,抛出异常* CallerRunsPolicy() 调用者运行策略 第九个进来时,不抛出异常,调用者执行(main主线程执行)* DiscardPolicy() 丢弃策略 第九个进来时,不抛出异常,丢弃任务* DiscardOldestPolicy() 丢弃最老的策略 第九个进来时,不抛出异常,尝试和最早的竞争**/
public class Demo03 {public static void main(String[] args) {// 1.AbortPolicy() 默认 堕胎策略,抛出异常// 创建线程池//ThreadPoolExecutor threadPool1 = new ThreadPoolExecutor(// 2,// 5,// 3,// TimeUnit.SECONDS,// new LinkedBlockingQueue<>(3),// Executors.defaultThreadFactory(),// new ThreadPoolExecutor.AbortPolicy()); //拒绝策略1//try {// // 使用线程池// for (int i = 1; i <= 100; i++) {// ute(()->{// System.out.println(Thread.currentThread().getName() + ":ok");// });// }//}catch (Exception e){// e.printStackTrace();//}finally {// // 线程池用完,线程结束,关闭线程池// threadPool1.shutdown();//}/*pool-1-thread-3:okpool-1-thread-5:okpool-1-thread-4:okpool-1-thread-1:okpool-1-thread-2:okpool-1-thread-4:okpool-1-thread-5:okpool-1-thread-3:ok第九个进来时,抛出RejectedExecutionException*/// 2.CallerRunsPolicy() 调用者运行策略,不抛出异常// 创建线程池//ThreadPoolExecutor threadPool2 = new ThreadPoolExecutor(// 2,// 5,// 3,// TimeUnit.SECONDS,// new LinkedBlockingQueue<>(3),// Executors.defaultThreadFactory(),// new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略2//try {// // 使用线程池// for (int i = 1; i <= 9; i++) {// ute(()->{// System.out.println(Thread.currentThread().getName() + ":ok");// });// }//}catch (Exception e){// e.printStackTrace();//}finally {// // 线程池用完,线程结束,关闭线程池// threadPool2.shutdown();//}/*pool-1-thread-4:okpool-1-thread-3:okpool-1-thread-1:okmain:ok 第9个人,主线程来处理,谁让他进入线程池的,就回到原来地方处理,就像公司让你去银行办理,银行拒绝,让你去公司办理,公司可以代办pool-1-thread-4:okpool-1-thread-4:okpool-1-thread-2:okpool-1-thread-1:okpool-1-thread-5:ok*/// 3.DiscardPolicy() 丢弃策略,不抛出异常// 创建线程池//ThreadPoolExecutor threadPool3 = new ThreadPoolExecutor(// 2,// 5,// 3,// TimeUnit.SECONDS,// new LinkedBlockingQueue<>(3),// Executors.defaultThreadFactory(),// new ThreadPoolExecutor.DiscardPolicy()); //拒绝策略3//try {// // 使用线程池// for (int i = 1; i <= 9; i++) {// ute(()->{// System.out.println(Thread.currentThread().getName() + ":ok");// });// }//}catch (Exception e){// e.printStackTrace();//}finally {// // 线程池用完,线程结束,关闭线程池// threadPool3.shutdown();//}/*pool-1-thread-1:okpool-1-thread-5:okpool-1-thread-1:okpool-1-thread-1:okpool-1-thread-4:okpool-1-thread-3:okpool-1-thread-2:okpool-1-thread-5:ok 不要第9个人*/// 4.DiscardOldestPolicy() 抛弃保单策略,不抛出异常// 创建线程池ThreadPoolExecutor threadPool4 = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy()); //拒绝策略4try {// 使用线程池for (int i = 1; i <= 9; i++) {ute(()->{System.out.println(Thread.currentThread().getName() + ":ok");});}}catch (Exception e){e.printStackTrace();}finally {// 线程池用完,线程结束,关闭线程池threadPool4.shutdown();}/*pool-1-thread-1:okpool-1-thread-4:okpool-1-thread-1:okpool-1-thread-3:okpool-1-thread-2:okpool-1-thread-1:okpool-1-thread-4:okpool-1-thread-5:ok 第9个进来,尝试和最早 的竞争,不抛出异常,这里没有竞争到,就抛弃了*/}
}
1.CPU密集型,几核,就是几,可以保证CPU效率最高
获取CPU数量:注意:不要写死,代码获取CPU核数,because运维CPU设备比你牛时,你写死了,会被打死!
①任务管理器查看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckEi7fG8-1689140798570)(C:UsersadminAppDataRoamingTyporatypora-user-imagesimage-20230703170543989.png)]
②设备管理器查看
③代码查看
Runtime().availableProcessors();
2.IO密集型(调优)
假设有15个大型任务IO十分占用资源,这时设计时,最好设置为 > 2*15 ~ 30核
新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
java.util.function
只有一个方法的接口,如:
@FunctionalInterface
public interface Runnable {public abstract void run();
}//简化编程模型
//foreach() 消费者类型的函数式接口
只有一行时,lambda简化:
只有输入,没有返回,饕餮!
只有返回,乜有输入,不用吃东西,只产出是啥!爷爷的爱
package com.kuang.FunctionalInterface;import java.util.function.Function;/*** Function 函数型接口:* 一个输入,一个输出* T:传入参数 R:返回类型* @FunctionalInterface* public interface Function<T, R> {* R apply(T t);* }*/
public class Test1 {public static void main(String[] args) {Function function = new Function<String, String>() {@Overridepublic String apply(String s) {return s;}};System.out.println(function.apply("输出s"));// lambda简化Function function1 = (str)->{return str;};System.out.println(function.apply("简化s"));}}
package com.kuang.FunctionalInterface;import java.util.function.Function;
import java.util.function.Predicate;/*** Predicate 断定型接口* 一个输入,boolean输出**/
public class Test2 {public static void main(String[] args) {Predicate predicate = new Predicate<String>() {@Overridepublic boolean test(String s) {return s.isEmpty();}};System.out.st("输出s"));System.out.st(""));// lambda简化Predicate<String> predicate1 = (str)->{return str.isEmpty();};Predicate<String> predicate11 = str->{return str.isEmpty();};Predicate<String> predicate2 = String::isEmpty;System.out.st("简化"));System.out.st(""));System.out.st("简化"));System.out.st(""));}
}
package com.kuang.FunctionalInterface;import java.util.function.Consumer;
import java.util.function.Predicate;/*** Cunsumer 消费型接口* 只有输入,没有返回,饕餮!*/
public class Test3 {public static void main(String[] args) {Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}};consumer.accept("测试");// lambda简化Consumer<String> consumer1 = (str) -> {System.out.println(str);};Consumer<String> consumer11 = System.out::println;consumer1.accept("简化测试");consumer11.accept("简化测试");}
}
package com.kuang.FunctionalInterface;import java.util.function.Supplier;/*** Supplier 供给型接口* 只有返回,乜有输入,不用吃东西,只产出是啥!爷爷的爱*/
public class Test4 {public static void main(String[] args) {Supplier<Integer> supplier = new Supplier<Integer>(){@Overridepublic Integer get() {return 1024;}};System.out.());// lambda简化Supplier<String> supplier1 = ()->{return "test";};System.out.());}
}
在JDK1.7,作用:并行情况下,提高效率,大数据量情况下使用!
特点:工作窃取
B线程执行的快,就会窃取A线程的任务,
里面维护的是双端队列
操作:
package com.kuang.forkjoin;import urrent.RecursiveTask;/*** ForkJoin* 分支合并,在JDK1.7* 作用:并行情况下,提高效率,大数据量情况下使用!一个线程并发为多个线程使用* 特点:工作窃取 B线程执行的快,就会窃取A线程的任务,里面维护的是双端队列(两边都可以操作)** 实例:1+2+...10亿求和* 1.一般操作 fori,sum+=i;* 2.使用ForkJoin(更快)* 3.使用Stream并行流(更快)** 如何使用ForkJoin?* 1.通过 ForkJoinPool()来执行* 2.计算任务 ute(ForkJoinTask task)* submit() 异步 提交任务,有结果* execute() 同步 执行任务,没有结果* 3.计算类继承 extends ForkJoinTask***/
public class ForkJoinDemo extends RecursiveTask<Long> { //继承JUC递归任务private Long start; //1private Long end; //1990900000private Long num = 1_0000_0000L; //临界值:小于10w个数相加时,一般方法,大于10w个数相加时,使用ForkJoin/Stream流public ForkJoinDemo(Long start, Long end) {this.start = d = end;}// 计算方法@Overrideprotected Long compute() {// 定义一个临界值,如果 <临界值,就用普通求和方式,如果 >临界值,就用ForkJoinif ((end - start) < num){Long sum = 0L;for (Long i = start; i < end; i++) {sum += i;}return sum;}else {Long middle = (start + end)/2;System.out.println("forkjoin middle="+middle);ForkJoinDemo task1 = new ForkJoinDemo(start,middle);task1.fork(); //拆分任务,把任务压入线程队列ForkJoinDemo task2 = new ForkJoinDemo(middle,end);task2.fork(); //拆分任务,把任务压入线程队列return task1.join()+task2.join(); //分解任务,最终每个分支都走了if里面}}}
CompletableFuture
package com.kuang.feture;import urrent.CompletableFuture;
import urrent.ExecutionException;
import urrent.TimeUnit;/*** 异步回调* Feture:未来,设计初中:对将来的某个事件进行建模* 异步执行* 成功回调* 失败回调* 理解:* AJAX,客户端发异步请求到服务器* Feture,多个线程同时执行时,异步执行线程请求,可以获取请求结果(or Void不获取请求结果)* 使用:* 没有返回值的 runAsync 异步回调,可以通过get()获取* 有返回值的 supplyAsync 异步回调,返回类型为泛型的类型*///没有返回值的 runAsync 异步回调
public class Test1 {public static void main(String[] args) throws ExecutionException, InterruptedException {// 发起一个异步请求CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "runAsync=>Void");});// 主线程System.out.println("main:1111");// 获取阻塞执行结果:异步请求默认没有返回,可以通过get()来获取结果();/*执行结果:main:1111ForkJoinPoolmonPool-worker-1runAsync=>Void*/}
}
package com.kuang.feture;import urrent.CompletableFuture;
import urrent.ExecutionException;//有返回值的 supplyAsync 异步回调
// ajax,成功和失败的回调
// 返回的是错误信息
public class Test2 {public static void main(String[] args) throws ExecutionException, InterruptedException {System.out.println("========start========");// 异步任务CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> { //Supplier 供给型接口,前面四大函数式接口讲过int i = 10/0;System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");return 1024;});// 完成异步任务后,打印输出成功回到、或者失败回调System.out.println(integerCompletableFuture.whenComplete((t, u) -> { //BiConsumer<T, U> 增强版消费型接口// successSystem.out.println("t=>" + t); // t:正常返回结果System.out.println("u=>" + u); // u:错误信息:没有就输出null}).exceptionally((e) -> {// errorSystem.out.Message()); // 输出异常信息return 233; // 可以获取到错误的返回结果}).get());System.out.println("========end========");/*异步执行内容中没有错误时:========start========ForkJoinPoolmonPool-worker-1supplyAsync=>Integert=>1024u=>null1024========end========异步执行内容中有错误时:========start========t=>nullu=>urrent.CompletionException: java.lang.ArithmeticException: / by zerojava.lang.ArithmeticException: / by zero233========end========*/}
}
问题:线程A正在用flag = true,进行工作,还没有写入主内存时,线程B修改flag = false,并写入了内容中,这时候就会有线程A工作内容中的flag与主存中值不一致。
参考文章:Java内存模型(JMM)总结 - Doing的文章 - 知乎
JMM模型下的线程间通信:
线程间通信必须要经过主内存。
如下,如果线程A与线程B之间要通信的话,必须要经历下面2个步骤:
1)线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2)线程B到主内存中去读取线程A之前已更新过的共享变量。
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
参考文章:C语言丨深入理解volatile关键字 - 沐歌爱编程的文章 - 知乎
问题:线程A正在用flag = true,进行工作,还没有写入主内存时,线程B修改flag = false,并写入了内容中,这时候就会有线程A工作内存中的flag与主存中值不一致。
处理思路:
解决办法:
对共享变量用 volatile进行修饰,说明这个变量是“易变的”,提醒编译器编译后的程序每次需要对这个变量操作的时候,不要做编译优化(不要读取工作内存的值),直接从变量内存地址中读取数据,从而解决A线程不可见问题。
测试:
package com.kuang.volatiledemo;import urrent.TimeUnit;/*** volatile :* 1.保证可见性* 2.不保证原子性* 3.禁止指令重排** 验证1:保证可见性* 参考文章:* C语言丨深入理解volatile关键字 - 沐歌爱编程的文章 - 知乎 * 多线程操作共享变量时,可见性问题:* 线程A正在用flag = true,进行工作,还没有写入主内存时,线程B修改flag = false,并写入了内容中,* 这时候就会有线程A工作内存中的flag与主存中值不一致。* 处理思路:* 需要线程A知道主内存中的值发生了变化* 解决办法:* 对共享变量用 volatile进行修饰,说明这个变量是“易变的”,* 提醒编译器,编译后的程序每次需要对这个变量操作的时候,不要做编译优化(不要读取工作内存的值),* 直接从变量内存地址中读取数据,从而解决对主内存共享变量变化时对A线程不可见现象。*/
public class Demo1 {// 共享变量// 当没有volatile修饰时,打印出1,还继续陷入死循环,程序不中断// 当有volatile修饰时,打印出1,立即中断//private static int num = 0;private volatile static int num = 0;public static void main(String[] args) {// 线程1 当num=0时,死循环new Thread(()->{while (num == 0){}},"线程1").start();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}// main线程 修改num=1num = 1;System.out.println(num);}
}
num++; 不是一个原子操作,所以多线程操作共享变量num时,会出现问题。
使用原子包装类解决这个问题
package com.kuang.volatiledemo;import urrent.ForkJoinPool;/*** volatile :* 1.保证可见性* 2.不保证原子性* 3.禁止指令重排** 验证2:不保证原子性* 原子性:不可分割,线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败。* sychronized、Lock锁可以保证原子性* 问题:* 如果不加sychronized、Lock锁,怎么可以保证原子性?* 使用原子类解决 AtomicInteger num Demo3**/
public class Demo2 {//private static int num = 0;private static volatile int num = 0;// volatile 不保证原子性public static void add(){num ++; //不是一个原子性操作// 底层多步骤操作,多个线程进来时,就会有是否原子性操作问题// 打开字节码文件,cmd进入,javap -c 分析// 1. getstatic 获取这个值// 2. iconst_l 常量i// 3. i+1// 4. putstatic 写回这个值}// synchronized 保证原子性public static synchronized void add1(){num ++;}public static void main(String[] args) {// 创建20个线程,每个线程执行1000次,预期得到结果2wfor (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {add(); // main 19383//add1(); //main 20000}}).start();}// 主线程// 判定执行:如果活跃的线程数>2,主线程和垃圾回收线程礼让,让其余线程先执行// 默认执行线程:main gcwhile (Thread.activeCount() > 2){Thread.yield();}System.out.println(Thread.currentThread().getName() + " " +num);}
}
package com.kuang.volatiledemo;import urrent.atomic.AtomicInteger;/*** 问题:* 如果不加sychronized、Lock锁,怎么可以保证原子性?* 使用原子包装类解决 AtomicInteger num Demo3*/
public class Demo3 {// volatile 保证可见性,不保证原子性// AtomicInteger 原子包装类声明变量,保证原子性private static volatile AtomicInteger num = new AtomicInteger();public static void add(){num.incrementAndGet();}public static void main(String[] args) {// 20个线程,每个线程加到1000,预计num=2wfor (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {add();}}).start();}// 打印出20个线程执行完的numwhile (Thread.activeCount() > 2){ // main,gcThread.yield();}System.out.println("num=" + num);}
}
内存屏障
内存屏障在单例模式里面使用最多!!!!!!!!
实现方式:饿汉式、懒汉式、静态内部类实现
DCL饿汉式:一上来就加载对象,分配内存空间。
单例模式:构造器私有
懒汉式:用的时候再分配。
这样写有问题,多线程并发拿到实例,拿到多个实例;
所以加入双重检测锁模式,这样拿到的是一个单例
这样写还可能有个问题,指令重排
双重检测锁+volatile禁止指令重排操作:保证避免指令重排,来确保原子性操作
静态内部类:
饿汉、懒汉、静态内部类都是不安全的!因为有反射~
反射会破坏单例!!!
怎么办呢?
解决办法:
1.构造器锁住,构造的时候判断下,是否为空,如果存在了,就抛出异常
上面这种情况解决了,但是如果通过构造器拿到两个实例,是否是相同的? 不同
说明上面的解决办法,没有处理这种情况
解决办法:
通过一个标志位,默认false,通过反射newInstance后,设置为true,这时候再次通过反射 newInstance时候,抛出异常
这样是不是没有问题了?
不,可以破坏
拿到标志位字段,破坏私有权限,把标志位的值又改为 false, 单例又就被破坏了
那这个如何解决?
源码 wInstance() 进去
如果类是一个枚举类型,就不能通过反射创建对象!
枚举是什么?
为什么枚举能避免单例被破坏?
反射不能破坏枚举,我们来试一下
报错:没有空参构造方法,不是预期的报错,枚举类不能通过反射创建对象
反编译class文件,查看也不是想要的错误
使用专业工具 复制到 EnumSingle.class 同级目录下,生成java文件,打开查看源码
写入参数,抛出想要的异常
饿汉实例:
package com.kuang.single;/*** 单例模式:构造器私有 ,只能创建一个任务管理器* 实现方式:* 1.饿汉式 一会吃拿过来就加载对象,分配内存空间,但是如果里面声明了固定空间变量,就会造成资源浪费* 2.懒汉式 用的时候再分配* 3.静态内部类 static修饰获取实例方法* 存在问题:* 饿汉、懒汉、静态内部类都是不安全的!因为有反射~* 反射会破坏单例!!!* 解决办法:* 枚举类* 1.构造器锁住,构造的时候判断下,是否为空,如果存在了,就抛出异常* 但是如果通过构造器拿到两个实例,是否是相同的? 不同* 2.通过一个标志位,默认false,通过反射newInstance后,设置为true,这时候再次通过反射 newInstance时候,抛出异常* 这样是不是没有问题了?* 不,可以破坏,拿到标志位字段,破坏私有权限,把标志位的值又改为 false, 单例又就被破坏了* 那这个如何解决?* 源码 wInstance() 进去* 如果类是一个枚举类型,就不能通过反射创建对象!* 为什么枚举能避免单例被破坏?* 3.枚举能避免单例被反射破坏,枚举会抛出异常,不能通过反射创建枚举对象* 结论:* 实现单例最保险的方式:枚举类*/// 饿汉式单例
public class HungryMan {// 可能会资源浪费private byte[] date1 = new byte[1024*1024];private byte[] date2 = new byte[1024*1024];private byte[] date3 = new byte[1024*1024];private byte[] date4 = new byte[1024*1024];private byte[] date5 = new byte[1024*1024];private final static HungryMan hungryMan = new HungryMan();public HungryMan() {}public static HungryMan getInstance(){return hungryMan;}
}
懒汉实例:
package com.kuang.single;// 懒汉模式
public class LazyMan {private static LazyMan LazyMan;public LazyMan() {System.out.println(Thread.currentThread().getName() + "ok");}public static LazyMan lazyMan;// 双重检测锁+volatile禁止指令重排操作public static volatile LazyMan lazyMan1;public static LazyMan getInstance1(){if (lazyMan == null){lazyMan = new LazyMan();}return lazyMan;}// 双重检测锁模式 懒汉单例模式 DCL懒汉式// 双层锁模式,实现效果:多线程并发情况下,只有一个线程获取到实例public static LazyMan getInstance2(){if (lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null){lazyMan = new LazyMan();}}}return lazyMan;}// 双重检测锁+volatile禁止指令重排操作:保证避免指令重排,来确保原子性操作public static LazyMan getInstance3(){if (lazyMan1 == null){synchronized (LazyMan.class){if (lazyMan1 == null){lazyMan1 = new LazyMan();}}}return lazyMan1;}// 多线程并发测试public static void main(String[] args) {// 多个线程获取实例,判断获取到的实例是否同一个for (int i = 0; i < 1000; i++) {new Thread(()->{Instance3();}).start();}}/*Thread-0ok 打印输出了多个线程,构造了多个实例,所以加双层检测模式Thread-4okThread-3okThread-6okThread-1ok*//*Thread-0ok 双层检测模式,实现效果:多线程并发情况下,只有一个线程获取到实例但是这样可能会有指令重排问题,因为 new LazyMan() 不是一个原子操作里面操作多个步骤:1.分配内存空间2.执行构造器房费,初始化对象3.把这个对象指向这个空间双重检测锁+volatile禁止指令重排操作:保证避免指令重排,来确保原子性操作*//*Thread-0ok 双重检测锁+volatile禁止指令重排操作 最终实现了单例模式*/}
静态内部类实例:
package com.kuang.single;// 静态内部类
public class Holder {private Holder(){}public static Holder getInstance(){return InnerClass.HOLDER;}public static class InnerClass{private static final Holder HOLDER = new Holder();}
}
懒汉模式实现单例,反射不安全测试:
package com.kuang.single;import flect.Constructor;
import flect.Field;
import flect.InvocationTargetException;// 懒汉模式实现单例,反射不安全测试
public class LazyManReflectTest {//public LazyManReflectTest() {// System.out.println(Thread.currentThread().getName() + "ok");//}// 构造器加锁:这样多线程通过类获取实例时,会先判断下,如果实例已存在,就抛出异常,从而实现单例//public LazyManReflectTest() {// synchronized (LazyManReflectTest.class){// if (lazyMan != null){// throw new RuntimeException("不要试图使用反射破坏单例");// }// }//}// 标志位private static boolean song = false;// 构造器加锁+标志位 避免通过构造器创建2个实例时,获取到不同实例,实现单例模式public LazyManReflectTest() {synchronized (LazyManReflectTest.class){if (!song){song = true;}else {throw new RuntimeException("不要试图使用反射破坏单例");}}}// 双重检测锁模式:禁止指令重排,确保原子性操作private volatile static LazyManReflectTest lazyMan;public static LazyManReflectTest getInstance(){if (lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null){lazyMan = new LazyManReflectTest();}}}return lazyMan;}// 反射破坏单例测试public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {// 通过反射获取1个实例、通过构造器获取1个实例,判断是否单例//LazyManReflectTest instance1 = Instance();//Constructor<LazyManReflectTest> declaredConstructor = DeclaredConstructor(null);//declaredConstructor.setAccessible(true);//LazyManReflectTest instance2 = wInstance();//LazyManReflectTest instance3 = wInstance(); // 通过构造器获取2个实例,判断是否单例// 通过反射获取到的实例和构造器获取到实例不是一个,获取到2个实例,破坏了单例模式// 解决办法:1.构造器锁住,构造的时候判断下,是否为空,如果存在了,就抛出异常 reflect.InvocationTargetException 不要试图使用反射破坏单例// 通过构造器获取2个实例,单例被破坏// 解决办法:通过一个标志位,默认false,通过反射newInstance后,设置为true,这时候再次通过反射 newInstance时候,抛出异常//System.out.println(instance1); //com.kuang.single.LazyManReflectTest@7f31245a//System.out.println(instance2); //com.kuang.single.LazyManReflectTest@6d6f6e28//System.out.println(instance3); //com.kuang.single.LazyManReflectTest@135fbaa4 单例被破坏// 拿到标志位字段,破坏私有权限,把标志位的值又改为 false, 单例又就被破坏了Field song = DeclaredField("song");song.setAccessible(true); //设置属性为可访问Constructor<LazyManReflectTest> constructor = DeclaredConstructor();constructor.setAccessible(true); //设置构造器为可访问LazyManReflectTest newInstance1 = wInstance();song.set(newInstance1,false); //将标志位设为未生成实例时的falseLazyManReflectTest newInstance2 = wInstance();System.out.println(newInstance1); //com.kuang.single.LazyManReflectTest@6d6f6e28System.out.println(newInstance2); //com.kuang.single.LazyManReflectTest@135fbaa4 单例被破坏// 那这个如何解决? 源码 wInstance() 进去,如果类是一个枚举类型,就不能通过反射创建对象!}
}
枚举类实例:
package com.kuang.single;import flect.Constructor;
import flect.InvocationTargetException;// 枚举避免单例被反射破坏 enum本身也是一个Class类
public enum EnumSingle {// 实例INSTANCE;// 获取实例方法public static EnumSingle getInstance(){return INSTANCE;}// 枚举私有构造器private EnumSingle(){}
}class Test{public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//EnumSingle instance1 = EnumSingle.INSTANCE;//EnumSingle instance2 = EnumSingle.INSTANCE;//EnumSingle instance3 = Instance();//System.out.println(instance1);//INSTANCE//System.out.println(instance2);//INSTANCE//System.out.println(instance3);//INSTANCE// 测试反射是否能破坏枚举单例,预期:如果通过反射创建多个实例,会抛出异常EnumSingle instance1 = EnumSingle.INSTANCE;//Constructor<EnumSingle> declaredConstructor = DeclaredConstructor(); //报错:EnumSingle.<init>() NoSuchMethodException,不能抛出想要的异常Constructor<EnumSingle> declaredConstructor = DeclaredConstructor(String.class, int.class); //IllegalArgumentException: Cannot reflectively create enum objectsdeclaredConstructor.setAccessible(true); // 设置为可访问EnumSingle instance2 = wInstance();System.out.println(instance1);System.out.println(instance2); //不能反射地创建枚举对象}
}
cas:
原子类自增操作:点进去
unsafe类
模拟ABA出现场景:
如何解决?
原子引用,带个版本号
这里有个坑:Integer
cas测试实例:
package com.kuang.cas;import urrent.atomic.AtomicInteger;/*** CAS是什么?* compareAndSet(int expect, int update):比较并交换* 入参:期望,更新;* 如果我期望的结果达到预期,就去更新,如果没有达到预期,就不更新* 因为 num++ 操作在底层不是原子操作,所以多线程并发会有问题,所以使用原子包装类* 但是原子包装类CAS有个问题(ABA问题)* 就是主内存:num=0,线程A,修改num=1,然后又修改回去0,这时候操作对线程B来说,线程B拿到的是0,B是不知道A改过* 如何解决CAS的ABA问题?* ABA问题:有线程修改了主内存,又修改回去,其他线程感知不到* 使用原子引用,带版本号* CAS缺点?* CAS比较当前工作内存中的值和主内存中的值,如果这个值和期望值相同,就执行更新操作,否则就一直循环!* 1.循环会耗时* 2.一次性只能保证一个共享变量的原子性* 3.ABA问题**/// CAS简单实例:比较并交换
public class Test1 {public static void main(String[] args) {// 创建一个原子包装类,设置初识值2020AtomicInteger atomicInteger = new AtomicInteger(2020);// public final boolean compareAndSet(int expect, int update)// atomicInteger进行判断,如果期望值达到2020,就更新为2021System.out.println(atomicIntegerpareAndSet(2020, 2021));System.out.()); // 获取原子包装类的值System.out.println(atomicIntegerpareAndSet(2020, 2021));System.out.());/*true2021false2021*/}
}package com.kuang.cas;import urrent.TimeUnit;
import urrent.atomic.AtomicInteger;// 模拟CAS出现的ABA问题
public class Test2 {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger(1);// 线程A:捣乱的线程,修改下,又修改回去new Thread(()->{// 如果原子Integer类值和预期值一样,就更新System.out.println(atomicIntegerpareAndSet(1, 2));System.out.());System.out.println(atomicIntegerpareAndSet(2, 1));System.out.());try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}).start();// 线程Bnew Thread(()->{System.out.println(atomicIntegerpareAndSet(1, 6));System.out.());}).start();/*true2true6停顿5s后true6*/}
}package com.kuang.cas;import urrent.TimeUnit;
import urrent.atomic.AtomicStampedReference;// 如何解决CAS的ABA问题:原子戳引用,带个版本号
// 注意包装类的坑:
// 包装类数据范围导致获取到的值不是预期内存中的值
// Integer使用了对象缓存机制,默认范围-128-127,超过区间外数据会在堆上,不会复用已有对象
public class Test3 {// 初始化原子戳引用 初识引用:2022, 初识版本:1static AtomicStampedReference<Integer> integerAtomicStampedReference = new AtomicStampedReference<>(1, 1);public static void main(String[] args) {// 模拟两个线程并发执行,获取版本号,只要有线程对值进行修改,就修改版本号,避免ABA问题new Thread(()->{int stamp = Stamp(); // 先获取版本号,睡5秒System.out.println("a1=>" + stamp);try {TimeUnit.SECONDS.sleep(2); // 等2s} catch (InterruptedException e) {e.printStackTrace();}// 线程a要把1改成6System.out.println(Thread.currentThread().getName()+"-1=>6->"+integerAtomicStampedReferencepareAndSet(1,Stamp(),Stamp() + 1));//获取是否达到期望修改System.out.println("a2=>" + Stamp());// 再把6改成1System.out.println(Thread.currentThread().getName()+"-6=>1->"+integerAtomicStampedReferencepareAndSet(6,Stamp(),Stamp() + 1));System.out.println("a3=>" + Stamp());},"a").start();new Thread(()->{int stamp = Stamp(); // 先获取版本号,睡5秒System.out.println("b1=>" + stamp);try {TimeUnit.SECONDS.sleep(5);// 等5s} catch (InterruptedException e) {e.printStackTrace();}// 好人线程要把1改成6,改不了,因为预期版本可能不是1,是已经被A改过的2System.out.println(Thread.currentThread().getName()+"-1=>6-"+integerAtomicStampedReferencepareAndSet(1,6,stamp,stamp + 1));System.out.println("b2=>" + Stamp());},"b").start();/*a1=>1b1=>1a-1=>6->truea2=>2a-6=>1->truea3=>3b-1=>6-false b线程等5s,所以a会先修改完,这时候,版本为3,b修改时,与预期版本1不一致,就不修改b2=>3*/}
}
测试传统版Synchronized锁:
测试结果:A拿到外面的锁,执行sms后,不会释放锁,再拿到call里面的锁,知道执行完后一起释放锁,B再拿到锁
测试可重复锁(lock版锁):lock锁必须配对
不断尝试获取锁,直到成功为止
实现自旋锁:实现方式CAS
测试自旋锁:线程1、线程2,
线程1加锁后,线程2想加锁,得等T1解锁后,T2才能加锁,T2等T1解锁的时候,T2在自旋等待T1释放锁
死锁测试:两个线程互相争抢资源
死锁时,程序卡住,怎么排查哪里死锁?
java bin 目录下,
使用 jps -l定位进程号 查看当前或者的java进程
使用 jstack 进程号,查看信息,找到死锁
找到一个死锁:
synchronized可重入锁测试:
package com.kuang.lock;import urrent.Callable;/*** 1.公平锁、非公平锁:* 公平锁:非常公平,不能插队,必须先来后到!* 非公平锁:非常不公平,可以插队(默认都是非公平锁) public ReentrantLock(){sync = new NonfairSync();}* 2.可重入锁* 拿到了外面的锁之后,就可以拿到里面的锁* 测试传统版Synchronized锁* 测试结果:A拿到外面的锁,执行sms后,不会释放锁,再拿到call里面的锁,知道执行完后一起释放锁,B再拿到锁* 测试可重复锁(lock版锁):lock锁必须配对* 3.自旋锁* 不断尝试获取锁,直到成功为止* 实现自旋锁:实现方式CAS* 测试自旋锁:线程1、线程2,* 线程1加锁后,线程2想加锁,得等T1解锁后,T2才能加锁,T2等T1解锁的时候,T2在自旋等待T1释放锁* 4.死锁* 死锁测试:两个线程互相争抢资源* 死锁时,程序卡住,怎么排查哪里死锁?java bin 目录下,* 1)使用 jps -l定位进程号 查看当前或者的java进程* 2)使用 jstack 进程号,查看死锁信息,找到死锁*/// 测试可重入锁(传统版Synchronized版)
// sms一层锁,call一层锁,sms调用call,这时候,两个线程A、B同时sms,
// 假设两个锁不一样,A线程先获取sms的锁,再获取里面call的锁,B线程想获取sms的锁,需要等A释放sms的锁,那A什么时候释放?
// 是在获取call的锁之前,还是释放call锁之后?
// 如果A在获取call之前释放sms的锁,B就可以获取,这时候打印顺序应该是 A:sms, B:sms, A:call, B:call
// 如果A在释放call锁之后释放sms的锁,B就可以获取,这时候打印顺序应该是 A:sms, A:call,B:sms, B:call (√)
// 结论:sms锁包裹call锁,拿到sms锁,就拿到call锁(可重入锁)
public class Test1 {public static void main(String[] args) {Phone phone = new Phone();new Thread(phone::sms,"A").start();new Thread(phone::sms,"B").start();/*AsmsAcallBsmsBcall*/}
}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可重入锁测试:
package com.kuang.lock;import urrent.locks.ReentrantLock;//测试可重复锁(lock版锁):lock锁必须配对,否则会死在里面
public class Test2 {public static void main(String[] args) {Phone1 phone = new Phone1();new Thread(phone::sms,"A").start();new Thread(phone::sms,"B").start();/*Asms 结果:A获取sms锁,再获取call锁,然后释放call锁,再释放sms锁,然后B开始获取AcallBsmsBcall*/}
}class Phone1{ReentrantLock lock = new ReentrantLock();public void sms(){lock.lock();lock.lock();try {System.out.println(Thread.currentThread().getName()+"sms");call();//call lock锁}catch (Exception e){e.printStackTrace();}finally {lock.unlock();lock.unlock();}}public void call(){lock.lock();try {System.out.println(Thread.currentThread().getName()+"call");}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}
自旋锁:
package com.kuang.lock;import jdk.internal.TryCatchBlockNode;
import AreaOp;import urrent.TimeUnit;
import urrent.atomic.AtomicReference;// 测试自旋锁
// 不断尝试获取锁加锁,直到成功为止
// 实现自旋锁:实现方式CAS 比较并交换,达到预期时更新,否则死循环
// 测试自旋锁:线程1、线程2,
// 线程1加锁后,线程2想加锁,得等T1解锁后,T2才能加锁,T2等T1解锁的时候,T2在自旋等待T1释放锁
public class MySpinLock {// 加锁对象是一个个线程,所以泛型里面是<Thread>AtomicReference<Thread> atomicReference = new AtomicReference(); // 原子引用:避免多线程并发指令重排// 本质:控制一次原子引用里面有一个线程// 加锁public void myLock(){// 获取当前线程,如果原子引用里面是Null,就把当前线程放入,如果有线程,进入死循环,一直等待,直到其他线程释放原子引用Thread currentThread = Thread.currentThread();System.out.println(Thread.currentThread().getName() + "==> mylock");// 自旋锁while (!atomicReferencepareAndSet(null,currentThread)){//System.out.println(Thread.currentThread().getName() + "==> 在自旋等待锁被释放");// 返回false,代表没有达到预期更新,就说明原子引用不为null,陷入死循环,相当于加锁}}// 释放锁public void myUnLock(){// 如果原子引用里面线程是当前线程,就释放原子引用为null,结束死循环,相当于解锁Thread currentThread = Thread.currentThread();System.out.println(Thread.currentThread().getName() + "==> myUnlock");atomicReferencepareAndSet(currentThread,null);}
}// 测试自己写的自旋锁,测试并发
class TestMySpinLock{public static void main(String[] args) {MySpinLock mySpinLock = new MySpinLock();new Thread(()->{Lock();try {// 等待,业务,通知TimeUnit.SECONDS.sleep(3);} catch (Exception e){e.printStackTrace();}finally {UnLock();}},"A").start();new Thread(()->{Lock();try {// 等待,业务,通知TimeUnit.SECONDS.sleep(1);} catch (Exception e){e.printStackTrace();}finally {UnLock();}},"B").start();}/*A==> mylockB==> mylock A先获取锁,B自旋等待,A释放锁,B获取锁,再释放锁A==> myUnlockB==> myUnlock*/
}
模拟死锁:
package com.kuang.lock;import urrent.TimeUnit;// 模拟死锁
// 线程1先锁资源A,再锁资源B,线程2先锁资源B,再锁资源A
// 模拟:线程1先获取资源A并锁住、线程2先获取资源B并锁住,
// 这时候,
// 线程1等线程2释放资源B才能继续执行释放资源A,供线程2获取
// 线程2等线程1释放资源A才能继续执行释放资源B,供线程1获取
// 两个线程都在等,但是都获取不到,就发生了死锁
/*使用 jps -l定位进程号 查看当前或者的java进程使用 jstack 进程号,查看信息,找到死锁"T2":at com.kuang.lock.MyThread.run(deadLock.java:54)- waiting to lock <0x00000000d6c06348> (a java.lang.String)- locked <0x00000000d6c06378> (a java.lang.String)at java.lang.Thread.run(Thread.java:748)"T1":at com.kuang.lock.MyThread.run(deadLock.java:54)- waiting to lock <0x00000000d6c06378> (a java.lang.String)- locked <0x00000000d6c06348> (a java.lang.String)at java.lang.Thread.run(Thread.java:748)*/
public class deadLock {public static void main(String[] args) {String A = "A";String B = "B";new Thread(new MyThread(A,B),"T1").start();new Thread(new MyThread(B,A),"T2").start();}/*T1lock:A=>to getB 线程都在死等对方释放锁T2lock:B=>to getA*/}class MyThread implements Runnable{private final String lockA;private final String lockB;public MyThread(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA){System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>to get"+ lockB);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB){System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"=> to get"+lockA);}}}}
本文发布于:2024-01-29 02:20:07,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170646601212028.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |