JUC学习
文章目录
1、什么是JUC
源码+官方文档
JUC是 java util concurrent
面试高频问JUC~!
java.util 是Java的一个工具包
业务:普通的线程代码 Thread
Runnable: 没有返回值、效率相比于Callable 相对较低!
2、线程和进程
进程:一个程序,允许一个java程序会进程里面会出现一个java.exe;数据+代码+pcb
一个进程可以包含多个线程,至少包含一个线程!
Java默认有几个线程?2个线程! main线程、GC线程
线程:开了一个进程qq,聊天打字,消息提示(线程负责的)
对于Java而言:Thread、Runable、Callable进行开启线程的。
**JAVA真的可以开启线程吗? 开不了的!**原因Java没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
并发、并行
并发: 多线程操作同一个资源。
- CPU 只有一核,模拟出来多条线程,那么我们就可以使用CPU快速交替,来模拟多线程。
并行: 多个人并排行走。
- CPU多核,多个线程可以同时执行。
public class Test { public static void main(String[] args) { //获取cpu的核数 System.out.println(Runtime.getRuntime().availableProcessors()); } }
并发编程的本质:充分利用CPU的资源!
线程的6个状态
public enum State { //创建 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待 WAITING, //超时等待 TIMED_WAITING, //终止 TERMINATED; }
面试题:谈一谈wait和sleep区别?
区别 | wait | sleep |
操作的类 | Object | Thread |
锁的释放 | 会释放锁 | 抱着锁睡觉 |
范围 | 同步代码块中 | 任何地方 |
异常捕获 | 不需要捕获异常 | 需要捕获异常 |
3、Lock锁(重点)
synchronized锁问题
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest * @Author: 张晟睿 * @Date: 2021/9/5 14:01 * @Version: 1.0 */ //资源类 属性 + 方法 oop class Ticket{ private int num = 50; //卖票方式 synchronized 本质:队列 锁 public synchronized void sale(){ if(num > 0){ System.out.println(Thread.currentThread().getName()+ " 卖出了第"+ num +" 张票,剩余:"+ --num +" 张票"); } } } public class TicketTest { public static void main(String[] args) { //多线陈操作 //并发:多个线程操作同一个资源ticket Ticket ticket = new Ticket(); //@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式 new Thread(()->{ for (int i = 0; i < 60; i++) { ticket.sale(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 60; i++) { ticket.sale(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 60; i++) { ticket.sale(); } },"C").start(); } }
Lock接口
公平锁: 公平,必须先来后到~;
非公平锁: 不公平,可以插队;(默认为非公平锁)
使用Lock进行操作
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest2 * @Author: 张晟睿 * @Date: 2021/9/5 16:15 * @Version: 1.0 */ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *@ClassName TicketTest2 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ class Ticket2{ /* * 加锁三步 * 1.实例化lock对象 * 2.lock加锁 * 3.unlock解锁 * */ Lock l = new ReentrantLock(); private int num = 50; //卖票方式 synchronized 本质:队列 锁 public void sale(){ //加锁 l.lock(); try { //业务代码 if(num > 0){ System.out.println(Thread.currentThread().getName()+ " 卖出了第"+ num +" 张票,剩余:"+ --num +" 张票"); } } catch (Exception e) { e.printStackTrace(); } finally { //解锁 l.unlock(); } } } public class TicketTest2 { public static void main(String[] args) { //多线陈操作 //并发:多个线程操作同一个资源ticket 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(); } }
区别 | synchronized | lock |
名称 | 属于关键字 | 属于对象 |
状态 | 不可以获取锁的状态 | 可以获取锁的状态 |
锁的管理 | 自动释放锁 | 需要手动加锁以及释放锁 |
线程 | 自己抱着锁 | 等待 |
可重入锁,不可以中断的,非公平的 | 可重入的,可以判断锁,可以自己设置公平锁和非公平锁 | |
代码同步 | 适合少量的代码同步 | 适合大量的代码同步 |
4、生产者消费者问题
synchronized版
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest3 * @Author: 张晟睿 * @Date: 2021/9/5 16:35 * @Version: 1.0 */ /** *@ClassName TicketTest3 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ public class TicketTest3 { 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 number = 0; // +1操作 public synchronized void increment() throws InterruptedException { if(number != 0 ){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } // -1操作 public synchronized void decrement() throws InterruptedException{ if (number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
问题存在,A线程B线程,现在如果我有四个线程A B C D!该怎么去解决问题
if判断改为While判断就可以解决虚假唤醒的问题。
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest3 * @Author: 张晟睿 * @Date: 2021/9/5 16:35 * @Version: 1.0 */ /** *@ClassName TicketTest3 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ //线程之间的通讯问题:生产者和消费者的问题! 等待唤醒,通知唤醒 //线程交替执行 A B操作同一个资源 public class TicketTest3 { 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.decrement(); } 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 number = 0; // +1操作 public synchronized void increment() throws InterruptedException { while(number != 0 ){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } // -1操作 public synchronized void decrement() throws InterruptedException{ while (number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
JUC版本的解决A B C D多线程的问题
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: JucTest1 * @Author: 张晟睿 * @Date: 2021/9/5 19:34 * @Version: 1.0 */ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *@ClassName JucTest1 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ public class JucTest1 { 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 number = 0; //lock锁 Lock l = new ReentrantLock(); Condition condition = l.newCondition(); public void increment() { l.lock(); try { //业务 while (number!=0){ //等待操作 condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程 我+1完毕了 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } public void decrement() { l.lock(); try { //业务 while (number==0){ //等待操作 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程 我-1完毕了 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } }
Condition的优势:精准通知、唤醒的线程
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: JucTest2 * @Author: 张晟睿 * @Date: 2021/9/5 19:52 * @Version: 1.0 */ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *@ClassName JucTest2 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ public class JucTest2 { 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 l = new ReentrantLock(); Condition condition1 = l.newCondition(); Condition condition2 = l.newCondition(); Condition condition3 = l.newCondition(); private int flag = 1; public void printA(){ l.lock(); //判断 -> 执行 -> 通知 try { while(flag != 1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName() + "->A" ); flag = 2; //唤醒指定线程 condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } public void printB(){ l.lock(); //判断 -> 执行 -> 通知 try { while(flag != 2){ //等待 condition2.await(); } System.out.println(Thread.currentThread().getName() + "->BB" ); flag = 3; //唤醒指定线程 condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } public void printC(){ l.lock(); //判断 -> 执行 -> 通知 try { while(flag != 3){ //等待 condition3.await(); } System.out.println(Thread.currentThread().getName() + "->CCC" ); flag = 1; //唤醒指定线程 condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } }
5、8锁现象
1-2锁
package com.zmz.lock8;/** * @ProjectName: Juc * @Package: com.zmz.lock8 * @ClassName: Test1 * @Author: 张晟睿 * @Date: 2021/9/5 21:18 * @Version: 1.0 */ import java.util.concurrent.TimeUnit; /** *@ClassName Test1 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ /* * 8锁,就是关于锁的8个问题 * 1、标准情况下,两个线程先打印 发短信还是打电话? 发短信 * 2、sendSms方法延迟4s,两个线程先打印 发短信还是打电话? 发短信 * */ public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); //锁存在 new Thread(()->{ phone.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); },"B").start(); } } class Phone{ //synchronized锁的对象是方法的调用者! //两个方法用的都是phone对象的锁! //谁先拿到谁执行! public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } }
3-4锁
package com.zmz.lock8;/** * @ProjectName: Juc * @Package: com.zmz.lock8 * @ClassName: Test2 * @Author: 张晟睿 * @Date: 2021/9/5 21:26 * @Version: 1.0 */import java.util.concurrent.TimeUnit;/** *@ClassName Test2 *@Description *@Author 张晟睿 *@Date 2021/9/5 **///3、增加一个普通方法后! 发短信还是Hello,发短信 hello//4、两个对象,两个同步方法,发短信还是打电话 打电话public class Test2 { public static void main(String[] args) { //两个对象,两把锁 Phone2 phone = new Phone2(); Phone2 phone2 = new Phone2(); //锁存在 new Thread(()->{ phone.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); }}class Phone2{ //synchronized锁的对象是方法的调用者! public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } //这里没有锁!bubu不是同步方法,不受锁的影响 public void hello(){ System.out.println("hello"); }}
5-6锁
package com.zmz.lock8;/** * @ProjectName: Juc * @Package: com.zmz.lock8 * @ClassName: Test3 * @Author: 张晟睿 * @Date: 2021/9/6 12:35 * @Version: 1.0 */import java.util.concurrent.TimeUnit;/** *@ClassName Test3 *@Description *@Author 张晟睿 *@Date 2021/9/6 **//** 5、增加两个静态同步方法,只有一个对象,先打印发短信还是打电话? 发短信* 6、两个对象!增加两个静态同步方法,只有一个对象,先打印发短信还是打电话 发短信* */public class Test3 { public static void main(String[] args) { //两个对象,两个调用者,两把锁! //两个对象的class类模板只有一个,static,锁的是Class Phone3 phone = new Phone3(); Phone3 phone2 = new Phone3(); //锁存在 new Thread(()->{ phone.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); }}//Phone3唯一的一个Class对象class Phone3{ //synchronized锁的对象是方法的调用者! //static 静态方法 //类一加载就有了!Class 模板 锁的是Class public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public static synchronized void call(){ System.out.println("打电话"); }}
7-8锁
package com.zmz.lock8;/** * @ProjectName: Juc * @Package: com.zmz.lock8 * @ClassName: Test4 * @Author: 张晟睿 * @Date: 2021/9/6 13:07 * @Version: 1.0 */import java.util.concurrent.TimeUnit;/** *@ClassName Test4 *@Description *@Author 张晟睿 *@Date 2021/9/6 **//** 1个静态同步方法,1个同步方法,1个对象 先打印发短信还是打电话? 打电话* 1个静态同步方法,1个同步方法,2个对象 先打印发短信还是打电话? 发短信* */public class Test4 { public static void main(String[] args) { //两个对象,两个调用者,两把锁! //两个对象的class类模板只有一个,static,锁的是Class Phone4 phone = new Phone4(); //锁存在 new Thread(()->{ phone.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); },"B").start(); }}//Phone4唯一的一个Class对象class Phone4{ //synchronized锁的对象是方法的调用者! //锁的是Class类模板 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("打电话"); }}
6、集合不安全
1、ArrayList集合不安全
package com;/** * @ProjectName: Juc * @Package: com * @ClassName: unsafe * @Author: 张晟睿 * @Date: 2021/9/6 19:37 * @Version: 1.0 */import java.util.*;import java.util.concurrent.CopyOnWriteArrayList;/** *@ClassName unsafe *@Description *@Author 张晟睿 *@Date 2021/9/6 **/public class unsafe { public static void main(String[] args) { //高并发下的ArrayList真的安全么? /** * 解决方法 * 1、List<String> list = new Vector<String>(); * 2、List<String> list = Collections.synchronizedList(new ArrayList<>()); * 3、List<String> list = new CopyOnWriteArrayList<>(); * */ List<String> list = new CopyOnWriteArrayList<>(); //启动10个多线程 for (int i = 1; i < 10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 5)); System.out.println(list); }, String.valueOf(i)).start(); } }}
CopyOnWriteArrayList源码分析
2、Set不安全
package com.zmz.unsafe;/** * @ProjectName: Juc * @Package: com.zmz.unsafe * @ClassName: SetSafe * @Author: 张晟睿 * @Date: 2021/9/6 21:20 * @Version: 1.0 */import java.util.Collections;import java.util.HashSet;import java.util.Set;import java.util.UUID;import java.util.concurrent.CopyOnWriteArraySet;/** *@ClassName SetSafe *@Description *@Author 张晟睿 *@Date 2021/9/6 **/public class SetSafe { 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 < 60; 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<>(); } //HashSet本质就是Map集合 public boolean add(E e) { return map.put(e, PRESENT)==null; }private static final Object PRESENT = new Object();//不变的值
3、HashMap不安全
package com.zmz.unsafe;/** * @ProjectName: Juc * @Package: com.zmz.unsafe * @ClassName: MapSafe * @Author: 张晟睿 * @Date: 2021/9/6 21:27 * @Version: 1.0 */import java.util.Collections;import java.util.HashMap;import java.util.Map;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;/** *@ClassName MapSafe *@Description *@Author 张晟睿 *@Date 2021/9/6 **/public class MapSafe { public static void main(String[] args) { //Map<String,String> map = new HashMap<>(); //Map<String, Object> map = Collections.synchronizedMap(new HashMap<>()); Map<String, Object> map = new ConcurrentHashMap<>(); for (int i = 1; i <= 30; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5)); System.out.println(map); },String.valueOf(i)).start(); } }}
7、Callable
- 可以有返回值
- 可以抛出异常
- 方法不同,run()/call()
callable源码
代码测试
package com.zmz.callable;/** * @ProjectName: Juc * @Package: com.zmz.callable * @ClassName: CallableTest * @Author: 张晟睿 * @Date: 2021/10/3 16:52 * @Version: 1.0 */import com.sun.org.apache.bcel.internal.generic.NEW;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** *@ClassName CallableTest *@Description *@Author 张晟睿 *@Date 2021/10/3 **/public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { // new Thread(new Runnable()).start(); // new Thread(new FutureTask<V>()).start(); // new Thread(new FutureTask<V>( Callable )).start(); new Thread().start(); MyThread myThread = new MyThread(); FutureTask futureTask = new FutureTask(myThread);//适配类 new Thread(futureTask,"A").start(); new Thread(futureTask,"B").start(); //结果存在缓存提交效率 Integer o = (Integer) futureTask.get();//获取返回的结果 //这个get方法可以会产生阻塞! 解决办法 放到最后一行或者异步通信 System.out.println(o); }}class MyThread implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("call()方法"); //耗时操作 return 1024; }}/*1、有缓存2、结果可能会等待,阻塞*/
8、常用的辅助类
1) CountDownLatch—减法计数器
package com.zmz.assist;/** * @ProjectName: Juc * @Package: com.zmz.assist * @ClassName: CountDownLatchDemo * @Author: 张晟睿 * @Date: 2021/10/3 18:00 * @Version: 1.0 */import java.util.concurrent.CountDownLatch;/** *@ClassName CountDownLatchDemo *@Description *@Author 张晟睿 *@Date 2021/10/3 **/public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch count = new CountDownLatch(10); for (int i = 1; i <= 10; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName() + "Go out"); count.countDown();//数量-1 },String.valueOf(i)).start(); } count.await();//等计数器归零,然后再往下执行 System.out.println("Close Door"); }}
原理:
- count.countDown();//数量-1
- count.await();//等待计数器归零。然后再向下执行
每次有线程用countDown()数量-1,如果计算器变为0了,然后count.await()就被唤醒,继续下面的执行!
2) CyclicBarrier—加法计数器
package com.zmz.assist;/** * @ProjectName: Juc * @Package: com.zmz.assist * @ClassName: CycilcBarrierDemo * @Author: 张晟睿 * @Date: 2021/10/3 18:23 * @Version: 1.0 */ import com.sun.org.apache.bcel.internal.generic.NEW; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** *@ClassName CycilcBarrierDemo *@Description *@Author 张晟睿 *@Date 2021/10/3 **/ public class CycilcBarrierDemo { public static void main(String[] args) throws BrokenBarrierException, InterruptedException { CyclicBarrier barrier = new CyclicBarrier(7,()->{ System.out.println("你已经凑齐了七颗龙珠!可以变身了"); }); for (int i = 1; i <= 7; i++) { final int temp = i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+ "收集" +(temp) +"颗龙珠"); try { barrier.await();//等待 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
3) Semaphore
Semaphore:信号量,很多时候用来处理高并发。
package com.zmz.assist;/** * @ProjectName: Juc * @Package: com.zmz.assist * @ClassName: SemaphoreDemo * @Author: 张晟睿 * @Date: 2021/10/3 19:32 * @Version: 1.0 */import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;/** *@ClassName SemaphoreDemo *@Description *@Author 张晟睿 *@Date 2021/10/3 **/public class SemaphoreDemo { 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(); } }}
原理:
- acquire():获得,假设已经满了组需要等待,直到被释放为止
- release():释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享的资源互斥使用!并发限流控制最大的线程数量!
9、读写锁ReadwriteLock
package com.zmz.lock;/** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: ReadwritelockDemo * @Author: 张晟睿 * @Date: 2021/10/3 20:48 * @Version: 1.0 */import java.util.HashMap;import java.util.Map;/** *@ClassName ReadwritelockDemo *@Description *@Author 张晟睿 *@Date 2021/10/3 **/public class ReadwritelockDemo { public static void main(String[] args) { Mycache mycache = new Mycache(); //写入 for (int i = 1; i < 6; i++) { final int temp = i; new Thread(()->{ mycache.put(temp+"",temp+""); },String.valueOf(i)).start(); } //读取 for (int i = 1; i < 6; i++) { final int temp = i; new Thread(()->{ mycache.get(temp+""); },String.valueOf(i)).start(); } }}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() + "写入完成"); } public void get(String key){ System.out.println(Thread.currentThread().getName() + "读取" + key); Object o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取完成"); }}/*1写入15写入55写入完成4写入44写入完成3写入32写入21读取13写入完成1写入完成2读取22读取完成3读取33读取完成1读取完成2写入完成5读取55读取完成4读取44读取完成*/
我们可以看到出现了严重的插队问题!该如何去解决囊?我们使用读写锁来解决插队的问题。
修改后的操作
package com.zmz.lock;/** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: ReadwritelockDemo * @Author: 张晟睿 * @Date: 2021/10/3 20:48 * @Version: 1.0 */ import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @ClassName ReadwritelockDemo * @Description * @Author 张晟睿 * @Date 2021/10/3 **/ /* *独占锁 (写锁)一次只能被一个线程占有 *共享锁 (读锁)多个线程可以同时占有 * ReadWriteLock * 读-读 可以共存 * 读-写 不可以共存 * 写-写 不可以共存 * */ public class ReadwritelockDemo { public static void main(String[] args) { MycacheLock mycache = new MycacheLock(); //写入 for (int i = 1; i < 6; i++) { final int temp = i; new Thread(() -> { mycache.put(temp + "", temp + ""); }, String.valueOf(i)).start(); } //读取 for (int i = 1; i < 6; i++) { final int temp = i; new Thread(() -> { mycache.get(temp + ""); }, String.valueOf(i)).start(); } } } //加锁 class MycacheLock { private volatile Map<String, Object> map = new HashMap<>(); //读写锁:更加细粒度的控制 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() + "写入完成"); } 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() + "读取完成"); } 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() + "写入完成"); } public void get(String key) { System.out.println(Thread.currentThread().getName() + "读取" + key); Object o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取完成"); } } /* 1写入1 1写入完成 2写入2 2写入完成 3写入3 3写入完成 4写入4 4写入完成 5写入5 5写入完成 1读取1 1读取完成 5读取5 3读取3 3读取完成 4读取4 4读取完成 5读取完成 2读取2 2读取完成 */
我们可以看到输出的结果在写入的时候有序的进行,读操作的时候可以无序的进行,可以看到已经到达我们预期的效果😊
10、堵塞队列
堵塞
什么情况下我们使用阻塞队列:多线程并发处理,线程池!
10.1 学会使用队列
添加、移除元素,现在有四组API
1) 四组API
方法 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞等待 | 超时等待 |
添加 | add() | offer() | put() | offer(E e, long timeout, TimeUnit unit) |
移除 | remove() | poll() | take() | poll(long timeout, TimeUnit unit) |
判断首部 | element() | peek() | - | - |
/* * 抛出异常 * */ public static void test1(){ //队列大小3 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("a")); System.out.println(blockingQueue.add("b")); System.out.println(blockingQueue.add("c")); //IllegalState ExceptionQueue full 抛出异常 //System.out.println(blockingQueue.add("d")); //检测队首元素 System.out.println(blockingQueue.element()); System.out.println("================================================"); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); //java.util.NoSuchElementException 抛出异常 //System.out.println(blockingQueue.remove()); }
/* * 不抛出异常,有返回值 * */ public static void test2(){ //队列大小3 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); //false System.out.println(blockingQueue.offer("d")); //检测队首元素 System.out.println(blockingQueue.peek()); System.out.println("================================================"); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); //null System.out.println(blockingQueue.poll()); }
/* * 阻塞等待之☞死死的等待 * */ public static void test3() throws InterruptedException { //队列大小3 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); //blockingQueue.put("d"); //无检测队首元素 System.out.println("================================================"); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); //System.out.println(blockingQueue.take()); }
/* * 等待阻塞(超时) * */ public static void test4() throws InterruptedException { //队列大小3 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); blockingQueue.offer("a"); blockingQueue.offer("b"); blockingQueue.offer("c"); blockingQueue.offer("d",2, TimeUnit.SECONDS); //无检测队首元素 System.out.println("================================================"); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); blockingQueue.poll(2,TimeUnit.SECONDS); }
2) SynchronizedQueue 同步队列
没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素
put take
package com.zmz.queue;/** * @ProjectName: Juc * @Package: com.zmz.queue * @ClassName: SyncQueue * @Author: 张晟睿 * @Date: 2021/10/8 14:18 * @Version: 1.0 */import java.util.concurrent.SynchronousQueue;import java.util.concurrent.TimeUnit;/** * 同步队列 * 和其他的lockQueue 不一样, SynchronousQueue 不存储元素 */public class SyncQueue { public static void main(String[] args) { SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>(); //同步队列 new Thread(()->{ try { System.out.println(Thread.currentThread().getName() + "put 1"); synchronousQueue.put("1"); System.out.println(Thread.currentThread().getName() + "put 2"); synchronousQueue.put("2"); System.out.println(Thread.currentThread().getName() + "put 3"); synchronousQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } },"Thread1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=>" + synchronousQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=>" + synchronousQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=>" + synchronousQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } finally { } },"Thread2").start(); }}/*Thread1put 1Thread2=>1Thread1put 2Thread2=>2Thread1put 3Thread2=>3*/
11、线程池
线程池有三大方法,七大参数,四种拒绝策略
程序的运行,本质: 占用系统的资源 ! 优化CPU资源的使用 ===>池化技术
线程池, 连接池, 内存池, 对象池///…
池化技术: 实现准备好一些资源, 有人要用,就来我这里拿,用完之后还给我
1) 线程池的好处:
- 降低资源消耗
- 提高响应速度
- 方便管理
线程复用,可以控制最大并发数,管理线程
2) 线程池: 三大方法
- ExecutorService service = Executors.newSingleThreadExecutor();//单个线程
- ExecutorService service = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
- ExecutorService service = Executors.newCachedThreadPool();//可伸缩的,
package com.zmz.Pool;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * @ProjectName: Juc * @Package: com.zmz.ThreadPool * @ClassName: ThreadPool * @Author: 张晟睿 * @Date: 2021/10/8 16:44 * @Version: 1.0 */public class ThreadPool { public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程 ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小 ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的 //线程池用完必须要关闭线程池 try { for (int i = 1; i <=100 ; i++) { //通过线程池创建线程 threadPool3.execute(()->{ System.out.println(Thread.currentThread().getName()+ " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool3.shutdown(); } }}
3) 七大参数
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 com.zmz.Pool; import java.util.concurrent.*; /** * @ProjectName: Juc * @Package: com.zmz.Pool * @ClassName: ThreadPoolExecutorTest * @Author: 张晟睿 * @Date: 2021/10/8 16:59 * @Version: 1.0 */ public class ThreadPoolExecutorTest { public static void main(String[] args) { // 获取cpu 的核数 int max = Runtime.getRuntime().availableProcessors(); ExecutorService service =new ThreadPoolExecutor( 2,//核心线程池大小 max,//最大的线程池大小 3,//超时了没有人调用就会释放 TimeUnit.SECONDS,//超时单位 new LinkedBlockingDeque<>(3),//阻塞队列 Executors.defaultThreadFactory(),//线程工厂 创建线程的 new ThreadPoolExecutor.AbortPolicy()//拒绝策略 ); try { for (int i = 1; i <= 5; i++) { service.execute(() -> { System.out.println(Thread.currentThread().getName() + "运行成功"); }); } }catch (Exception e) { e.printStackTrace(); } finally { service.shutdown(); } } }
4) 拒绝策略
- new ThreadPoolExecutor.AbortPolicy() 超出最大处理线程抛出异常
- new ThreadPoolExecutor.CallerRunsPolicy() 从哪个线程创建就由那个线程执行
- new ThreadPoolExecutor.DiscardPolicy() 队列满了不会抛出异常
- new ThreadPoolExecutor.DiscardOldestPolicy() 尝试去和第一个竞争,也不会抛出异常
12、四大函数式接口
新时代的程序员👨💻:lambda表达式、链式编程、函数式接口、Stream流式计算
1) Function函数型接口
package com.zmz.FourFunction;import java.util.function.Function;/** * @ProjectName: Juc * @Package: com.zmz.FourFunction * @ClassName: functionDemo * @Author: 张晟睿 * @Date: 2021/10/8 17:15 * @Version: 1.0 */public class functionDemo { public static void main(String[] args) { Function<String, String> function = (str) -> { return str; }; System.out.println(function.apply("Hello,zmz!")); }}
2) Predicate 断定型接口
package com.zmz.FourFunction;import java.util.function.Predicate;/** * @ProjectName: Juc * @Package: com.zmz.FourFunction * @ClassName: PredicateDemo * @Author: 张晟睿 * @Date: 2021/10/8 17:18 * @Version: 1.0 */public class PredicateDemo { public static void main(String[] args) { Predicate<String> predicate = (str) -> {return str.isEmpty();}; // false System.out.println(predicate.test("zmz")); // true System.out.println(predicate.test("")); }}
3) Suppier 供给型接口
package com.zmz.FourFunction;import java.util.function.Supplier;/** * @ProjectName: Juc * @Package: com.zmz.FourFunction * @ClassName: SuppierDemo * @Author: 张晟睿 * @Date: 2021/10/8 17:21 * @Version: 1.0 */public class SuppierDemo { public static void main(String[] args) { Supplier<String> supplier = ()->{return "1024";}; System.out.println(supplier.get()); }}
4) Consummer 消费型接口
package com.zmz.FourFunction;import java.util.function.Consumer;/** * @ProjectName: Juc * @Package: com.zmz.FourFunction * @ClassName: ConsummerDemo * @Author: 张晟睿 * @Date: 2021/10/8 17:21 * @Version: 1.0 */public class ConsummerDemo { public static void main(String[] args) { Consumer<String> consumer = (str)->{ System.out.println(str); }; consumer.accept("zmz"); }}
13、Stream 流式计算
package com.zmz.Stream;/** * @ProjectName: Juc * @Package: com.zmz.Stream * @ClassName: User * @Author: 张晟睿 * @Date: 2021/10/8 18:01 * @Version: 1.0 */public class User { private int id; private String name; private int age; public User(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
package com.zmz.Stream; /** * @ProjectName: Juc * @Package: com.zmz.Stream * @ClassName: StreamDemo * @Author: 张晟睿 * @Date: 2021/10/8 18:00 * @Version: 1.0 * * * 题目要求: 用一行代码实现 * * 1. Id 必须是偶数 * * 2.年龄必须大于23 * * 3. 用户名转为大写 * * 4. 用户名倒序 * * 5. 只能输出一个用户 */ import java.util.Arrays; import java.util.List; public class StreamDemo { public static void main(String[] args) { User u1 = new User(1, "a", 23); User u2 = new User(2, "b", 23); User u3 = new User(3, "c", 23); User u4 = new User(6, "d", 24); User u5 = new User(4, "e", 25); List<User> list = Arrays.asList(u1, u2, u3, u4, u5); // lambda、链式编程、函数式接口、流式计算 list.stream() .filter(user -> {return user.getId()%2 == 0;}) .filter(user -> {return user.getAge() > 20;}) .map(user -> {return user.getName().toUpperCase();}) .sorted((user1, user2) -> {return user2.compareTo(user1);}) .limit(1) .forEach(System.out::println); } }
14、ForkJoin—多线并发处理框架
什么是ForkJoin?
ava.util.concurrent.ForkJoinPool由Java大师Doug Lea主持编写,它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。本文中对Fork/Join框架的讲解,基于JDK1.8+中的Fork/Join框架实现,参考的Fork/Join框架主要源代码也基于JDK1.8+。
这几篇文章将试图解释Fork/Join框架的知识点,以便对自己、对各位读者在并发程序的设计思路上进行一些启发。文章将首先讲解Fork/Join框架的基本使用,以及其中需要注意的使用要点;接着使用Fork/Join框架解决一些实际问题;最后再讲解Fork/Join框架的工作原理。
1)ForkJoin 特点: 工作窃取!
2)如果使用ForkJoin
第一步,通过ForkJoinPool来执行
第二步,计算任务 execute(ForkJoinTask<?> task)
第三步,计算类要去继承ForkJoinTask
ForkJoin 的计算类
ForkJoinComputer.java
package com.zmz.ForkJoin; import java.util.concurrent.RecursiveTask; /** * @ProjectName: Juc * @Package: com.zmz.ForkJoin * @ClassName: ForkJoinComputer * @Author: 张晟睿 * @Date: 2021/10/9 15:17 * @Version: 1.0 */ public class ForkJoinComputer extends RecursiveTask<Long> { private long start; private long end; /** 临界值 */ private long temp = 1000000L; public ForkJoinComputer(long start, long end) { this.start = start; this.end = end; } /** * 计算方法 * @return */ @Override protected Long compute() { if ((end - start) < temp) { Long sum = 0L; for (Long i = start; i < end; i++) { sum += i; } return sum; }else { // 使用ForkJoin 分而治之 计算 //1 . 计算平均值 long middle = (start + end) / 2; ForkJoinComputer forkJoinDemo1 = new ForkJoinComputer(start, middle); // 拆分任务,把线程压入线程队列 forkJoinDemo1.fork(); ForkJoinComputer forkJoinDemo2 = new ForkJoinComputer(middle, end); forkJoinDemo2.fork(); long taskSum = forkJoinDemo1.join() + forkJoinDemo2.join(); return taskSum; } } }
测试类 ForkJoinTest.java
package com.zmz.ForkJoin; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.stream.LongStream; /** * @ProjectName: Juc * @Package: com.zmz.ForkJoin * @ClassName: ForkJoinTest * @Author: 张晟睿 * @Date: 2021/10/9 15:18 * @Version: 1.0 */ public class ForkJoinTest { private static final long SUM = 20_0000_0000; public static void main(String[] args) throws ExecutionException, InterruptedException { test1(); test2(); test3(); } /** * 使用普通方法 */ public static void test1() { long star = System.currentTimeMillis(); long sum = 0L; for (long i = 1; i < SUM ; i++) { sum += i; } long end = System.currentTimeMillis(); System.out.println(sum); System.out.println("普通程序猿——时间:" + (end - star)); System.out.println("============================"); } /** * 使用ForkJoin 方法 */ public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinComputer(0L, SUM); ForkJoinTask<Long> submit = forkJoinPool.submit(task); Long along = submit.get(); System.out.println(along); long end = System.currentTimeMillis(); System.out.println("中级程序猿——时间:" + (end - start)); System.out.println("--------------"); } /** * 使用 Stream 流计算 */ public static void test3() { long start = System.currentTimeMillis(); long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum); System.out.println(sum); long end = System.currentTimeMillis(); System.out.println("高级程序猿——时间:" + (end - start)); System.out.println("--------------"); System.out.println("============================"); } }
分析一下高级程序猿的处理:
.parallel().reduce(0, Long::sum)使用一个并行流去计算整个计算,提高效率。
15、异步回调
Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 —》发送ajax异步请求给后端
但是我们平时都使用CompletableFuture
(1)没有返回值的runAsync异步回调
package com.zmz.Async;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;/** * @ProjectName: Juc * @Package: com.zmz.Async * @ClassName: runAsync * @Author: 张晟睿 * @Date: 2021/10/11 18:58 * @Version: 1.0 */public class runAsync { public static void main(String[] args) throws ExecutionException, InterruptedException { // 发起 一个 请求 System.out.println(System.currentTimeMillis()); System.out.println("---------------------"); CompletableFuture<Void> future = CompletableFuture.runAsync(()->{ //发起一个异步任务 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"....."); }); System.out.println(System.currentTimeMillis()); System.out.println("------------------------------"); //输出执行结果 System.out.println(future.get()); //获取执行结果 }}
(2)有返回值的异步回调supplyAsync
package com.zmz.Async;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;/** * @ProjectName: Juc * @Package: com.zmz.Async * @ClassName: supplyAsync * @Author: 张晟睿 * @Date: 2021/10/11 19:09 * @Version: 1.0 */public class supplyAsync { public static void main(String[] args) throws ExecutionException, InterruptedException { //有返回值的异步回调 CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(2); int i=1/0; } catch (InterruptedException e) { e.printStackTrace(); } return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { /*我们可以看到whenComplete以上的程序有两个参数,一个是t 一个是u T:是代表的 正常返回的结果; U:是代表的 抛出异常的错误信息; 如果发生了异常,get可以获取到exceptionally返回的值; */ //success 回调 System.out.println("t=>" + t); //正常的返回结果 System.out.println("u=>" + u); //抛出异常的 错误信息 }).exceptionally((e) -> { //error回调 System.out.println(e.getMessage()); return 404; }).get()); }}
16、JMM(Java Memory Model )
1)我们先了解一下什么JMM?
JMM:JAVA内存模型,不存在的东西,抽象的,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
线程中分为 工作内存、主内存。
八种操作:
名称 | 描述 |
Read(读取) | 作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用 |
load(载入) | 作用于工作内存的变量,它把read操作从主存中变量放入工作内存中 |
Use(使用) | 作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令 |
assign(赋值) | 作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中 |
store(存储) | 作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用 |
write(写入) | 作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中 |
lock(锁定) | 作用于主内存的变量,把一个变量标识为线程独占状态 |
unlock(解锁) | 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yTIjbiQ8-1635035368123)(https://gitee.com/z6135/cloudimage/raw/master/img/juc1 (2)].png)
对于八种操作给了相应的规定:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
遇到问题:程序不知道主存中的值已经被修改过了!
17、volatile
1)保证可见性
package com.zmz.JMM;import java.util.concurrent.TimeUnit;/** * @ProjectName: Juc * @Package: com.zmz.JMM * @ClassName: JMMdemo01 * @Author: 张晟睿 * @Date: 2021/10/11 20:43 * @Version: 1.0 */public class JMMdemo01 { // 如果不加volatile 程序会死循环 // 加了volatile是可以保证可见性的 private volatile static Integer number = 0; public static void main(String[] args) { //main线程 //子线程1 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //子线程2 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number=1; System.out.println(number); }}
2)不保证原子性
原子性:意思就是说不可分割,举一个例子就是说线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
package com.zmz.JMM;/** * @ProjectName: Juc * @Package: com.zmz.JMM * @ClassName: JMMdemo02 * @Author: 张晟睿 * @Date: 2021/10/11 20:46 * @Version: 1.0 */public class JMMdemo02 { private static volatile int num = 0; public static void add(){ num++; //++ 不是一个原子性操作,是2个~3个操作 } public static void main(String[] args) { //理论上number === 20000 for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 1; j <= 1000 ; j++) { add(); } }).start(); } while (Thread.activeCount()>2){ //main gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+",num="+num); }}
使用原子类
package com.zmz.JMM;import java.util.concurrent.atomic.AtomicInteger;/** * @ProjectName: Juc * @Package: com.zmz.JMM * @ClassName: JMMdemo03 * @Author: 张晟睿 * @Date: 2021/10/11 21:05 * @Version: 1.0 */public class JMMdemo03 { private static volatile AtomicInteger number = new AtomicInteger(); public static void add(){// number++; number.incrementAndGet(); //底层是CAS保证的原子性 } public static void main(String[] args) { //理论上number === 20000 for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 1; j <= 1000 ; j++) { add(); } }).start(); } while (Thread.activeCount()>2){ //main gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+",num="+number); }}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
3)禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=2; //1 int y=4; //2 x=x+10; //3 y=x*x; //4 //我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成3124 1423 //可不可能是 4123? 不可能的 1234567
可能造成的影响结果:前提: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 |
可能在线程A中会出现,先执行b=1,然后再执行x=a
在B线程中可能会出现,先执行a=2,然后执行y=b
那么就有可能结果如下:x=4; y=2
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:保证特定的操作的执行顺序;可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
4)总结
- 由于内存屏障,可以保证避免指令重排的现象产生
- 不能保证原子性
- volatile可以保证可见性
🥰面试题:在哪里用这个内存屏障用得最多呢?
单例模式
18、单例模式
1)饿汉式
package single; //饿汉式单例模式 @SuppressWarnings("all") public class Hungry { private byte[] date1= new byte[1024*1024]; private byte[] date2= new byte[1024*1024]; private byte[] date3= new byte[1024*1024]; private byte[] date4= new byte[1024*1024]; private Hungry(){ } private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
2)DCL懒汉式
package single; import java.lang.reflect.Constructor; //懒汉式 public class LazyMan { private LazyMan(){ synchronized(LazyMan.class){ throw new RuntimeException("不要试图使用反射破坏异常"); } // System.out.println(Thread.currentThread().getName()+"ok"); } private static LazyMan lazyMan; //双重检测锁 public static LazyMan getInstance(){ if (lazyMan==null) { synchronized (LazyMan.class) { if (lazyMan==null) { lazyMan = new LazyMan(); //不是原子性操作 //1.分配内存空间 //2.执行构造方法,初始化对象 //3.把这个对象指向空间 } } } return lazyMan; } public static void main(String[] args) throws Exception { // LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance = declaredConstructor.newInstance(); LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); for (int i = 0; i < 2; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
3)静态内部类
package single; public class Holder { private Holder(){} private static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass { private static final Holder HOLDER = new Holder(); } }
单例不安全, 主要的原因是因为反射。
4)枚举
package com.zmz.Singleton; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * @ProjectName: Juc * @Package: com.zmz.Singleton * @ClassName: EnumSingle * @Author: 张晟睿 * @Date: 2021/10/12 20:06 * @Version: 1.0 */ //enum 是什么? enum本身就是一个Class 类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>() EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
枚举类型的最终反编译源码:
public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }
19、深入理解CAS
如果想要深入了解CAS,请跳转到以下文章
1)什么是CAS?
CAS(Compare And Swap比较并替换),包含三个值当前内存值(V)、预期原来的值(A)以及期待更新的值(B),这里我们先做简单的介绍后期会出一期博文进行单独介绍的。
package com.zmz.CAS; import java.util.concurrent.atomic.AtomicInteger; /** * @ProjectName: Juc * @Package: com.zmz.CAS * @ClassName: CasDemo01 * @Author: 张晟睿 * @Date: 2021/10/12 20:15 * @Version: 1.0 */ public class CasDemo01 { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }
我们点开AtomicInteger源码观察一下,可以发现
2)总结
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
- 一次性只能保证一个共享变量的原子性
- 循环会耗时
- 它会存在ABA问题
什么是ABA问题?(就是我们所说的狸猫换太子)
package com.zmz.CAS; import java.util.concurrent.atomic.AtomicInteger; /** * @ProjectName: Juc * @Package: com.zmz.CAS * @ClassName: CasDemo02 * @Author: 张晟睿 * @Date: 2021/10/12 20:40 * @Version: 1.0 */ public class CasDemo02 { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 // atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }
20、原子引用
为了解决ABA问题,对应的思想:就是使用了乐观锁~
注意:
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
说明:对于Integer var = ?在-128 至127之间的赋值,Integer对象是在IntegerCache .cache产生,会复用已有对象, 这个区间内的Integer值可以直接使用==进 行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
package com.zmz.CAS; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicStampedReference; /** * @ProjectName: Juc * @Package: com.zmz.CAS * @ClassName: CasDemo03 * @Author: 张晟睿 * @Date: 2021/10/12 20:45 * @Version: 1.0 */ public class CasDemo03 { /**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题 * 正常在业务操作,这里面比较的都是一个个对象 */ static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); // CAS compareAndSet : 比较并交换! public static void main(String[] args) { new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 获得版本号 System.out.println("a1=>" + stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 修改操作时,版本号更新 + 1 atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println("a2=>" + atomicStampedReference.getStamp()); // 重新把值改回去, 版本号更新 + 1 System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a3=>" + atomicStampedReference.getStamp()); }, "a").start(); // 乐观锁的原理相同! new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 获得版本号 System.out.println("b1=>" + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(1, 3, stamp, stamp + 1)); System.out.println("b2=>" + atomicStampedReference.getStamp()); }, "b").start(); } }
21、各种锁的理解
1)公平锁,非公平锁
- 公平锁:非常公平的锁,不能插队,必须先来后到
- 非公平锁:非常不公平,允许插队,可以改变顺序
2)可重入锁
1.Synchonized锁
package com.zmz.lock; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: SynchonizedDemo * @Author: 张晟睿 * @Date: 2021/10/12 20:59 * @Version: 1.0 */ public class SynchonizedDemo { 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()+"=> 发短息"); call();//这里也有一把锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"=> 打电话"); } }
2.Lock锁
package com.zmz.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: LockDemo * @Author: 张晟睿 * @Date: 2021/10/12 21:02 * @Version: 1.0 */ public class LockDemo { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone2{ Lock lock=new ReentrantLock(); public void sms(){ lock.lock(); //细节:这个是两把锁,两个钥匙 //lock锁必须配对,否则就会死锁在里面 try { System.out.println(Thread.currentThread().getName()+"=> 发短信"); call();//这里也有一把锁 } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> 打电话"); }catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } } }
注意:
- lock锁必须配对,相当于lock和 unlock 必须数量相同
- 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁
3)自旋锁
1.spinlock
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}
2.自我设计自旋锁
package com.zmz.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: MySpinlockTest * @Author: 张晟睿 * @Date: 2021/10/12 21:07 * @Version: 1.0 */ class MySpinlock { // 默认 // int 0 //thread null AtomicReference<Thread> atomicReference=new AtomicReference<>(); //加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"===> mylock"); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ System.out.println(Thread.currentThread().getName()+" ==> 自旋中~"); } } //解锁 public void myUnlock(){ Thread thread=Thread.currentThread(); System.out.println(thread.getName()+"===> myUnlock"); atomicReference.compareAndSet(thread,null); } } public class MySpinlockTest { public static void main(String[] args) throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); reentrantLock.unlock(); //使用CAS实现自旋锁 MySpinlock spinlockDemo=new MySpinlock(); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"Thread1").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"Thread2").start(); } }
运行结果:t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。
4)死锁
package com.zmz.lock; import java.util.concurrent.TimeUnit; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: DeadLockDemo * @Author: 张晟睿 * @Date: 2021/10/12 21:13 * @Version: 1.0 */ public class DeadLockDemo { public static void main(String[] args) { String lockA= "lockA"; String lockB= "lockB"; new Thread(new MyThread1(lockA,lockB),"Thread1").start(); new Thread(new MyThread1(lockB,lockA),"Thread2").start(); } } class MyThread1 implements Runnable{ private String lockA; private String lockB; public MyThread1(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA); } } } }
🤡我们如何去解决死锁的问题?
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
jps -l
2、进程进程号 找到死锁信息
jstack 进程号 #如果出现拒绝访问 一定要以管理员身份运行
一般情况信息在最后:
希望对各位的面试有帮助,也希望小伙伴们多多支持厂长,留下你们的爱心💕和赞👍!
最后厂长祝大家1024程序员节快乐,拿到心仪的Offer