JUC并发编程
什么是JUC

java.util 工具包
业务:普通的线程代码:Thread
Runnable:没有返回值,效率相比于Callable相对较低
面试:单例模式,排序算法,生产者和消费者,死锁
线程和进程
进程和线程(使用一句话描述)
进程:一个程序 静态的指令集合 QQ.exe Music.exe
一个进程可以包含多个线程(至少包含一个)
java默认几个线程? 2(main GC(垃圾回收))
线程:程序被加载到内存中动态执行的实例(例如开了多个窗口)
java无法真正开启线程
它是采用调用本地方法,底层的c++,Java无法直接操作硬件
并发和并行
并发编程:并发和并行
并发(多线程操作同一个资源)
- CPU 单核,模拟出来多个线程,快速交替
并行:多个人同时行走
- CPU 多核,多个线程可以同时执行 使用线程池
package javaSEStudy.JUC.JUCDemo;
public class Basics {
public static void main(String[] args) {
//获取CPU核数
//CPU密集型 IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
线程有几个状态
public enum State{
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待(死等)
WAITING,
// 超时等待(等待一定时间)
TIMED_WAITING,
// 终止
TERMINATED;
}
wait 和 sleep 区别
1.来自不同的类
wait ---> Object类
sleep ---> Thread类
2.释放锁
wait 会释放锁
sleep 睡觉了,抱着锁睡觉,不会释放
3.使用范围不同
wait 必须在同步代码块(synchronized)
sleep 可以在任何地方睡
4.是否需要捕获异常
wait 不需要捕获异常
sleep 必须要捕获异常(可能会发生超时等待异常)
Lock锁(重点)
传统的 Synchronized Lock
package javaSEStudy.JUC.JUCDemo;
// 基本卖票例子
/**
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性 2.方法
*/
public class Basics2 {
public static void main(String[] args) {
// 并发: 多线程操作同一个资源类
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口
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();
}
}
// 资源类
class Ticket{
// 属性 ,方法
private int number = 50;
// 卖票的方式
//synchronized 本质:排队,锁
//锁对象,锁class
public synchronized void sale(){
if (number > 0){
System.out.println(Thread.currentThread().getName() + "卖出了:"+(number--)+"票,剩余:"+number);
}
}
}
Lock接口


ReentrantLock()

公平锁:十分公平,可以先来后到
非公平锁:十分不公平,允许插队(默认)
package javaSEStudy.JUC.JUCDemo.basics;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 基本卖票例子 使用Lock
public class Basics3 {
public static void main(String[] args) {
// 并发: 多线程操作同一个资源类
Ticket1 ticket1 = new Ticket1();
new Thread( ()-> {for (int i = 0; i < 60; i++) ticket1.sale();},"A").start();
new Thread( ()-> {for (int i = 0; i < 60; i++) ticket1.sale();},"B").start();
new Thread( ()-> {for (int i = 0; i < 60; i++) ticket1.sale();},"C").start();
}
}
// Lock
class Ticket1{
// 属性 ,方法
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区别
Synchronized :
- java内置的关键字
- 无法判断获取锁的状态
- 会自动释放锁
- 线程1获得锁,但是发生阻塞,线程2会死等
- 可重入锁,不可以中断,非公平的
- 适合锁少量的代码同步问题
Lock :
- 是一个类
- 可以判断是否获取到了锁
- 必须要手动释放锁,如果未释放,会死锁
- 线程1获得锁,但是发生阻塞,线程2就不会死等
- 可重入锁,可以判断锁,是否公平自己设置
- 适合锁大量的同步代码
生产者和消费者问题
Synchronized版
package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;
/**
* 线程之间的通信问题 : 等待唤醒,通知唤醒
* 线程交替执行: A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class Synchronized_ {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"B").start();
}
}
/**
* 多线程处理逻辑
* 判断等待 , 业务 , 通知
*/
//资源类
class Data{
private int number = 0;
//增加资源方法
public synchronized void increment() {
if (number != 0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + "--> " + number);
//通知其他线程,+1完毕
this.notifyAll();
}
//消耗资源方法
public synchronized void decrement() {
if (number == 0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + "--> " + number);
//通知其他线程,-1完毕
this.notifyAll();
}
}
存在问题
只有两个线程是安全的,但是当线程数增加到3,4的时候,就发生问题

我们这里使用的是if判断,所以会发生虚假唤醒,应使用while循环
package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;
/**
* 线程之间的通信问题 : 等待唤醒,通知唤醒
* 线程交替执行: A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class Synchronized_ {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"B").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"C").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"D").start();
}
}
/**
* 多线程处理逻辑
* 判断等待 , 业务 , 通知
*/
//资源类
class Data{
private int number = 0;
//增加资源方法
public synchronized void increment() {
// if (number != 0){
// //等待
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
while (number != 0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + "--> " + number);
//通知其他线程,+1完毕
this.notifyAll();
}
//消耗资源方法
public synchronized void decrement() {
// if (number == 0){
// //等待
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
while (number == 0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + "--> " + number);
//通知其他线程,-1完毕
this.notifyAll();
}
}
JUC版

在这里使用的是await signal
通过Lock可以找到Condition

代码实现
package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//JUC版的生产者与消费者问题
public class Lock_ {
public static void main(String[] args) {
Data1 data = new Data1();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"B").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"C").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"D").start();
}
}
class Data1{
private int number = 0;
Lock lock = new ReentrantLock();
//这里condition取代了对象监视器的用法
Condition condition = lock.newCondition();
//增加资源方法
public void increment() {
lock.lock();
try {
while (number != 0) {
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "--> " + number);
//通知其他线程,+1完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
//消耗资源方法
public void decrement() {
lock.lock();
try {
while (number == 0) {
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "--> " + number);
//通知其他线程,+1完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
Condition
作用:精准的通知和唤醒线程,有序执行
package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 详解Condition
*/
public class Condition_ {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
class Data2{
//1A 2B 3C
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try {
// 业务,判断 -> 执行 -> 通知
while (number != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"---> AAAAAAA");
//唤醒 唤醒指定的人
number = 2;
//精准唤醒Condition2
condition2.signal();
} 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()+"---> BBBBBBB");
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()+"---> CCCCCCC");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
如何判断锁的是谁?什么是锁,锁的是谁
锁对象(多个),Class模板(1个)
第一二问
package javaSEStudy.JUC.JUCDemo.lock_8;
import java.util.concurrent.TimeUnit;
/**
* 8锁就是关于锁的8个问题
* 1.标准情况下,两个线程先输出谁 先输出A
* 2.将A延时2秒,先输出谁,两个线程先输出谁 先输出A
*/
public class Test1 {
public static void main(String[] args) {
// 这里只有一个对象
Phone phone = new Phone();
// 为什么A永远第一个打印,不是因为A先调用
//是因为锁的存在
new Thread(()->{
phone.send();
},"A").start();
// 休眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
// Synchronized锁的对象是方法的调用者
// 两个方法用的同一个锁,谁先拿到谁执行
public synchronized void send(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送消息");
}
public synchronized void call(){
System.out.println("打电话");
}
}
第三四问
package javaSEStudy.JUC.JUCDemo.lock_8;
import java.util.concurrent.TimeUnit;
/**
* 8锁就是关于锁的8个问题
* 3.增加了一个普通方法,先输出发短信还是玩手机
* 倘若未将A休眠,则先输出发短信,休眠,因为它调用
* 倘若A休眠,则先输出玩手机,他没有锁,不受锁的影响
*
* 4.两个对象,两个调用者,两把锁
* 倘若A未休眠2秒,A和B线程随机先启动,但是大概率先启动A,因为CPU是单核,且启动顺序为A,B,大概率先执行A
* 倘若A休眠2秒,则一定先启动B,他们锁的是两个不同对象,则先执行B
*/
public class Test2 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
Phone1 phone2 = new Phone1();
// 为什么A永远第一个打印,不是因为A先调用
//是因为锁的存在
new Thread(()->{
phone1.send();
},"A").start();
// 休眠
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone1{
// Synchronized锁的对象是方法的调用者
// 两个方法用的同一个锁,谁先拿到谁执行
public synchronized void send(){
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("发送消息");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁,不受锁的影响
public void play(){
System.out.println("玩手机");
}
}
第五六问
package javaSEStudy.JUC.JUCDemo.lock_8;
import java.util.concurrent.TimeUnit;
/**
* 8锁问题
* 5.增加两个静态的同步方法,只有一个对象,先打印谁? 先打印A
* 在这里锁的是Class模版,和类一起锁着的
* 6.两个对象,增加两个静态的同步方法,只有一个对象,先打印谁?
* 由于锁的是Class模版,所以即使是两个对象,也还是先执行A
*/
public class Test3 {
public static void main(String[] args) {
// 两个对象的类模版只有一个,static,锁的是Class
Phone3 phone1 = new Phone3();
Phone3 phone3 = new Phone3();
new Thread(()->{
phone1.send();
},"A").start();
// 休眠
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
new Thread(()->{
phone3.call();
},"B").start();
}
}
// Phone2只有唯一一个Class对象
class Phone2{
// Synchronized锁的对象是方法的调用者
// static 静态方法
// 和类一起加载,锁的是Class模版
public static synchronized void send(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送消息");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
第七八问
package javaSEStudy.JUC.JUCDemo.lock_8;
import java.util.concurrent.TimeUnit;
/**
* 8锁问题
* 7. 一个对象,一个普通的同步方法,一个静态的同步方法,先打印谁
* 先打印B,因为这是两把不同的锁,不用等待
* 8. 两个对象,一个普通的同步方法,一个静态的同步方法,先打印谁
* 先打印B,两个对象,两把不同的锁,互不干扰
*/
public class Test4 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.send();
},"A").start();
// 休眠
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone3 {
// 静态同步方法 锁的是Class类模版
public static synchronized void send(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送消息");
}
// 普通同步方法 锁的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
小结
new -->是this,这个具体的对象
static -->是Class这个唯一模版
集合类,不安全
List不安全
package javaSEStudy.JUC.JUCDemo.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//ConcurrentModificationException 并发修改异常
public class List_{
public static void main(String[] args) {
// List<String> list = Arrays.asList("1", "2");
// list.forEach(System.out::println);
//并发下ArrayList不安全
/**
* 解决方案
* 1.将new ArrayList 改为 new Vector
* 因为Vector本身就是绝对安全
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
*/
// List<String> list = new ArrayList<>();
// List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
// CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
// 多个线程调用的时候,list这一个资源,读取的时候是固定的,但是写的时候可能会发生覆盖
// 在写入的时候,避免覆盖,造成数据问题
// CopyOnWriteArrayList 比 Vector好处: 读写分离,Vector调用的是Synchronized,COW调用Lock,效率更高
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Set不安全
package javaSEStudy.JUC.JUCDemo.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 同理: ConcurrentModificationException 并发修改异常
* 解决方法
* 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2.Set<String> set = new CopyOnWriteArraySet<>();
*/
public class Set_ {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
hashSet底层是什么?
public HashSet() {
map = new HashMap<>();
}
底层就是hashMap,add set方法本质就是map,他们的key无法重复
map不安全
package javaSEStudy.JUC.JUCDemo.unsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
// Map不安全
/**
* ConcurrentModificationException
* 并发修改异常
*/
public class Map_ {
public static void main(String[] args) {
// map不是这么使用的
// 默认等价于 new HashMap<>(16,0.75)
Map<String, String> map = new HashMap<>();
// 两种解决方法
// Collections.synchronizedMap(map);
// new ConcurrentHashMap<>();
for (int i = 1; i <= 100; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Callable

特点
- 可以有返回值
- 可以抛出异常
- 方法不同,Runnable是实现run()方法,但是Callable是实现call方法


直接上代码
package javaSEStudy.JUC.JUCDemo.callable_;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Callable_ {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread()).start();
// 最好别用上述方式,实现功能有限
// 在这里,Thread只能接收Runnable,所以Callable想要调用Thread,需要通过Runnable接口
MyThread thread = new MyThread();
// 适配类
FutureTask futureTask = new FutureTask(thread);
// 从而成功将Callable启动
// 这里只会打印一个你好,因为结果被缓存,所以效率更高
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start();
// 获取返回值
// 注意,这个get可能会产生阻塞,一般把他放到最后
// 或者使用异步通信来处理
System.out.println(futureTask.get());
// 链式调用
// new Thread(new FutureTask(new MyThread())).start();
}
}
class MyThread implements Callable<String> {
@Override
public String call() {
System.out.println("你好");
return "hello";
}
}
ps: 1.有缓存 2.结果可能需要等待,会阻塞
常用辅助类
CountDownLatch

package javaSEStudy.JUC.JUCDemo.assistant;
import java.util.concurrent.CountDownLatch;
// CountDownLatch 详解 计数器
public class CountDownLatch_ {
public static void main(String[] args) throws InterruptedException {
// 必须要执行任务的时候再去使用
// 总数是10,这是个倒计时
CountDownLatch cdl = new CountDownLatch(10);
for (int i = 1; i <= 10 ; i++) {
new Thread(()->{
System.out.println("第 "+Thread.currentThread().getName()+" 个 GO GO GO ,出发喽");
// 数量-1
cdl.countDown();
},String.valueOf(i)).start();
}
// 等待计数器归零,然后再向下执行
// 如果没有这个等待,直接将门关了,后面的也就出不去了
// 这里关闭的顺序不是按照次序的,但是都是保证全部出去后再关闭
// cdl.await();
System.out.println("关门了");
// 倘若我需要将大的进程关闭,得等线程全部关闭才能关闭,可以使用该倒计时
// -1操作
// cdl.countDown();
}
}
原理
cdl.countDown(); 数量-1
cdl.await(); 等待计数器归零
每次都有线程调用countDown()方法,数量-1,假设计数器归零,await()方法就会被唤醒,继续执行
CyclicBarrier

加法计算器
package javaSEStudy.JUC.JUCDemo.assistant;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
// 加法计算器
public class CyclicBarrier_ {
public static void main(String[] args) {
/**
* 集齐7颗龙珠召唤神龙
*/
// 召唤龙珠的线程
CyclicBarrier cb = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
// lambda 能操作到 i 吗,无法直接拿到
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集了" + temp + "颗龙珠");
try {
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore

话不多说,直接上代码
package javaSEStudy.JUC.JUCDemo.assistant;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// 信号量详解
public class Semaphore_ {
public static void main(String[] args) {
// 需要参数 线程数量
// 限流的时候可以使用
Semaphore semaphore = new Semaphore(3);
for (int i = 1; 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 {
// release() 释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire() 获取 假设如果已经满了,就等待,等待到被释放为止
semaphore.release() 释放 会将当前的信号量释放+1,然后唤醒等待的线程
作用:多个共享资源的互斥操作的使用!并发限流,控制最大的线程数
读写锁
ReadWriteLock

package javaSEStudy.JUC.JUCDemo.readWriteLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* 读写锁详解 写入只允许一个线程写入,读的时候可以多线程读
* 读 - 读 可以共存!
* 读 - 写 不能共存
* 写 - 写 不能共存
*/
public class ReadWriteLock_ {
public static void main(String[] args) {
// 发生并发异常
// MyCache1 myCache2 = new MyCache1();
//
// // 进行写入操作
// for (int i = 1; i <= 5; i++) {
// final int temp = i;
// new Thread(()->{
// myCache2.put(temp+"",temp+"");
// },String.valueOf(i)).start();
// }
// // 进行读取操作
// for (int i = 1; i <= 5; i++) {
// final int temp = i;
// new Thread(()->{
// myCache2.get(temp+"");
// },String.valueOf(i)).start();
// }
MyCache2 myCache2 = new MyCache2();
// 进行写入操作
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache2.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 进行读取操作
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache2.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache1 {
// volatile 保证原子性
private volatile Map<String, Object> map = new HashMap<>();
// 存入数据 写入
public void put(String key, Object value) {
// 多线程一定要注意是那条线程进入
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完毕");
}
// 取出数据 读取
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完毕");
}
}
/**
* 自定义缓存
* 加了锁版本
*/
class MyCache2 {
// volatile 保证原子性
private volatile Map<String, 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();
}
}
}
阻塞队列

先进先出
阻塞队列

BlockingQueue属于Collection集合框架,与List和Set同级,实现子类也是ArrayBlockingQueue和LinkedBlockingQueue


阻塞队列不是新的东西
什么情况下会使用阻塞队列
多线程并发处理,线程池
学会使用队列: 添加,移除,判断
四组API
- 抛出异常
- 不会抛出异常
- 等待阻塞
- 超时等待
| 方式 | 抛出异常 | 不会抛出异常,有返回值 | 等待,阻塞 | 超时等待 |
|---|---|---|---|---|
| 添加 | add() | offer() | put() | offer( , , ) |
| 移除 | remove() | poll() | take() | poll( , ) |
| 检查队首元素 | element | peek | - | - |
package javaSEStudy.JUC.JUCDemo.blockingQueue_;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
// 阻塞队列
@SuppressWarnings({"all"})
public class BlockingQueue_ {
public static void main(String[] args) throws InterruptedException {
/**
* 父类: Collection
* List,Set,BlockingQueue 这三个同级
*/
// test1();
// test2();
// test3();
test4();
}
// 四种API
/**
* 抛出异常
*/
public static void test1() {
// 括号里面是队列大小
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);
// add()返回值是 布尔值
System.out.println(abq.add("A"));
System.out.println(abq.add("B"));
System.out.println(abq.add("C"));
// 此时再添加一个元素
// 抛出异常:IllegalStateException: Queue full 队列已满
// System.out.println(abq.add("D"));
// 查看队首元素
System.out.println(abq.element());
System.out.println("----------------------------");
// remove() 会弹出当前这个值,FIFO 先进先出
System.out.println(abq.remove());
System.out.println(abq.remove());
System.out.println(abq.remove());
// 现在队列是空的,再去取值,会抛出异常 NoSuchElementException 没有可搜索元素异常
// System.out.println(abq.remove());
}
/**
* 不抛出异常,但是有返回值
*/
public static void test2() {
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);
System.out.println(abq.offer("A"));
System.out.println(abq.offer("B"));
System.out.println(abq.offer("C"));
// 容量只有3个,前三个返回true,但是当无法加入时,则返回false,不会发生异常
// System.out.println(abq.offer("D"));
// 检查队首元素
System.out.println(abq.peek());
System.out.println("--------------------");
System.out.println(abq.poll());
System.out.println(abq.poll());
System.out.println(abq.poll());
// 只有三个,当三个全部取出,会返回null
System.out.println(abq.poll());
}
/**
* 等待,阻塞
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);
// 加入,没有返回值,也就无法sout
// 会一直阻塞
abq.put("A");
abq.put("B");
abq.put("C");
// 在添加一个元素,会一直等待
// abq.put("D");
System.out.println("--------------------");
// 取出操作,有返回值,为当前元素
System.out.println(abq.take());
System.out.println(abq.take());
System.out.println(abq.take());
// 此时已经没有元素可取出,所以一直阻塞
System.out.println(abq.take());
}
/**
* 等待超时,自动退出
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);
// 加入,也是offer,但是需要添加时间参数
// 第一个参数为添加内容,第二个参数为等待时间,第三个为等待时间单位
System.out.println(abq.offer("A"));
System.out.println(abq.offer("B"));
System.out.println(abq.offer("C"));
// 此时再添加一个元素,但是让他进行超时等待
// 加入等待两秒,倘若无法加入,则自动退出
System.out.println(abq.offer("D", 2, TimeUnit.SECONDS));
System.out.println("----------------------");
// 取出
System.out.println(abq.poll());
System.out.println(abq.poll());
System.out.println(abq.poll());
// 此时只有三个参数,无法再取出
// 两个参数,第一个参数为等待时间,第二个参数为等待时间单位
System.out.println(abq.poll(2, TimeUnit.SECONDS));
}
}
SynchronizedQueue 同步队列
没有容量,进去一个元素必须等待取出后才能再放入
put, take
直接上个栗子
package javaSEStudy.JUC.JUCDemo.blockingQueue_;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
// 同步队列详解
/**
* 与其他的BlockingQueue不一样
* SynchronizedQueue不存储元素
* put了一个元素,必须从里面先take出来,才能继续put
*/
public class SynchronizedQueue_ {
public static void main(String[] args) {
SynchronousQueue<String> soq = new SynchronousQueue<>();
new Thread(() -> {
try {
soq.put("1");
System.out.println(Thread.currentThread().getName() + " put 1");
soq.put("2");
System.out.println(Thread.currentThread().getName() + " put 2");
soq.put("3");
System.out.println(Thread.currentThread().getName() + " put 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "--" + soq.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "--" + soq.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "--" + soq.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
线程池
线程池:三大方法,七大参数,四种拒绝策略
池化技术:程序的运行,本质:占用系统的资源,优化资源的使用. --> 池化技术
线程池,连接池,内存池,对象池... // 创建和销毁十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来该地方来拿,用完之后再还到该地方
默认大小为 2
线程池的好处:
- 1.降低资源的消耗
- 2.提高响应速度
- 3.在池中,方便管理
总结:线程复用,可以控制最大并发数,管理线程
三大方法

package javaSEStudy.JUC.JUCDemo.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 线程池的三大方法
public class PoolMethod {
public static void main(String[] args) {
// 单例模式,创建只有一个线程的线程池
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 创建固定大小的线程池,需要给定参数,参数为创建线程池的大小
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 创建一个可伸缩,具有弹性的线程池,线程池大小随任务改变而改变
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 100; i++) {
// 使用了线程池之后,用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池使用完毕需要将其关闭
threadPool.shutdown();
}
}
}
七大参数
package javaSEStudy.JUC.JUCDemo.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 七大参数详解
public class PoolParameter {
public static void main(String[] args) {
/**
* 使用 Executors 去创建一个线程池
* 本质: new ThreadPoolExecutor
*/
// 单例模式,创建只有一个线程的线程池
ExecutorService threadPool1 = Executors.newSingleThreadExecutor();
// 创建固定大小的线程池,需要给定参数,参数为创建线程池的大小
ExecutorService threadPool2 = Executors.newFixedThreadPool(5);
// 创建一个可伸缩,具有弹性的线程池,线程池大小随任务改变而改变
ExecutorService threadPool3 = Executors.newCachedThreadPool();
/**
* newSingleThreadExecutor() 源码
*
* public static ExecutorService newSingleThreadExecutor() {
* return new FinalizableDelegatedExecutorService
* (new ThreadPoolExecutor(1, 1,
* 0L, TimeUnit.MILLISECONDS,
* new LinkedBlockingQueue<Runnable>()));
* }
*/
/**
* newFixedThreadPool() 源码
*
* public static ExecutorService newFixedThreadPool(int nThreads) {
* return new ThreadPoolExecutor(nThreads, nThreads,
* 0L, TimeUnit.MILLISECONDS,
* new LinkedBlockingQueue<Runnable>());
* }
*/
/**
* newCachedThreadPool() 源码
*
* Integer.MAX_VALUE 约等于 21亿 OOM溢出
*
* public static ExecutorService newCachedThreadPool() {
* return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
* 60L, TimeUnit.SECONDS,
* new SynchronousQueue<Runnable>());
* }
*/
/**
* ThreadPoolExecutor() 源码
*
* public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
* int maximumPoolSize, // 最大核心线程池大小
* long keepAliveTime, // 线程池中超出核心线程数的空闲线程存活时间,无人调用就会释放
* TimeUnit unit, // 超时单位
* BlockingQueue<Runnable> workQueue, // 阻塞队列
* ThreadFactory threadFactory, // 线程工厂,创建线程的,一般不用动
* RejectedExecutionHandler handler) { // 拒绝策略
* if (corePoolSize < 0 ||
* maximumPoolSize <= 0 ||
* maximumPoolSize < corePoolSize ||
* keepAliveTime < 0)
* throw new IllegalArgumentException();
* if (workQueue == null || threadFactory == null || handler == null)
* throw new NullPointerException();
* this.corePoolSize = corePoolSize;
* this.maximumPoolSize = maximumPoolSize;
* this.workQueue = workQueue;
* this.keepAliveTime = unit.toNanos(keepAliveTime);
* this.threadFactory = threadFactory;
* this.handler = handler;
* }
*/
}
}
四种拒绝策略

package javaSEStudy.JUC.JUCDemo.pool;
import java.util.concurrent.*;
// 线程池的拒绝策略
public class PoolRefuse {
public static void main(String[] args) {
// 手动创建一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
// 阻塞队列最多存放三条数据
new LinkedBlockingQueue<>(3),
// 调用Executors 默认的线程工厂
Executors.defaultThreadFactory(),
// 默认的拒绝策略(办理业务线程满了,现在还有线程想进来,不处理该请求)
// 抛出异常 RejectedExecutionException
// new ThreadPoolExecutor.AbortPolicy()
// 哪儿来的去哪里,这里多出来的两个线程来自main线程,这里由main线程处理
// new ThreadPoolExecutor.CallerRunsPolicy()
// 队列满了,丢掉超出的任务,不会抛出异常
// new ThreadPoolExecutor.DiscardPolicy()
// 队列满了,尝试和最早的竞争,也不会抛出异常
// 倘若竞争失败就抛出任务,竞争成功就执行该任务
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
// 最大承载数: 最大线程数+阻塞队列
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName()+" 完成 ");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
小结和拓展
最大线程到底该如何定义? (调优)
- CPU 密集型 电脑几个核心,最大线程就是几,CPU效率最高(电脑是12核心,12条线程同时运行)
- IO 密集型 判断程序中十分耗费IO的线程有多少个,其最大线程数就会大于该线程数
package javaSEStudy.JUC.JUCDemo.pool;
import java.util.concurrent.*;
// 自定义线程
public class PoolRefuse {
public static void main(String[] args) {
// 自定义线程池
// 最大线程池如何定义
// 1. CPU 密集型 电脑几个核心,最大线程就是几,CPU效率最高(电脑是12核心,12条线程同时运行)
// 2. IO 密集型 判断程序中十分耗费IO的线程有多少个,其最大线程数就会大于该线程数
// 获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
// 阻塞队列最多存放三条数据
new LinkedBlockingQueue<>(3),
// 调用Executors 默认的线程工厂
Executors.defaultThreadFactory(),
// 默认的拒绝策略(办理业务线程满了,现在还有线程想进来,不处理该请求)
// 抛出异常 RejectedExecutionException
// new ThreadPoolExecutor.AbortPolicy()
// 哪儿来的去哪里,这里多出来的两个线程来自main线程,这里由main线程处理
// new ThreadPoolExecutor.CallerRunsPolicy()
// 队列满了,丢掉超出的任务,不会抛出异常
// new ThreadPoolExecutor.DiscardPolicy()
// 队列满了,尝试和最早的竞争,也不会抛出异常
// 倘若竞争失败就抛出任务,竞争成功就执行该任务
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
// 最大承载数: 最大线程数+阻塞队列
for (int i = 1; i <= 100; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName()+" 完成 ");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
四大函数式接口(重点)
必会技能:Lambda表达式,链式编程,函数式接口,Stream流式计算
函数式接口: 只有一个方法的接口
例如 Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
作用:简化编程模型,在新版本的框架底层大量应用
foreach(消费者类的函数式接口)

Function(函数式接口)
package javaSEStudy.JUC.JUCDemo.functionalInterface_;
import java.util.function.Function;
// Functiona详解
/**
* Function 函数型接口,有一个输入参数,有一个输出
* 只要是函数式接口,就可用lambda表达式简化
*/
public class Functiona_ {
public static void main(String[] args) {
/**
* Function 源码
* 传入参数T,返回类型R
*
* @FunctionalInterface
* public interface Function<T, R> {
* R apply (T t);
* }
*/
new Function<String, String>() {
@Override
public String apply(String str) {
return str;
}
};
// 对上述代码进行lambda表达式简化
Function<String, String> function = (str) -> {
return str;
};
}
}
Predicate(断定型接口)
package javaSEStudy.JUC.JUCDemo.functionalInterface_;
import java.util.function.Predicate;
// Predicate详解
/**
* Predicate 断定型接口:有一个输入参数,返回值只能为Boolean
*/
public class Predicate_ {
public static void main(String[] args) {
/**
* Predicate 源码
*只有一个输入参数,返回值为Boolean
*
* @FunctionalInterface
* public interface Predicate<T> {
*
* boolean test (T t);
* }
*/
// 判断字符串是否为空
Predicate<String> op1 = new Predicate<>() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};
// 使用Lambda表达式简化
Predicate<String> op2 = str -> {
return str.isEmpty();
};
// 输出 true,未放入参数
System.out.println(op1.test(""));
}
}
Consumer(消费型接口)
package javaSEStudy.JUC.JUCDemo.functionalInterface_;
import java.util.function.Consumer;
/**
* Consumer 消费型接口,只有输入参数,没有返回值
*/
public class Consumer_ {
public static void main(String[] args) {
/**
* Consumer 源码
* 只有输入参数T,没有返回值
*
* @FunctionalInterface
* public interface Consumer<T> {
*
* void accept (T t);
* }
*/
Consumer<String> consumer1 = new Consumer<>() {
@Override
public void accept(String str) {
System.out.println(str);
}
};
// 使用Lambda表达式简化
Consumer<String> consumer2 = (str) -> {
System.out.println(str);
};
// 打印字符串
consumer1.accept("Hello World");
}
}
Supplier(供给型接口)
package javaSEStudy.JUC.JUCDemo.functionalInterface_;
import java.util.function.Supplier;
/**
* Supplier 供给型接口 只有返回值,没有输入参数
*/
public class Supplier_ {
public static void main(String[] args) {
/**
* Supplier 源码
* 没有参数,只有返回值
*
* @FunctionalInterface
* public interface Supplier<T> {
* T get ();
* }
*/
Supplier<String> supplier1 = new Supplier<>() {
@Override
public String get() {
return "你好,世界";
}
};
// 使用Lambda表达式简化
Supplier<String> supplier2 = ()->{
return "你好,世界";
};
// 创建输出
System.out.println(supplier1.get());
}
}
Stream流式计算
什么是Stream流式计算
大数据 : 存储 + 计算


获得流
怎么得到一条Stream流
| 获取方式 | 方法名 | 说明 |
|---|---|---|
| 单列集合 | default Stream |
Colleation中的默认方法 |
| 双列集合 | 无 | 无法直接使用Stream(使用需转为keySet() entrySet()) |
| 数组 | public static |
Arrays工具类中的静态方法 |
| 零散数据 | public static |
Stream接口中的静态方法,需要同种数据类型 |
直接上代码展示
package javaSEStudy.JUC.JUCDemo.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.stream.Stream;
// 获得Stream流
public class FetchStream {
public static void main(String[] args) {
/**
* 1.单列获得Stream流
*/
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d", "e");
// 获取一条流水线,并将集合中的数据放到流水线上
// 因为ArrayList是Collection的实现子类,可以直接使用
Stream<String> stream = list.stream();
// 使用终结方法打印流水线上的所有数据
stream.forEach(System.out::println);
// 将上述两条代码使用链式调用
list.stream().forEach(System.out::println);
/**
* 2.双列获得Stream流
*/
HashMap<String, Integer> sihm = new HashMap<>();
sihm.put("a", 1);
sihm.put("b", 2);
sihm.put("c", 3);
sihm.put("d", 4);
// 使用keySet获取Stream流
sihm.keySet().stream().forEach(System.out::println);
// 使用entrySet获取Stream流
sihm.entrySet().stream().forEach(System.out::println);
System.out.println("---------------");
sihm.entrySet().stream().forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));
/**
* 3.数组获得Stream流
*/
int[] arr1 = {1, 2, 3, 4, 5};
String[] arr2 = {"a", "b", "c", "d", "e"};
Arrays.stream(arr1).forEach(System.out::println);
Arrays.stream(arr2).forEach(System.out::println);
/**
* 4.一堆零散数据,但是数据类型需要相同,才能获得Stream流
*
* Stream.of() 使用细节
* 方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
* 但是数组必须是引用数据类型(String),不能是基本数据类型(int),否则将整个数组当做一个元素返回一个地址值
*/
Stream.of(1,2,3,4,5).forEach(System.out::println);
System.out.println("---------------");
Stream.of("a","b","c").forEach(System.out::println);
}
}
流的中间方法
常见方法
- filter 过滤
- limit 获取前几个元素
- skip 跳过前几个元素(只获取后面的元素)
- distinct 元素去重(依赖HashCode和equals方法)
- concat 将两个流合并为一个流
- map 转换流中的元素类型
注意:
1.中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
2.修改Stream流中的数据,不会影响原来集合或者数组中的数据
代码展示
package javaSEStudy.JUC.JUCDemo.stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
import java.util.stream.Stream;
// Stream流的中间方法
public class StreamMiddleMethod {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "张无忌", "周芷若", "赵敏", "张强", "张三丰",
"张翠山", "张亮", "张亮", "蔡徐坤", "蔡徐坤", "蔡徐坤", "蔡徐坤");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2, "基尼太美", "哎哟你干嘛");
// filter 过滤 张开头留下,其余不要
list1.stream()
.filter((s)-> s.startsWith("张"))
.filter(s -> s.length() > 2)
.forEach(System.out::println);
System.out.println("--------------------");
// limit 获取前几个元素
// skip 跳过前几个元素,获取后面的元素
list1.stream()
.limit(6)
.skip(2)
.forEach(System.out::println);
System.out.println("---------------------");
// distinct 元素去重
list1.stream()
.distinct()
.forEach(System.out::println);
System.out.println("----------------------");
// concat 合并两个流为一个流
Stream.concat(list1.stream(), list2.stream())
.distinct()
.forEach(System.out::println);
System.out.println("----------------------");
// map 转换流中的数据类型
// 需求:只获取年龄进行打印 String -> int
ArrayList<String> list3 = new ArrayList<>();
Collections.addAll(list3, "张无忌-15", "周芷若-18", "赵敏-18", "张强-2", "张三丰-99", "蔡徐坤-2.5");
// 当map方法执行完毕后,流上的数据就变成了整数,forEach中的数据现在就是整数了
list3.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
String[] arr = s.split("-");
String ageString = arr[1];
int age = Integer.parseInt(ageString);
return age;
}
}).forEach(System.out::println);
// 让我们将上述代码使用lambda表达式简化
list3.stream()
.map(s ->{return Integer.parseInt(s.split("-")[1]);})
.forEach(System.out::println);
}
}
流的终结方法
常用方法
- foreach 遍历
- count 统计
- toArray 收集流中的数据,放到数组中
- collect(Collector collecor) 收集流中的数据放到集合中
package javaSEStudy.JUC.JUCDemo.stream;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
// Stream流的终结方法
@SuppressWarnings({"all"})
public class StreamEndMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-男-14", "周芷若-女-18", "赵敏-女-16", "张强-男-12", "张三丰-男-99",
"张翠山-女-23", "张亮-男-24", "蔡徐坤-男-2.5");
// forEach 遍历
list.stream().forEach(System.out::println);
// count 统计
System.out.println(list.stream().count());
// toArray 将流里的数据收集为一个数组
// 无参构造,保存为Object类型,在将其转换为String显示
Object[] array = list.stream().toArray();
System.out.println(Arrays.toString(array));
// 有参构造,IntFunction的泛型:具体类型的数组
// apply的形参:流中数据的个数,要跟数组的长度保持一致
// apply的返回值:具体类型的数组
// 方法体:创建数组
// toArray方法的参数的作用:负责创建一个指定类型的数组
// toArray方法的底层,会依次得到流中的每个数据,并把数据方法数组中
// toArray方法的返回值:是一个装着流中所有数据的数组
String[] arr = list.stream().toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(arr));
// 将上述代码简化
String[] arr1 = list.stream().toArray((value) -> {
return new String[value];
});
System.out.println(Arrays.toString(arr1));
System.out.println("------------------------------------");
// collect(Collector collector) 收集流中的数据,放到集合(List Set Map)
/**
* 收集到List中
* 需求:收集男性
*/
List<String> newList = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toList());
// System.out.println(newList);
/**
* 收集到Set中
* 需求:收集女性
*/
Set<String> newSet = list.stream()
.filter(s -> "女".equals(s.split("-")[1]))
.collect(Collectors.toSet());
// System.out.println(newSet);
/**
* 收集到Map中
* 注意: 如果我们要收集数据到Map中,键不能重复,否则会报错
*
* 需求: 谁为键,谁为值
* 姓名:键 年龄:值 收集男性
*
* toMap 参数一表示键的生成规则 参数二表示值的生成规则
*
* 参数一:
* Function 泛型一:流中每一个数据的类型
* 泛型二:Map集合中键的数据类型
* 方法apply:依次表示流中每一个数据 张无忌-男-14
* 方法体: 生成键 张无忌
* 返回值: 已经生成的键
*
* 参数二:
* Function 泛型一:流中每一个数据的类型
* 泛型二:Map集合中值的数据类型
* 方法apply:依次表示流中每一个数据 张无忌-男-14
* 方法体: 生成键 14
* 返回值: 已经生成的值
*/
Map<String, Double> map1 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
return s.split("-")[0];
}
}, new Function<String, Double>() {
@Override
public Double apply(String s) {
return Double.parseDouble(s.split("-")[2]);
}
}));
// 让我们将上述代码简化
Map<String, Double> map2 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(s -> s.split("-")[0], s -> Double.parseDouble(s.split("-")[2])));
System.out.println(map2);
}
}
小结

使用案例
package javaSEStudy.JUC.JUCDemo.stream.test;
import java.util.Arrays;
import java.util.List;
/**
* 要求
* 1.ID必须为偶数
* 2.年龄必须大于23
* 3.用户名转为大写
* 4.用户名字母倒序输出
* 5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 24);
User u5 = new User(5, "e", 25);
User u6 = new User(6, "f", 26);
// 集合就是存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5, u6);
// 计算交给Stream filter 过滤
// 链式编程
list.stream()
// ID必须为偶数
.filter(u -> {return u.getId() % 2 == 0;})
// 年龄必须大于23
.filter(u -> {return u.getAge() > 23;})
// 用户名转为大写
.map(u ->{return u.getName().toUpperCase();})
// 用户名字母倒序输出,无参为正序输出
.sorted((n1,n2) ->{return n2.compareTo(n1);})
// 只输出一个用户 limit 限制流中输出数量
.limit(1)
// 遍历输出
.forEach(System.out::println);
}
}
forkJoin
什么是forkJoin
里面维护的都是双端队列
并行执行任务,提高效率(大数据量)
批处理

forkJoin特点: 工作窃取

解释:
当A的任务未完成,B的任务完成,B不会原地等待,会去将A的任务拿过来执行
使用方法

我们直接上代码
package javaSEStudy.JUC.JUCDemo.forkJoin_;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
// 求和计算的三种方式
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 消耗时间 4632
// test1();
// 消耗时间 4307
// test2();
// 消耗时间 72
test3();
}
// 传统写法
public static void test1(){
long start = System.currentTimeMillis();
Long sum = 0L;
for (Long i = 1L; i <= 10_0000_0000L; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("和 "+ sum +"消耗时间 "+(end - start));
}
// 使用ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoin_ task = new ForkJoin_(0L, 10_0000_0000L);
// 执行任务,没有返回值
// forkJoinPool.execute(task);
// 提交任务,有返回值
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
// 在这里会阻塞等待
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("和 "+ sum +"消耗时间 "+(end - start));
}
// 使用Stream并行流
public static void test3(){
long start = System.currentTimeMillis();
// range -> 开区间 () rangeClosed() -> 左开右闭 (]
// parallel() 并行计算 reduce() 拿出结果
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("和 "+ sum +"消耗时间 "+(end - start));
}
}
异步回调
Future : 对将来某个事件的结果进行建模!

package javaSEStudy.JUC.JUCDemo.future_;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
// 异步调用: ajax
// 异步执行 成功回调 失败回调
public class Future_ {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 发起一个请求 无返回值 runAsync进行异步回调
CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Void 无返回值");
});
System.out.println("我先执行输出");
// 获取执行结果
cf1.get();
System.out.println("---------------------------------------");
/**
* success code 200
* error code 404 500
*/
// 发起一个请求 有返回值 supplyAsync进行异步回调
// ajax 成功和失败的回调
// 失败会返回错误信息
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " Integer 有返回值");
// 当编译错误时
// int i = 1 / 0;
return 1024;
});
// whenComplete编译成功返回的结果
cf2.whenComplete((t,u) -> {
System.out.println(t); // t 正确的返回结果
System.out.println(u); // u 错误信息
})
// 编译失败返回的结果
.exceptionally((e)->{
System.out.println(e.getMessage());
return 404; // 可以获取到错误返回结果
}).get();
}
}
理解JMM
什么是JMM?
JVM -> java虚拟机
JMM -> java内存模型,是一个不存在的东西,是一种约定,概念
关于JMM的一些同步约定
- 1.线程解锁前: 必须把共享变量立刻刷回主存
解释:线程有自己的工作内存,操作一个变量会从主存拷贝变量到自己的工作内存,然后对该变量进行操作,如果要解锁,必须将该变量重新在主存中刷新,确保两个变量保持一致 - 2.线程加锁前,必须读取主存中的最新值到工作内存中
- 3.加锁和解锁是同一把锁
线程: 工作内存,主存
八个操作

数据同步八大原子操作
(1)lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
(3)read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
(4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
(6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
(7)store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
如果一个变量从主内存中复制到工作内存中,就需要按顺序的执行read、load指令,如果是工作内存刷新到主内存则需要按顺序的执行store、write操作。但JMM只保证了顺序执行,并没有保证连续执行。
原子操作
同步规则分析
- 不允许线程无原因的将变量从工作内存写回主内存(没有经过任何的assign)。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量(load或者assign)的变量,就是对一个变量进行use和store操作之前,必须先自行load与assign操作。
- 一个变量在同一时刻只能被一个线程lock,同一个线程能多次lock,必须对应的多次unlock,lock与unlock必须成对的出现。
- 如果对一个变量执行lock操作,将会清除工作内存中此变量的值,在执行引擎使用这个变量时,必须重新load、assign操作。
- 如果一个线程对一个变量没有进行lock操作,则不允许unlock,也不允许对其他线程进行unlock。
- 对一个变量进行unlock时,必须先将变量刷新到主内存。
可见性,原子性与有序性
原子性
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程给打断。
可见性
可见性指的是当一个共享变量被一个线程修改后,其他线程是否能够立即感知到。对于串行执行的程序是不存在可见性,当一个线程修改了共享变量后,后续的线程都能感知到共享变量的变化,也能读取到最新的值,所以对于串行程序来讲是不存在可见性问题。
对于多线程程序,就不一定了,前面分析过对于共享变量的操作,线程都是将主内存的变量copy到工作内存进行操作后,在赋值到主内存中。这样就会导致,一个线程改了之后还未回写到主内存,其余线程就无法感知到变量的更新,线程之间的工作内存是不可见的。另外指令重排序以及编译器优化也会导致可见性的问题。
有序性
有序性是指对于单线程的代码,我们总是认为程序是按照代码的顺序进行执行,对于单线程的场景这样理解是没有问题,但是在多线程情况下, 程序就会可能发生乱序的情况,编译器编译成机器码指令后,指令可能会被重排序,重排序的指令并不能保证与没有排序前的保持一致。
在java程序中,倘若在本线程内,所有的操作都可视为有序性,在多线程环境下,一个线程观察另外一个线程,都视为无顺序可言。
问题 :程序不知道主内存中的值已经被更改

package javaSEStudy.JUC.JUCDemo.volatile_;
import java.util.concurrent.TimeUnit;
// Volatile 有关问题
public class VolatileQuestion {
// 定义一个可操作变量
private static int num = 0;
public static void main(String[] args) {
// 制定一个线程,上面是main线程
// 未结束死循环说明该线程对主存变化不可知
new Thread(() -> {
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
/**
* 问题?
* while死循环,下面已经将num值更改,但是循环并没有停下来,证明修改的值未被该子线程读取
*/
}
}
Volatile
Volatile 是Java虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性(不可分割)
- 静止指令重排
如果不加Synchronized和lock,如何保证原子性

使用原子类解决解决原子性问题
Unsafe是一个很特殊的存在
这些类的底层都直接和操作系统挂钩,在内存中修改值,所以比Lock高效

什么是指令重排
写的程序,计算机并不是按照写的那样进行执行
源码 -> 编译器优化的重排 -> 指令并行也可能重排 -> 内存系统也会重排 -> 执行
处理器在进行指令重排的时候会考虑数据之间的依赖性
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
执行顺序: 期望 -> 1234 可能为2134 1324
不可能: 4132
前提条件 a b x y = 0
| 线程A | 线程B |
|---|---|
| x = a | y = b |
| b = 1 | a = 2 |
正常结果: x = 0, y = 0
进行指令重排
| 线程A | 线程B |
|---|---|
| b = 1 | a = 2 |
| x = a | y = b |
错误结果: x = 2, y = 1
volatile 可以避免指令重排
内存屏障 -> CPU指令(静止上下文指令顺序交换)
作用:
- 保证特定的操作顺序
- 可以保证某些变量的内存可见性
利用这些特性volatile实现了可见性

可见性
package javaSEStudy.JUC.JUCDemo.volatile_;
import java.util.concurrent.TimeUnit;
//Volatile 详解
public class Volatile_ {
// 定义一个可操作变量,加volatile保证可见性
private volatile static int num = 0;
public static void main(String[] args) {
// 制定一个线程,上面是main线程
// 加入volatile关键字后死循环结束,证明可见
new Thread(() -> {
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
/**
* 问题?
* while死循环,下面已经将num值更改,但是循环并没有停下来,证明修改的值未被该子线程读取
*/
}
}
不保证原子性
线程在执行任务的时候不可被打扰,也不可被分割,要么同时成功,要么同时失败
package javaSEStudy.JUC.JUCDemo.volatile_;
import java.util.concurrent.atomic.AtomicInteger;
// Volatile 不保证原子性
public class VolatileAtomicity {
// 即使加入了Volatile也很难到达两万
// private volatile static int num = 0;
// 使用原子类解决原子性问题
private volatile static AtomicInteger num = new AtomicInteger();
// 加入关键字Synchronized可保证达到两万
public static void add(){
// 这不是一个原子性操作
// num++;
// AtomicInteger 的+1 方法,底层使用CAS
num.getAndIncrement();
}
public static void main(String[] args) {
// 理论上num结果为2万,但是由于不保证原子性,很难达到两万
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
// 会有两条线程默认执行 main gc
// 倘若下面可以执行,证明上面线程未执行完毕
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " :" + num);
}
}
单例模式
饿汉式
容易导致资源浪费
package javaSEStudy.JUC.JUCDemo.single;
/**
* 饿汉式
* 一上来就将对象加载
*/
public class Hungry {
// 一上来就将对象全部加载
// 此处内存空间未被利用,导致内存空间浪费
private byte[] byte1 = new byte[1024];
private byte[] byte2 = new byte[1024];
private byte[] byte3 = new byte[1024];
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
private static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
单线程ok,但是多线程需要加锁,双重(锁+volatile)检测机制,但是可以使用反射破坏
加锁也可以防止反射破坏,但是两个对象都是用反射获取,,使用信号灯去防止破坏,但是也可以使用反射去获取信号灯的值再去破坏单例模式
package javaSEStudy.JUC.JUCDemo.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 懒汉式
* 用的时候再去加载
*/
public class Lazy {
// 设置一个红绿灯,避免下面两个反射去破坏
private static boolean flag = false;
private Lazy() {
System.out.println(Thread.currentThread().getName() + " OK");
// 加锁可以防止反反射破坏
// 加入信号灯防止两个反射破坏
synchronized (Lazy.class) {
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("不要使用反射破坏");
}
// if (lazy!=null) {
// throw new RuntimeException("不要使用反射破坏");
// }
}
}
private volatile static Lazy lazy;
// 单线程下情况ok
// public static Lazy getInstance(){
// if(lazy == null){
// lazy = new Lazy();
// }
// return lazy;
// }
// 多线程需要加锁
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
// 不是一个原子性操作
/**
* 指令重排可能导致132顺序执行,所以上面需要加入volatile
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向内存空间
*/
lazy = new Lazy();
}
}
}
return lazy;
}
// 多线程并发 可以发现多线程情况下导致线程发生异常
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// for (int i = 1; i <= 100; i++) {
// new Thread(() -> {
// Lazy.getInstance();
// }).start();
// }
// 获取型号灯,去改变信号灯的值,就可以进行破解
Field flag = Lazy.class.getDeclaredField("flag");
// 将私有关键字进行公有
flag.setAccessible(true);
// 使用反射破坏单例模式
// Lazy instance = Lazy.getInstance();
// 加了null,表明获取的是无参构造器
Constructor<Lazy> dc = Lazy.class.getDeclaredConstructor(null);
// 使用反射获取对象,无视private,
dc.setAccessible(true);
// 不获取getInstance,都使用反射去破坏,如下面new出来,即可无视构造器的锁
Lazy lazy1 = dc.newInstance();
// 将信号灯值改变
flag.set(lazy1, false);
Lazy lazy2 = dc.newInstance();
// 发现空间地址号不一致,已被改变
System.out.println(lazy1);
System.out.println(lazy2);
}
}
静态内部类
使用静态内部类去实现单例模式
package javaSEStudy.JUC.JUCDemo.single;
// 静态内部类
// 也是不安全的,存在反射机制
public class Holder {
// 构造器私有
private Holder() {
}
public static Holder getInstance(){
return InnerHolder.HOLDER;
}
public static class InnerHolder{
private final static Holder HOLDER = new Holder();
}
public static void main(String[] args) {
}
}
枚举类
单例不安全,使用枚举类去防止反射破坏
注意idea可能会骗人,展示的是无参构造,实际上是有参构造
使用jad实现反编译查看源码
package javaSEStudy.JUC.JUCDemo.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 使用枚举类去避免反射破坏
* 本身也是一个Class类
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
// 测试上面的INSTANCE是否能被拿到且是同一个对象
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// EnumSingle instance1 = EnumSingle.INSTANCE;
// EnumSingle instance2 = EnumSingle.INSTANCE;
//
// // 输出true,证明上面那两个是同一个对象
// System.out.println(instance1 == instance2);
// 如果我们使用反射区破坏该单例模式
// 枚举类的源码会发生报错,指出不能使用反射破坏枚举
EnumSingle instance1 = EnumSingle.INSTANCE;
// 查看idea的out文件夹,说这是个无参构造器,但是程序运行却不是无参构造器,证明idea出错
// 下面这个报错指没有无参构造器
// NoSuchMethodException: javaSEStudy.JUC.JUCDemo.single.EnumSingle.<init>()
// 使用jad工具,发现是一个String和int两个参数的构造器
// 该报错信息才是正确报错
// IllegalArgumentException: Cannot reflectively create enum objects
Constructor<EnumSingle> dc = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
// 私有改公有
dc.setAccessible(true);
EnumSingle instance2 = dc.newInstance();
// 使用反射创建对象去比较两个是否是同一个对象
System.out.println(instance1==instance2);
}
}
深入理解CAS
什么是CAS?
CPU操作的原语
一句话概括:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,就执行操作 ! 如果不是就一直循环!
优点:操作自带原子性
缺点:
- 底层是自旋锁,循环会浪费时间
- 一次性只能保证一个共享变量的原子性
- ABA问题
package javaSEStudy.JUC.JUCDemo.cas;
import java.util.concurrent.atomic.AtomicInteger;
// CAS详解
public class CAS_ {
// CAS 是CPU的并发原语
public static void main(String[] args) {
// 先实现Atomic 原子类 可以给定初始值
AtomicInteger ai = new AtomicInteger(1024);
// CAS就是compareAndSet的缩写,比较并交换 参数:期望 新值
// public final boolean compareAndSet(int expectedValue, int newValue)
// 如果等于我期望的值,就将其换为新值,否则不更新
ai.compareAndSet(1024, 2048);
// 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
System.out.println(ai.get());
}
}
Unsafe类

