回顾
-
JUC - java.util.concurrent
-
企业开发实现多线程一般实现callable接口
-
java真的可以开启线程吗?
不可以,底层调用的本地(native)方法,底层是c++, java运行在虚拟机上,无法直接操作硬件
-
并发并行
- 并发(多线程操作同一个资源)
- CPU 一核,模拟出来多条线程,天下武功,唯快不破,快速交替
- 并行(多个人一起行走)
- CPU 多核,多个线程可以同时执行
- 并发(多线程操作同一个资源)
-
并发编程的本质:充分利用CPU的资源
-
线程有几个状态,Thread.State,源码复制来的!
//新生 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待,死等 WAITING, //超时等待,过时不候 TIMED_WAITING, //终止 TERMINATED;
wait/sleep的区别
-
来自不同的类
- wait -> Object
- sleep -> Thread
-
释放锁
- wait释放锁
- sleep不会释放锁,抱着锁睡觉了
-
使用的范围不同
- wait必须在同步代码块中
- sleep可以在任何地方睡
-
是否需要捕获异常
- wait不需要捕获异常
- sleep必须要捕获异常
企业级开发
synchronized锁
package com.juc; /** * 真正的多线程开发,公司中的开发,降低耦合性 * 线程就是一个单独的资源类,没有任何附属的操作 * 1、属性、方法 */ public class Runnable { public static void main(String[] args) { Ticket ticket = new Ticket(); //@FunctionalInterface 函数式接口,jdk1.8 lambda表达式(参数)->{代码} new Thread(()->{ for (int i = 0; i < 60; i++) { ticket.sale(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 60; i++) { ticket.sale(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 60; i++) { ticket.sale(); } },"C").start(); } } class Ticket{ private int ticketNum=50; //synchronized 本质:队列,锁 public synchronized void sale(){ if (ticketNum > 0) { System.out.println("卖出了"+(ticketNum--)+"票,剩余"+ticketNum+"票"); } } }
Lock锁
-
Lock接口(JDK1.8文档)
Lock l = ...; l.lock(); //加锁 try { // access the resource protected by this lock } finally { l.unlock(); } //解锁
-
Lock实现类
- ReentrantLock 可重入锁,普通锁
- ReentrantReadWriteLock.ReadLock 读锁
- ReentrantReadWriteLock.WriteLock 写锁
-
ReentrantLock 构造
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1ZKoNLH-1680884771787)(JUC.assets/image-20230203133256516.png)]
公平锁:十分公平,可以先来后到 3h 3s,3h排在3s前面很不公平
非公平锁:十分不公平,可以插队(默认)
package com.juc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicket2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
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 Ticket2 {
private int tickNum=50;
Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); //加锁
try {
//业务代码
if (tickNum > 0) {
System.out.println("卖出了" + (tickNum--) + "票,剩余" + tickNum + "票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); //解锁
}
}
}
Synchronized和Lock的区别
-
Synchronized是Java内置的关键字,Lock是一个Java类
-
Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
-
Synchronized会自动释放锁(用Synchronized只是加了这个关键字,都是自动的),Lock必须要手动释放锁,如果不释放,死锁
-
Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁就不一定会等待下去
Lock lock = new ReentrantLock(); lock.tryLock(); //尝试获取锁
-
Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自已设置)
-
Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
线程通信,生产者消费者问题
线程通信,生产者和消费者问题,Synchronized版
package com.juc;
/**
* A B 两个线程交替执行num加一减一
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data{
private int num=0;
//num+1
public synchronized void increment() throws InterruptedException {
if (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我+1完毕了
this.notifyAll();
}
//num-1
public synchronized void decrement() throws InterruptedException {
if (num == 0) {
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我-1完毕了
this.notifyAll();
}
}
问题存在!如果有A,B,C,D四个线程?虚假唤醒
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x7pKQ64Y-1680884771787)(JUC.assets/image-20230203145056979.png)]
if 换成 while 判断
package com.juc;
/**
* A B C D四个线程交替执行num加一减一
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int num=0;
//+1
public synchronized void increment() throws InterruptedException {
while (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (num == 0) {
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我-1完毕了
this.notifyAll();
}
}
生产者消费者问题之Lock版
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j926jBCm-1680884771788)(JUC.assets/image-20230203181611539.png)]
通过Lock找到Condition
Lock lock = new ReentrantLock();
Condition condition=lock.newCondition();
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* A B C D四个线程交替执行num加一减一
*/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
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 Data2{
private int num=0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1
public void increment() {
lock.lock(); //加锁
try {
//业务代码
while (num != 0) {
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() {
lock.lock(); //加锁
try {
while (num == 0) {
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我-1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition精准的通知和唤醒
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NR4UpbWc-1680884771788)(JUC.assets/image-20230203151611366.png)]
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* A线程执行完调用B线程,B线程执行完调用C线程,C线程执行完调用A线程
*/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{ //资源类
Lock lock = new ReentrantLock();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
int number=1;
public void printA() {
lock.lock();
try {
//业务代码,判断->执行->通知
while (number != 1) {
condition1.await();
}
number=2;
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
//唤醒B线程
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number != 2) {
condition2.await();
}
number = 3;
System.out.println(Thread.currentThread().getName() + "=>BBBBBB");
//唤醒C线程
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number != 3) {
condition3.await();
}
number = 1;
System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCC");
//唤醒A线程
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
锁是什么,如何判断锁的是谁?
疑问:线程执行了才能拿到锁吧!两个线程启动后谁先执行不是取决于CPU调度吗,phone.sendSms()代码顺序在前面,它就先执行了?
package com.juc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 1、标准情况下,两个线程先打印发短信还是打电话? 发短信
* 2、sendSms延迟四秒,两个线程先打印发短信还是打电话? 发短信
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}).start();
//休眠一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void sendSms() {
//休眠4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
package com.juc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 3、增加了一个普通方法hello,先打印输出发短信还是hello? hello
* 4、两个对象,两个同步方法,先打印输出发短信还是打电话? 打电话
*/
public class Test2 {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone.sendSms();
}).start();
//休眠一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}).start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void sendSms() {
//休眠4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
//这里没有锁,不是同步方法,不受锁的影响
public void hello() {
System.out.println("hello");
}
}
package com.juc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 5、两个静态的同步方法,只有一个对象,先打印发短信还是打电话? 发短信
* 5、两个对象,两个静态的同步方法,,先打印发短信还是打电话? 发短信
*/
public class Test3 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone3 phone = new Phone3();
Phone3 phone3 = new Phone3();
new Thread(() -> {
phone.sendSms();
}).start();
//休眠一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone3.call();
}).start();
}
}
class Phone3{
//synchronized 锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁执行
//静态方法,锁的是Class,Class只有一个
public static synchronized void sendSms() {
//休眠4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
package com.juc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 一个静态的同步方法,一个普通的同步方法,一个对象,先打印发短信还是打电话? 打电话
* 一个静态的同步方法,一个普通的同步方法,两个对象,先打印发短信还是打电话? 打电话
*/
public class Test4 {
public static void main(String[] args) {
//虽然有两个对象,但是一个方法锁的是类,一个方法锁的是对象,还是两把锁,互不影响
Phone4 phone = new Phone4();
Phone4 phone4 = new Phone4();
new Thread(() -> {
phone.sendSms();
}).start();
//休眠一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone4.call();
}).start();
}
}
class Phone4{
//静态方法,锁的是Class
public static synchronized void sendSms() {
//休眠4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//锁的是对象,两个方法不是同一个锁,不需要等待前一个方法执行完
public synchronized void call() {
System.out.println("打电话");
}
}
小结
new this 具体的一个手机
static Class 唯一的一个模板
集合类不安全
List不安全
package com.juc.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* ConcurrentModificationException 并发修改异常
*/
public class ListTest {
public static void main(String[] args) {
/**
* 并发下ArrayList不安全
* 解决方案
* 1、 List<String> list = new Vector<>();
* 2、 List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3、 List<String> list = new CopyOnWriteArrayList<>();
* CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
* 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
* 在写入的时候避免覆盖,造成数据问题
* CopyOnWriteArrayList 比 Vector 厉害在哪?
* Vector底层用的Synchronized效率低,CopyOnWriteArrayList底层用的写入时复制+Lock锁
*/
// List<String> list = new ArrayList<>();
// List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
System.out.println(i);
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList源码
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
Vector源码
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
Set不安全
package com.juc.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 TestSet {
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 = 0; 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<>();
}
//add set 本质就是 map key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object(); //不变的值
hashmap不安全
package com.juc.unsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 同理
* 1、Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
* 2、 Map<String, String> map = new ConcurrentHashMap<>();
*/
public class TestHashMap {
public static void main(String[] args) {
// Map<String, String> map = new HashMap<>();
// Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
Callable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4SVl8kY-1680884771789)(JUC.assets/image-20230204150737121.png)]
1、有返回值
2、可以抛出异常
3、方法不同,run()/ call()
代码使用
package com.juc.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test1 {
public static void main(String[] args) {
//通过Runnable的实现类->FutureTask来使用Callable
//通过FutureTask获取返回值
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<>()).start();
// new Thread(new FutureTask<>(new Callable())).start();
//适配类
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start(); //结果会被缓存,提高效率
try {
//获取返回结果,可能会产生阻塞,把它放到最后,或者使用异步通信来处理
Integer o = (Integer) futureTask.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call");
return 1024;
}
}
细节:
1、结果会被缓存
2、获取返回结果,可能会产生阻塞
常用的辅助类
CountDownLatch ([lætʃ])数量,减少,门闩
package com.juc.count;
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch
* 减法计数器,有初始数值,每个线程执行完数量减1
* 用来保证所有线程都执行完毕,再向下执行
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "走了");
countDownLatch.countDown(); //数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,然后再向下执行
System.out.println("都走了,关门了");
}
}
CyclicBarrier ([ˈsaɪklɪk] [ˈbæriə®]),周期的,难以逾越的数量
package com.juc.count;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 加法计数器
*/
public class Demo2 {
public static void main(String[] args) {
//召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("集齐七颗龙珠,召唤神龙成功!");
});
for (int i = 1; i <= 7; i++) {
final int num = i;
//lambda操作不到 i
new Thread(() -> {
System.out.println("集齐第" + num + "个龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
Semaphore ([ˈseməfɔː®]),信号量,信号标,发信号
package com.juc.count;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 规定初始线程数量
* 在这个线程数量区间内,acquire 得到,release释放
*/
public class Demo3 {
public static void main(String[] args) {
//线程数量,模拟停车位,限流
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire(); //得到
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release(); //释放
}
}, String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire():获得,假设已经满了,等待,直到有被释放的为止
semaphore.release():释放,会将当前的信号量释放+1,然后唤醒等待的线程
作用:多个共享资源互斥的使用;并发限流,控制最大的线程数
读写锁 ReadWriteLock
package com.juc.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* * ReadWriteLock 读写锁,写只可以同时一个线程写,读可以被同时多个线程读
*/
public class Demo1 {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
for (int i = 1; i <= 10; i++) {
final int tmp=i;
new Thread(()->{
myCache.put(String.valueOf(tmp), tmp);
},String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int tmp=i;
new Thread(()->{
myCache.get(String.valueOf(tmp));
},String.valueOf(i)).start();
}
}
}
class MyCacheLock{
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key,Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
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) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().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);
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 = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
阻塞队列 BlockingQueue
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1GwQBSR-1680884771789)(JUC.assets/image-20230209182605033.png)]
BlockingQueue 关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fItHIaN5-1680884771790)(JUC.assets/image-20230211153102457.png)]
阻塞队列的应用
-
多线程并发
-
线程池
阻塞队列的使用
四组API
方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待 添加 add() offer() put() offer(E e, long timeout, TimeUnit unit) 移除 remove() poll() take() poll(long timeout, TimeUnit unit) 判断队列首 element() peek() - - package com.juc.bq; import java.util.concurrent.ArrayBlockingQueue; /** * 有返回值,抛出异常 */ public class Test { public static void main(String[] args) { //队列长度3 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3); System.out.println(blockingQueue.add("aa")); System.out.println(blockingQueue.add("bb")); System.out.println(blockingQueue.add("cc")); //IllegalStateException: Queue full 队列满了 再插入 抛出异常 // System.out.println(blockingQueue.add("dd")); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); //java.util.NoSuchElementException 队列空的 再取 抛出异常 // System.out.println(blockingQueue.remove()); } }
package com.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 有返回值,不抛出异常
*/
public class Test2 {
public static void main(String[] args) {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d")); //false 不抛出异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); //null 不抛出异常
}
}
package com.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 等待,阻塞(一直阻塞)
*/
public class Test3 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); //队列满了,再插入,一直阻塞
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); //队列空了,再取,一直阻塞
}
}
package com.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 等待,阻塞(超时等待)
*/
public class Test4 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// blockingQueue.offer("d",2, TimeUnit.SECONDS); //等待(阻塞)超过两秒就退出
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); //等待(阻塞)超过两秒就退出
}
}
同步队列 SynchronousQueue
使用
package com.juc.bq;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
* 和其他的BlockingQueue不一样,SynchronousQueue不存储元素
* put了一个元素,必须从里面先take出来,否则不能再put进去值
*/
public class Test5 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"插入a");
blockingQueue.put("a");
System.out.println(Thread.currentThread().getName()+"插入b");
blockingQueue.put("b");
System.out.println(Thread.currentThread().getName()+"插入c");
blockingQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
疑问:为什么存和取必须要分别在两个线程中才能执行?在同一个线程中是执行不了的
线程池
线程池:三大方法,七大参数,四种拒绝策略
池化技术
程序的本质:占用系统资源,优化资源的使用=>池化技术
线程池、内存池、对象池…创建销毁十分消耗系统资源
池化技术:事先准备好一些资源,有人要用就来我这里拿,用完之后还给我
线程池的好处:
1、降低资源的消耗
2、提高响应速度
3、方便管理
线程复用,可以控制最大并发数,管理线程
线程池三大方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4HYHsWY9-1680884771791)(JUC.assets/image-20230209172133875.png)]
package com.juc.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Executors 线程池工具类,三大方法
*
*/
public class TestPool {
public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// new Thread(() -> {
// System.out.println(Thread.currentThread().getName());
//
// }).start();
// }
//创建一个单一线程池,只有一个线程
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建一个固定大小的线程池,线程数不超过固定值
// ExecutorService threadPool = Executors.newFixedThreadPool(3);
//创建一个线程池,可创建的线程数,遇强则强,遇弱则弱
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100.; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
}
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //21亿,容易OOM
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
相同点:都调用了ThreadPoolExecutor方法,建议通过ThreadPoolExecutor创建线程池
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57QT058C-1680884771791)(JUC.assets/image-20230209172133875.png)]
七大参数
手动创建线程池
package com.juc.pool;
import java.util.concurrent.*;
/**
* 手动创建线程池,七大参数
*
*/
public class TestPool {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy() //拒绝策略,不处理,抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() //由原先的线程处理(比如主线程)
// new ThreadPoolExecutor.DiscardOldestPolicy() //不处理,不抛出异常
new ThreadPoolExecutor.DiscardPolicy() //和最早的线程竞争,不抛出异常
);
//最大承载=最大线程数+阻塞队列容量
try {
for (int i = 1; i <= 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
}
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,创建线程的,一般不用动
RejectedExecutionHandler handler //拒绝策略
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ocHX7urv-1680884771792)(JUC.assets/image-20230211200118783.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhv101oc-1680884771793)(JUC.assets/image-20230211200839389.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dT4bjIHu-1680884771793)(JUC.assets/image-20230211202106553.png)]
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy() //拒绝策略,不处理,抛出异常 new ThreadPoolExecutor.CallerRunsPolicy() //由原先的线程处理(比如主线程) new ThreadPoolExecutor.DiscardOldestPolicy() //不处理,不抛出异常 new ThreadPoolExecutor.DiscardPolicy() //和最早的线程竞争,不抛出异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C4W9BZml-1680884771793)(JUC.assets/image-20230211202808510.png)]
四大函数式接口
函数式接口:只有一个方法的接口,@FunctionalInterface
四大函数式接口:java.util.function包下的四个函数式接口
函数型接口
package com.juc.function;
import java.util.function.Function;
public class Demo1 {
public static void main(String[] args) {
// Function function=new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s;
// }
// };
Function function=(s)->{return s;};
System.out.println(function.apply("aa"));
}
}
断定型接口
package com.juc.function;
import java.util.function.Predicate;
public class Demo2 {
public static void main(String[] args) {
// Predicate predicate=new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate=(s)->{return s.isEmpty();};
System.out.println(predicate.test(""));
}
}
供给型接口
package com.juc.function;
import java.util.function.Supplier;
public class Demo3 {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "a";
// }
// };
Supplier<String> supplier=()->{return "a";};
System.out.println(supplier.get());
}
}
消费型接口
package com.juc.function;
import java.util.function.Consumer;
public class Demo4 {
public static void main(String[] args) {
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer<String> consumer=(s)->{System.out.println(s);};
consumer.accept("aa");
}
}
Stream流式计算
集合、数据库都是存数据的,计算应该交给流去做
java.util.stream.Stream
package com.juc.function;
import java.util.Arrays;
import java.util.List;
/**
* 筛选
* ID必须是偶数
* 年龄大于23
* 用户名转成大写
* 用户名字母倒序
* 只输出一个用户
*/
public class Demo5 {
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(6, "e", 25);
//lambda表达式、链式编程、函数式接口、Stream流式计算
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
users.stream()
.filter((e)->{return e.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map((u)->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
方法源码,底层都是函数式接口
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<T> sorted(Comparator<? super T> comparator);
ForkJoin
在jdk1.7出现,针对大数据量,并行执行任务,提高效率
把大任务拆分
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zxXZ2dd-1680884771794)(JUC.assets/image-20230213112959126.png)]
package com.juc.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* ForkJoin实现类
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp=10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start < temp) {
Long sum=0L;
for (Long i = start; i <=end ; i++) {
sum += i;
}
return sum;
}else{
Long middle = (start + end) / 2;
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
ForkJoinDemo task2 = new ForkJoinDemo(middle, end);
task1.fork();
task2.fork();
return task1.join() + task2.join();
}
}
}
package com.juc.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) {
// test1(); //5423
try {
// test2(); //3782
test3(); //180
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 普通实现
*/
public static void test1() {
long start = System.currentTimeMillis();
Long sum = 0L;
for (Long i = 1L; i <= 10_0000_0000L; i++) {
sum += i;
}
System.out.println(sum);
System.out.println(System.currentTimeMillis() - start);
}
/**
* ForkJoin实现
* @throws ExecutionException
* @throws InterruptedException
*/
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo forkJoinDemo = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);
Long aLong = submit.get();
System.out.println(aLong);
System.out.println(System.currentTimeMillis() - start);
}
/**
* Steam并行流实现
*/
public static void test3() {
long start = System.currentTimeMillis();
//stream并行流
long reduce = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
System.out.println(reduce);
System.out.println(System.currentTimeMillis() - start);
}
}
异步回调
java.util.concurrent.Furure 接口
package com.juc.future;
import com.sun.xml.internal.ws.util.CompletedFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 异步执行 java的ajax
*/
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1();
test2();
}
public static void test1() throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步执行,没有返回值");
});
completableFuture.get();
System.out.println("11111");
}
public static void test2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
// int i = 33 / 0;
return 1024;
});
Integer result = completableFuture.whenComplete((t, u) -> { //成功
System.out.println(t); //正常的返回值
System.out.println(u); //错误信息
}).exceptionally((e) -> { //失败
System.out.println(e.getMessage());
return 233;
}).get();
System.out.println(result);
}
}
JMM(Java内存模型)
从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。
关于JMM的一些同步的约定
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
8种操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wnlRSGj3-1680884771794)(JUC.assets/image-20230213142018770.png)]
-
lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
-
read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
-
load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
-
use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
-
assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
-
store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
-
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
-
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
问题:线程A修改了值,线程B不能及时可见
解决办法:使用Volatile
Volatile
Volatile的三个特性
- 保证可见性
- 不保证原子性
- 避免指令重排
保证可见性
package com.juc.volalitetest;
import java.util.concurrent.TimeUnit;
/**
* 问题:主线程修改了num,线程A无法及时读取到变化
* 解决:使用volatile,保证了可见性
*/
public class Demo2 {
private volatile static int num=0;
public static void main(String[] args) {
new Thread(()->{
while (num == 0) {
}
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
不保证原子性
线程执行的时候不能被打扰,也不能被分割
package com.juc.volalitetest;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 问题:num最后总是不等于2万
* volatile也不能解决,因为volatile不保证原子性
* 解决:
* 1、可以加锁
* 2、使用java.util.concurrent.atomic包下的原子类
*
*/
public class Demo3 {
// private volatile static int num=0;
private volatile static AtomicInteger num=new AtomicInteger();
public static void add() {
// num++;
num.getAndIncrement(); //+1
}
//理论上num应该等于2万
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) { //main gc
Thread.yield(); //线程礼让
}
System.out.println(Thread.currentThread().getName() + num);
}
}
指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性
int x=1; //1
int y=2; //2
x = x+5; //3
y = x*x; //4
我们所期待的是按照1234执行,但是可能执行的时候会变成:2134 1324
但是不可能是4123,数据之间有依赖
可能造成的结果:abxy这四个值都是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可以避免指令重排,利用了内存屏障
单例模式
饿汉式
package com.juc.single;
/**
* 饿汉式单例
*/
public class Hunger {
//可能会浪费空间
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private Hunger(){
}
private final static Hunger HUNGER = new Hunger();
public Hunger getInstance() {
return HUNGER;
}
}
DCL懒汉式
package com.juc.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* 懒汉式单例
*/
/**
* 道高一尺魔高一尺五,都不是最终的解决办法
*/
public class LazyMan {
private static boolean createFlag = false;
private LazyMan() {
//解决反射破坏
synchronized (LazyMan.class) {
// if (lazyMan != null) {
// throw new RuntimeException("不要试图使用反射破坏异常");
// }
if (!createFlag) {
createFlag = true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName());
}
private volatile static LazyMan lazyMan;
//多线程下有问题,不是单例
// public static LazyMan getInstance() {
// if (lazyMan == null) {
// lazyMan = new LazyMan();
// }
// return lazyMan;
// }
//双重检测锁模式,懒汉式单例, DCL懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
/**
* lazyMan = new LazyMan(); 不是一个原子性操作,这一步会有下面的操作:
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向空间
*
* 正常顺序是123
* 指令重排后可能是132,此时lazyMan不是null,但此时LazyMan还没有执行构造方法,就直接返回,会有问题,所以要加volatile,禁止指令重排
*/
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
// //多线程下有问题,不是单例
// for (int i = 0; i < 100; i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
// reflec();
reflec2();
}
//反射破坏单例模式
public static void reflec() throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//公有化构造
declaredConstructor.setAccessible(true);
LazyMan lazyMan = declaredConstructor.newInstance();
System.out.println(instance==lazyMan);
}
//反射破坏单例模式2
public static void reflec2() throws Exception {
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//公有化构造
declaredConstructor.setAccessible(true);
//调用两次newInstance,两个不是同一个对象
LazyMan lazyMan1 = declaredConstructor.newInstance();
Field createFlag = LazyMan.class.getDeclaredField("createFlag");
createFlag.setAccessible(true);
createFlag.set(lazyMan1,false);
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1==lazyMan2);
}
}
静态内部类
package com.juc.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.juc.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//枚举,本身也是一个java类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
EnumSingle instance = EnumSingle.INSTANCE;
// EnumSingle instance2 = EnumSingle.INSTANCE;
//反编译代码后,需要使用有参构造
// Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//java.lang.NoSuchMethodException
// Cannot reflectively create enum objects 不能用反射创建单例对象
System.out.println(instance);
System.out.println(instance2);
}
}
CAS
sun.misc.Unsafe类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNsH0Z7z-1680884771794)(JUC.assets/image-20230213181032321.png)]
公平锁非公平锁
Lock lock = new ReentrantLock(); //非公平锁
Lock lock2 = new ReentrantLock(true); //公平锁
可重入锁(递归锁)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EV7uvamQ-1680884771795)(JUC.assets/image-20230214125223425.png)]
synchronized锁
package com.juc.fair;
public class Demo2 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sms();
}, "A").start();
new Thread(() -> {
phone.sms();
}, "B").start();
}
}
class Phone{
public synchronized void sms() {
System.out.println(Thread.currentThread().getName()+"->sms");
call(); //这里也有锁
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"->call");
}
}
Lock锁
package com.juc.fair;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo3 {
public static void main(String[] args) {
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone2.sms();
}, "A").start();
new Thread(() -> {
phone2.sms();
}, "B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public synchronized void sms() {
lock.lock(); //细节问题:lock.lock();lock.unlock(); 锁必须配对,否则就会死在里面
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"->sms");
call(); //这里也有锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public synchronized void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"->call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
lock.unlock();
}
}
}
死锁
指进程之间互相要获得对方的锁
解决死锁
jps - l
命令看进程号jstack 进程号
找到死锁问题