多线程进阶=>JUC并发编程
1. 线程与进程
并发、并行
并发编程:并发、并行
并发(多线程操作同一个资源)
- CPU一核,模拟出来多个线程,快速交替
并行
- 多核CPU,多个线程同时执行,线程池
2. lock 锁
synchronized 与 lock 区别
- synchronized 内置的 java 关键字, Lock 是一个类
- synchronized 无法获取锁的状态,Lock 可以判断是否获取到了锁
- synchronized 会自动释放锁,lock 必须手动释放锁
- synchronized 可重入锁,不可以中断,非公平; lock,可重入锁,可以判断锁,非公平(可以自己设置)
- synchronized 适合少量的代码同步问题,lock 适合锁大量的同步代码。
锁是什么,如何判断锁
3.生产者消费者
package 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
* @author guojingbo
* @date 2021-09-24 15:56
* @param null
* @return
*/
public class pc {
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{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try {
//业务->判断->执行->通知
while (number!=1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAAA");
//唤醒指定的人,B
number = 2;
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()+"=>BBBBBBBBB");
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()+"=>CCCCCCCCCCCC");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.8锁现象
如何判断锁的是谁!永远知道什么是锁,锁到底锁的是谁!
深刻理解我们的锁
package JUC.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁就是关于锁的八个问题
* 1. 标准问题,两个线程先打印还是发短信? 1/发短信 2/打电话 第一个线程先开启
* 2.sendSms延迟 4 秒,两个线程先打印还是发短信? 1/发短信 2/打电话 两个线程先快先执行
* 3. 先打印发短信还是 hello 1/hello 2/发短信 hello 没有加 synchronized 关键字
* 4. 两个对象第一个 sendSms 第二个 call 哪个先 1.打电话 2、发消息 因为两个对象锁,并且打电话执行的更快
* 5. 两个静态方法两个对象顺序一致哪个先 1.发消息 2.打电话 静态方法抢类锁,就算两个对象也还是一个类锁
* 6.1个静态同步方法普通的同步方法,一个对象 1\打电话 2\发短信 因为抢的锁不同一个抢类锁一个抢对象锁
* @author guojingbo
* @date 2021-09-24 16:34
*
* @return
*/
public class Phone {
public static void main(String[] args) {
int[] ints = new int[128];
String a ="0";
char c = a.charAt(0);
ints[a.charAt(0)]++;
// ints[a.charAt(0)]=ints[a.charAt(0)]+1;
for (int anInt : ints) {
System.out.println(anInt);
}
System.out.println(c);
Test1 test = new Test1();
new Thread(()->{
test.sendSms();
}).start();
//捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
test.call();
}).start();
}
}
class Test1{
//synchronized 锁的对象是锁的调用者
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
5.集合类不安全
package arithemetic;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
// ConcurrentModificationException 并发修改异常
/*
解决方案
1. List<String> list = new Vector<>();
2. List<String> list = Collections.synchronizedList(new ArrayList<>());
3. List<String> list = new CopyOnWriteArrayList<>();
* */
//copyOnWrite 写入时复制 COW 计算机设计领域的一种忧化策略
//多个线程调用的时候,list 读取的时候,固定的,写入覆盖
//在写入的时候避免覆盖,造成数据问题
public static void main(String[] args) throws IOException, InterruptedException {
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();
}
}
}
学习策略:1.先会用,2.货比三家,3.研究源码
package JUC;
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();
* @author guojingbo
* @date 2021-09-24 19:58
* @return
*/
public class setTest {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
ConcurrentHashMap 不能接受 null的 key 和 null 的 value 会抛出空指针异常
6.Callable(简单)
- 可以有返回值
- 可以抛出异常
- 方法不同,run(),call()
new Thread(new Runnable).start()
new Thread(new FutureTask()).start()
new Thread(new FutureTask(Callable)).start
package JUC.coallable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class callableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread()).start();
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被放入缓存提高效率
String o =(String) futureTask.get();//这个 get 方法可能会产生阻塞,一般将其放在最后
System.out.println(o);
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
return "234";
}
}
细节:
- 有结果
- 可能需要等待,会阻塞
7.常用的辅助类(重要)
7.1 CountDownLatch
package JUC.add;
import java.util.concurrent.CountDownLatch;
// 计数器
public class countDownLatchDemo {
public static void main(String[] args) {
// 必须要执行的任务的时候使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Get Out");
countDownLatch.countDown();//数量减一
},String.valueOf(i)).start();
}
try {
countDownLatch.await();//等待计数器归零,然后再向下执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Close Door");
}
}
减法计数器
每次有线程调用 countDown
数量减一,假设计数器变为0 ,countDownLatch.await
调用这个函数后会判断是等于 0 如果不等于0会被阻塞,如果等于 0 会继续执行
指定线程执行完毕,再执行操作
7.2 CyclicBarrier
加法计数器
package JUC.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 集齐七颗龙珠召唤神龙
* @author guojingbo
* @date 2021-09-25 09:29
* @param args
* @return void
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
CyclicBarrier
执行达到指定线程数再执行操作
7.3 Semaphore
Semaphore: 同一时间只能有指定数量个得到线程
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量:限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; 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();
}
}).start();
}
}
}
semaphore.acquire()
获得,假设已经满了,等待,直到有资源被释放
semaphore.release()
释放,将当前占有的信号量释放
作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!
8.读写锁
ReadWriteLock
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 一次能被多个线程占有
* ReadWriteLock
* 读-读 可以
* 写-读 不可以
* 写-写 不可以
* @author guojingbo
* @date 2021-09-25 10:32
* @return
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp+"",temp+"");
}, String.valueOf(temp)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp+"");
}, String.valueOf(temp)).start();
}
}
}
class MyCacheLock {
private Map<String, String> resource = new HashMap<String, String>();
//读写锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//写
public void put(String key, String value) {
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName() + "写入" + key);
resource.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);
resource.get(key);
System.out.println(Thread.currentThread().getName() + "读取完毕");
}catch (Exception e){
e.printStackTrace();
}finally {
}
}
}
class MyCache {
private Map<String, String> resource = new HashMap<String, String>();
//写
public void put(String key, String value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
resource.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完毕");
}
//读
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
resource.get(key);
System.out.println(Thread.currentThread().getName() + "读取完毕");
}
}
8.阻塞队列
如果队列满了会阻塞
如果队列是空的,必须阻塞生产
什么情况我们会使用阻塞队列:多线程并发处理,线程池!
学会使用队列
添加、移除
四组API
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(内容,时间,单位)offer("D",2,TimeUnit.SECONDS) |
移除 | remove | pool | take | pool (时间,单位) |
判断队列头 | element | peek |
/*抛出异常
* @author guojingbo
* @date 2021-09-25 11:31
*
* @return void
*/
public static void test1(){
//队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
}
}
SynchronousQueue
同步队列
没有容量,进去一个元素必须等待取出来之后才能再往里面放一个元素!
和其他的 BlockingQueue 不一样,SynchronousQueue 不存储元素 put 了一个元素,必须从里面先 take 取出来,否则不能再 Put 值
9、线程池
线程池:三大方法、7大参数、4种拒绝策略
线程池的好处
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程复用、可以控制最大并发数、管理线程
七大参数
public ThreadPoolExecutor(int corePoolSize, //核心线程池的大小
int maximumPoolSize, //最大核心池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue,// 阻塞队列
RejectedExecutionHandler handler) { //拒绝策略
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory()//线程工厂,一般不用动
, handler);
}
四种策略
/**
* @author guojingbo
* @date 2021-09-25 15:19
* @return
* AbortPolicy() //满了如果还有人进来,就不处理这个人的,并且抛出异常
* CallerRunsPolicy() //哪来的去哪里
* DiscardPolicy() //队列满了,不会抛出异常,丢弃任务
* DiscardOldestPolicy()//队列满了,尝试和最早的竞争,也不会抛出异常
*/
小结与拓展
调优
/*
最大线程该如何定义
1. cpu 密集型 8条线程同时执行,保证 cpu 效率最高 Runtime.getRuntime().availableProcessors()
2. io 密集型 >判断你的程序中十分耗费 io 的线程 一般为这个数量的两倍
// 一个程序15个大型任务
* */
四大函数式接口
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//foreach
-
Consumer : 消费型接口
- void accept(T t);
-
Supperlier:借给型接口
- T get() 经过一系列的处理返回一个与原来一样类型的变量
-
Function<T,R> : 函数型接口
- R apply(T t) 输入T 返回 R
-
Predicate: 断定型接口
- boolean test(T t) 有一个输入参数,返回值只能是布尔值
10. ForkJoin
什么是 ForkJoin
ForkJoin 在JDK 1.7,并行执行任务来提高效率的,大数据量!
大数据:Map Reduce(大任务拆分为小任务)
使用Stream 流
LongStream.rangeClosed(0,10_0000_0000L).parallel().reduce(0,Long::sum);
11. 异步回调
Future 是一个接口 CompletableFuture
是其一个实现类
//异步回调
// CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsYnc");
// });
// future.get() 有返回值的异步回调
CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"runAsYnc");
return 1024;
});
uCompletableFuture.whenComplete((t,u)->{
System.out.println(t+"=>");// 正常的返回结果
}).exceptionally((e)->{
String message = e.getMessage();// 不正常的返回结果
return 404;
});
12.JMM
淡淡对 Volatile 的理解
Volatile 是 Java 提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM
JMM 是java 内存模型,不存在的东西,一个概念!约定
关于JMM 一些同步的约定
- 线程解锁前,必须将共享变量立刻刷回主存
- 线程解锁前,必须读取主存中的最新值到工作内存中
- 加锁与解锁是同一把锁
2、不保证原子性
原子性:不可分割
线程A 在执行的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
如果不加 lock 与 sychronized 怎么保证原子性
使用原子类解决这个原子性问题,这些类底层都直接与操作系统挂钩!在内存中修改值。
3、指令重排
什么是指令重排
源代码->编译器忧化的重排->指令并行也可能有一个重排->内存系统也会重排->执行
处理器在指令重排的时候会考虑指令之间的依赖性
volatile 可以避免指令重排
内存屏障。CPU指令,作用:
- 保证特定的操作执行顺序
- 保证一些变量内存的可见性
13、彻底玩转单例模式
饿汉式 DCL懒汉式 ,深究!
饿汉式
package single;
public class Hungry {
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
package single;
public class LazyMan {
private LazyMan(){}
private volatile static LazyMan lazyMan;
// 双重检测 DCL 懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
/**
* 1. 分配内存空间
* 2.执行构造方法
* 3.将这个对象指向这个空间
* 1->2->3
* 1->3->2
* 所以我们需要将这个对象加上 volatitle 保证指令不被重排
*/
}
}
}
return lazyMan;
}
// 多线程并发
}
enum
package single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSigle {
INSTANCE;
public EnumSigle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSigle instance = EnumSigle.INSTANCE;
Constructor<EnumSigle> declaredConstructor = EnumSigle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSigle instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance);
}
}
14.深入理解CAS
什么是CAS
compareAndSet
:比较并交换
java 无法操作内存 Java 可以调用 c++ c++可以调用内存,Unsafe 类就是java 的后门,通过这个类操作内存
CAS 是CPU 的并发原语
unSafe 类
CAS ABA问题(狸猫换太子)
CAS:比较当前工作内存中的值与主内存中的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环!
缺点:
- 循环会耗时
- 一次只能保证一个共享变量的原子性
- ABA 问题
15.原子引用
对应的思想:乐观锁
带版本号的原子操作!
public class CASDemo {
//如果泛型是一个包装类,注意对象的引用问题
public static void main(String[] args) {
AtomicStampedReference<Object> atomicStampedReference = new AtomicStampedReference<>(1, 1);
atomicStampedReference.compareAndSet
(2020, 2022, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
}
}
16、各种锁的理解
1. 公平锁、非公平锁
公平锁:不能够插队,必须先来后到
非公平锁: 可以插队,cpu说的算
2.可重入锁
拿到外面的锁,也就拿到了里面的锁
3.自旋锁
package JUC.spinlock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*
* @author guojingbo
* @date 2021-09-27 14:36
* @return
*/
public class SplinlockDemo {
AtomicReference<Thread>atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>lock");
// 自旋锁
while (atomicReference.compareAndSet(null,thread)){
}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"====>MyUnlock");
atomicReference.compareAndSet(thread,null);
}
}class Test{
public static void main(String[] args) throws InterruptedException {
SplinlockDemo lock = new SplinlockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"T2").start();
}
}
4.死锁
解决
-
使用
jps -l
定位进程号
-
使用
jstack
进程号 找到死锁问题