Java无法操作内存,但是有个后门 Unsafe类可以去操作内存
上面的valueOffset 是内存地址偏移值
CAS的+1操作


CAS的ABA问题(狸猫换太子)

两条线程去执行操作,B快于A,先将1修改为3,又将3改为1,但是此时A不知道这个1已经是被修改过的
package javaSEStudy.JUC.JUCDemo.cas;
import java.util.concurrent.atomic.AtomicInteger;
// CAS详解
public class CAS_ {
// CAS 是CPU的并发原语
public static void main(String[] args) {
// 先实现Atomic 原子类 可以给定初始值
AtomicInteger ai = new AtomicInteger(1024);
// 平时写的SQL语句 : 乐观锁!
// CAS就是compareAndSet的缩写,比较并交换 参数:期望 新值
// public final boolean compareAndSet(int expectedValue, int newValue)
// 如果等于我期望的值,就将其换为新值,否则不更新
// --------------捣乱的线程----------------
ai.compareAndSet(1024, 2048);
// 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
System.out.println(ai.get());
ai.compareAndSet(2048, 1024);
// 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
System.out.println(ai.get());
// --------------期望的线程----------------
ai.compareAndSet(1024, 9999);
// 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
System.out.println(ai.get());
}
}
原子引用
本质:带版本号的原子操作

ps:Integer使用了对象缓存机制,默认范围是-128 - 127,推荐使用静态工厂方法ValueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配内存空间
对应思想:乐观锁
解决ABA问题
package javaSEStudy.JUC.JUCDemo.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
// AtomicStampedReference 详解
public class AtomicStampedReference_ {
public static void main(String[] args) {
/**
* AtomicStampedReference
* 如果泛型是一个包装类,注意对象的引用问题
*
* 正常在业务中 <> 存放的是user这种对象
*/
// 第一个参数为初始值,第二个相当于时间戳
// 只要修改过值,时间戳+1
AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
// 执行时先获取版本号
System.out.println("A1 -> " + asr.getStamp());
// 使用sleep,保证拿到的版本号是同一个
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 拿到版本号后将其+1
System.out.println(asr.compareAndSet(1, 2,
asr.getStamp(), asr.getStamp() + 1));
System.out.println("A2 -> " + asr.getStamp());
// 拿到版本号后将其+1
System.out.println(asr.compareAndSet(2, 1,
asr.getStamp(), asr.getStamp() + 1));
System.out.println("A3 -> " + asr.getStamp());
}, "A").start();
// 与乐观锁原理相同
new Thread(() -> {
// 执行时先获取版本号
int stamp = asr.getStamp();
System.out.println("B1 -> " + stamp);
// 使用sleep,保证拿到的版本号是同一个
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(asr.compareAndSet(1, 9,
stamp, stamp + 1));
System.out.println("B2 -> " + asr.getStamp());
}, "B").start();
}
}
各种锁的理解
公平锁与非公平锁
公平锁:非常公平,不能插队,讲究先来后到
非公平锁:不公平,允许插队,(默认都是非公平的)
// 默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 改为公平锁
new ReentrantLock(true)
// 源码
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁
可重入锁 也叫递归锁

package javaSEStudy.JUC.JUCDemo.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁
* 一定是A走完了才是B
* 即使在第二个方法中也有锁,但是是在一个锁的里面,拿到外面的锁后,直接自动拿到里面的锁
*/
public class Re_entrant_Lock {
public static void main(String[] args) {
// 使用Synchronized
Phone1 phone1 = new Phone1();
new Thread(() -> {
phone1.sms();
},"A").start();
new Thread(() -> {
phone1.sms();
},"B").start();
}
}
// 使用Synchronized
class Phone1{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+" sms");
// call 方法也有锁
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+" call");
}
}
class Phone2{
// 锁必须配队,否则会死里面
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
try {
// 先拿到外面的lock,解了之后,在拿call的锁
System.out.println(Thread.currentThread().getName()+" sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" sms");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
自旋锁
spinLock

我们来自定义一个自旋锁
package javaSEStudy.JUC.JUCDemo.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁详解
* 下面栗子,只有当A自旋完毕,才会释放锁,B才能释放
*/
public class SpinLock {
public static void main(String[] args) throws InterruptedException {
SpinLock lock = new SpinLock();
new Thread(() -> {
lock.MyLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.MyUnlock();
}
}, "A").start();
// 保证A获取到锁
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.MyLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.MyUnlock();
}
}, "B").start();
}
// int 0
// Thread null
AtomicReference<Thread> ar = new AtomicReference();
// 加锁
public void MyLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " MyLock");
// 自旋锁
while (!ar.compareAndSet(null, thread)) {
}
}
// 解锁
public void MyUnlock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " MyUnlock");
ar.compareAndSet(thread, null);
}
}
死锁
什么是死锁

死锁代码
package javaSEStudy.JUC.JUCDemo.lock;
import java.util.concurrent.TimeUnit;
// 死锁
public class Deadlock {
public static void main(String[] args) {
String LockA = "LockA";
String LockB = "LockB";
new Thread(new MyThread(LockA, LockB),"T1").start();
new Thread(new MyThread(LockB, LockA),"T2").start();
}
}
class MyThread implements Runnable {
private String LockA;
private String LockB;
public MyThread(String lockA, String lockB) {
LockA = lockA;
LockB = lockB;
}
@Override
public void run() {
synchronized (LockA) {
System.out.println(Thread.currentThread().getName()+" lock:"+LockA+" --> get "+LockB);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockB) {
System.out.println(Thread.currentThread().getName()+" lock:"+LockB+" --> get "+LockA);
}
}
}
}
如何解决死锁
- 使用jps定位进程号 命令:
jps -l // 查看全部活着进程

- 使用jstack查看进程信息 命令:
jstack 进程号


显示找到一个死锁,哪个进程持有哪个锁,等待哪个锁
浙公网安备 33010602011771号