JUC
1、什么是JUC
JUC:指的是java.util三个并发编程工具包
java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks实现多线程的四种方式:
继承Thread类实现Runnable接口实现Callable接口线程池业务:普通的线程代码 Thread
Runnable 没有返回值、效率相比于Callable相对较低!
Runnable接口和Callable接口区别:
是否有返回值:Runnable无返回值,Callable有返回值是否抛出异常:call方法计算一个结果,如果不能这样做,就会抛出异常实现方法名称不同,Runnable接口是run方法,Callable接口是call方法2、线程和进程
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单元。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单元。
进程:是一个程序,一个进程包含多个线程,且至少包含一个线程。
Java默认有两个线程:main 和 GC。
Java能开启线程吗? start方法开启线程
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //本地方法,底层C++,Java无法操作硬件,由操作系统决定是否创建线程,是否立即创建线程 private native void start0();
Java是不能开启线程的,底层是调用start0()是一个native方法,由底层的C++方法编写。java无法直接操作硬件。
线程的几种状态
Thread.State
public enum State { NEW,//新建 RUNNABLE,//准备就绪 BLOCKED, //阻塞 WAITING,//一直等待 TIMED_WAITING,//超时等待,过时不候 TERMINATED;//终止 }
wait/sleep区别
1.来自不同的类
wait => Object,任何对象实例都能调用
sleep => Thread,Thread的静态方法
2.关于锁的释放
wait会释放锁;sleep不会释放锁,它也不需要占用锁
3.使用范围、捕获异常不同
wait:必须在同步代码块中使用,不需要捕获异常
sleep:可以在任何地方使用,必须要捕获异常
并发、并行
并发编程:并发、并行
并发: 同一时刻多个线程访问同一个资源(多线程共享资源)
例如:春运抢票、电商秒杀
并行: 多项工作一起执行,之后再汇总
例如:泡方便面,电水壶烧水的同时,拆开泡面调料倒入桶中
System.out.println(Runtime.getRuntime().availableProcessors());//获取cpu的核数
并发编程的本质:充分利用CPU资源
管程
Monitor 监视器(就是平常说的锁)
是一种同步机制,保证同一时间内,只有一个线程访问被保护的数据或者代码
jvm同步基于进入和退出,使用管程对象实现的
用户线程和守护线程
用户线程:自定义线程(new Thread())
守护线程:后台中一种特殊的线程,比如垃圾回收
主线程结束了,用户线程还在运行,jvm存活
没有用户线程了,都是守护线程,jvm结束
3、Lock锁(重点)
多线程编程步骤:
创建资源类,在资源类创建属性和操作方法创建多个线程,调用资源类的操作方法案例:三个售票员同时卖30张票。
传统 synchronized
public class SaleTicketDemo1 { public static void main(String[] args) { //并发:多个线程操作同一个资源类,把资源类丢入线程 Ticket ticket = new Ticket(); //Runnable接口 -》 函数式接口,lambda表达式:函数式接口的实例 new Thread(() -> { for (int i = 0; i < 30; i++) { ticket.sale(); } },"A").start(); new Thread(() -> { for (int i = 0; i < 30; i++) { ticket.sale(); } },"B").start(); new Thread(() -> { for (int i = 0; i < 30; i++) { ticket.sale(); } },"C").start(); }}/** * 资源类 */class Ticket { //属性 private int num = 30; //synchronized 本质:线程串行化,排队,锁 public synchronized void sale() { if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + (num--) + "张票,剩余" + num + "张"); } }}
Lock接口
实现类
公平锁:十分公平,先来后到(阳光普照,效率相对低一些)
非公平锁:十分不公平,可以插队(默认,可能会造成线程饿死,但是效率高)
public class SaleTicketDemo2 { public static void main(String[] args) { //并发:多个线程操作同一个资源类,把资源类丢入线程 Ticket2 ticket = new Ticket2(); new Thread(() -> {for (int i = 0; i < 40; i++) ticket.sale();},"A").start(); new Thread(() -> {for (int i = 0; i < 40; i++) ticket.sale();},"B").start(); new Thread(() -> {for (int i = 0; i < 40; i++) ticket.sale();},"C").start(); }}/** * 资源类 * * Lock三部曲 * 1.new ReentrantLock(); * 2.lock.lock(); //加锁 * 3.finally -》 lock.unlock();//解锁 * */class Ticket2 { //属性 private int num = 30; // 创建可重入锁 Lock lock = new ReentrantLock(); public void sale() { lock.lock(); //加锁 try { //业务代码 if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + (num--) + "张票,剩余" + num + "张"); } } finally { lock.unlock();//解锁 } }}
synchronized 和 Lock区别
1.synchronized 是内置的Java关键字,Lock是Java的一个接口
2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized 会自动释放锁,Lock必须手动释放锁!如果不释放锁,会造成死锁
4.synchronized 线程一在获得锁的情况下阻塞了,第二个线程就只能傻傻的等着;Lock就不一定会等待下去
5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平/公平(可以自己设置,默认非公平锁)
6.synchronized 适合锁少量的同步代码;Lock适合锁大量同步代码!
7.Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时(即大量线程同时竞争),此时Lock的性能要远远优于synchronized
4、线程间通信
生产者和消费者问题
面试:单例模式、排序算法、生产者消费者问题、死锁
资源类操作步骤:判断等待 -》执行业务-》唤醒通知
生产者和消费者问题 synchronized 版
Object类中方法
/** * 线程之间通信问题:生产者和消费者问题! 等待唤醒,通知唤醒 * 线程之间交替执行 A B 操作同一个变量 num = 0 * A num+1 * B num-1 */public class ProducerAndConsumer { 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; //+1 public synchronized void increment() throws InterruptedException { if (num != 0) { //等待 this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程我+1 完毕了 this.notifyAll(); } //-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 四个线程 虚假唤醒
public class ProducerAndConsumer { 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(); }}/** * 资源类 * <p> * 判断等待,业务,通知 */class Data { private int num = 0; /** * +1 */ public synchronized void increment() throws InterruptedException { if (num != 0) { //等待 this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程我+1 完毕了 this.notifyAll(); } /** * -1 */ public synchronized void decrement() throws InterruptedException { if (num == 0) { //等待 this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程我-1 完毕了 this.notifyAll(); }}
为什么会出现虚假唤醒问题呢?
主要是因为wait方法是从哪里等待就从哪里唤醒(而且wait是不会持有锁的,别的线程直接能拿到锁),if只会判断一次,只要第一次满足条件,等待唤醒就会直接执行下面+1/-1操作,所以才会出现>1或者<0的数甚至出现死锁现象,这些都是虚假唤醒导致的。
防止虚假唤醒问题:if 改为 while
/** * 线程之间通信问题:生产者和消费者问题! 等待唤醒,通知唤醒 * 线程之间交替执行 A B 操作同一个变量 num = 0 * A num+1 * B num-1 */public class ProducerAndConsumer { 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(); }}
JUC版的生产者和消费者问题
通过Lock找到Condition(官方文档)
代码实现
public class ProducerAndConsumer2 { public static void main(String[] args) { Data2 data = new Data2(); 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 Data2 { private int num = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //condition.await(); // 等待 //condition.signalAll(); // 唤醒全部 //+1 public void increment() throws InterruptedException { 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() throws InterruptedException { lock.lock(); try { while (num == 0) { //等待 condition.await(); } num--; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程我-1 完毕了 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }}
多线程编程完整步骤
第一步:创建资源类,在资源类中创建属性和操作方法
第二步:资源类操作方法
(1)判断等待
(2)业务逻辑
(3)通知唤醒
第三步:创建多个线程,调用资源类的操作方法
第四步:防止虚假唤醒
线程间定制通信
线程按约定顺序执行
任何一个新的技术,绝对不是仅仅只覆盖了原来的技术,有其优势和补充!
Condition精准的通知和唤醒线程
代码实现:三个线程顺序执行
/** * A B C三个线程顺序执行 */public class C { public static void main(String[] args) { Resource resource = new Resource(); new Thread(() -> { for (int i = 0; i < 10; i++) { resource.printA(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { resource.printB(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { resource.printC(); } }, "C").start(); }}/** * 资源类 Lock */class Resource { private final Lock lock = new ReentrantLock(); private final Condition condition1 = lock.newCondition(); private final Condition condition2 = lock.newCondition(); private final Condition condition3 = lock.newCondition(); private int num = 1; // 1A 2B 3C public void printA() { lock.lock(); try { //业务 判断 -》 执行 -》 通知 while (num != 1) { //等待 condition1.await(); } num = 2; System.out.println(Thread.currentThread().getName() + "=>AAA"); //唤醒,唤醒指定线程B condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB() { lock.lock(); try { //业务 判断 -》 执行 -》 通知 while (num != 2) { condition2.await(); } System.out.println(Thread.currentThread().getName() + "=>BBB"); num = 3; //唤醒线程C condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC() { lock.lock(); try { //业务 判断 -》 执行 -》 通知 while (num != 3) { condition3.await(); } System.out.println(Thread.currentThread().getName() + "=>CCC"); num = 1; //唤醒线程A condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }}
5、八锁现象
锁是什么?如何判断锁是谁?
任何类的对象、类对象
深刻理解锁
* 八锁,就是关于锁的八个问题 * 1、标准情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话 * 2、sendSms()延迟4s情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话 */public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); //锁的存在 new Thread(() -> { phone.sendSms(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(() -> { phone.call(); },"B").start(); }}class Phone{ //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("打电话"); }}
/** * 3、增加了一个普通方法后,先打印发短信还是hello? hello * 4、两个对象,两个同步方法 两个线程先打印 发短信 还是 打电话? 1/打电话 2/发短信 */public class Test2{ public static void main(String[] args) { //两个对象,两个不同的对象,两把锁! Phone2 phone1 = new Phone2(); Phone2 phone2 = new Phone2(); //锁的存在 new Thread(() -> { phone1.sendSms(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); 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("打电话"); } //没有锁,不是同步方法,不受锁的影响 public void hello(){ System.out.println("hello"); }}
/** * 5.增加两个静态的同步方法 一个对象 两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话 * 6.两个对象 两个静态的同步方法 两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话 */public class Test3 { public static void main(String[] args) { //两个不同的对象 但是类只会加载一次 共用一把锁 Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); //锁的存在 new Thread(() -> { phone1.sendSms(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(() -> { phone2.call(); },"B").start(); }}class Phone3{ //static静态方法 锁的是类对象 Phone3.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.一个静态同步方法一个普通同步方法 两个对象 两个线程先打印 发短信 还是 打电话? 打电话 发短信 */public class Test4 { public static void main(String[] args) { Phone4 phone1 = new Phone4(); Phone4 phone2 = new Phone4(); //锁的存在 new Thread(() -> { phone1.sendSms(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(() -> { phone2.call(); },"B").start(); }}class Phone4{ //static静态同步方法 锁的是类对象 Phone3.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("打电话"); }}
小结:
同步方法,谁先拿到锁谁先执行,同一把锁顺序执行。普通同步方法锁的是 this 当前对象(即方法的调用者)。静态同步方法锁的是类对象,类只会加载一次,所以静态同步方法永远锁的都是类对象(XXX.class)。synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为一下3种形式。
对于普通同步方法,锁是当前实例对象。对于静态同步方法,锁是当前类的Class对象。对于同步方法块,锁是synchronized括号里配置的对象。6、集合类不安全
List不安全
//java.util.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 底层是ReentrantLock实现 效率更高 灵活性也更高 List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); }, String.valueOf(i)).start(); } }}
Set不安全
import java.util.Set;import java.util.UUID;import java.util.concurrent.CopyOnWriteArraySet;//java.util.ConcurrentModificationExceptionpublic class SetTest { public static void main(String[] args) { //HashSet<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底层是什么?
HashSet底层是HashMap
public HashSet() { map = new HashMap<>();}// add() set 本质就是map 中的key, key是不可重复的!public boolean add(E e) { return map.put(e, PRESENT)==null;}// PRESENT 不变的值!private static final Object PRESENT = new Object();
Map不安全
回顾HashMap底层
public class MapTest { public static void main(String[] args) { /* 并发下 HashMap线程不安全 解决方案: 1.Map<String, Object> map = Collections.synchronizedMap(new HashMap<>()); 2.Map<String, Object> map = new ConcurrentHashMap<>(); */ //HashMap<String, Object> map = new HashMap<>(); //Map<String, Object> map = Collections.synchronizedMap(new HashMap<>()); Map<String, Object> 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(); } }}
7、Callable
1.可以有返回值
2.可以抛出异常
3.方法不同, run() -> call()
代码测试
public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { new Thread(new MyThread()).start(); /* 本身Callable接口和Runnable接口毫无关系 通过一个Runnable接口的实现类FutureTask,Callable接口与Runnable接口构建了关系,便可以启动线程 */ //适配类 FutureTask 是 Runnable接口的实现类 构造器 FutureTask(Callable<V> callable) MyThread1 t1 = new MyThread1(); FutureTask<Integer> futureTask = new FutureTask<>(t1); //泛型是线程返回值类型 /* 启动两个线程,只会打印一个`call()...` */ new Thread(futureTask,"A").start(); //怎么启动Callable new Thread(futureTask,"B").start(); //结果会被缓存,效率高 Integer i = futureTask.get(); //获取线程返回值 get()可能会产生阻塞!把他放到最后 或者 使用异步通信来处理! System.out.println(i); }}class MyThread implements Runnable{ @Override public void run() { }}/** * 泛型是返回值类型 */class MyThread1 implements Callable<Integer>{ @Override public Integer call(){ System.out.println("call()..."); //耗时的操作 return 1024; }}
注意:
1.有缓存
2.获取结果可能需要等待,会阻塞!
8、常用的辅助类(必会)
8.1 CountDownLatch(减法计数器)
/** * 计数器 */public class CountDownLatchDemo { 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() + "--->Go out"); countDownLatch.countDown(); // -1 }, String.valueOf(i)).start(); } countDownLatch.await(); //等待计数器归零在向下执行 System.out.println("Close Door!"); }}
原理:
countDownLatch.countDown(); // 数量-1
countDownLatch.await(); // 等待计数器归0,再向下执行
每次有线程调用 countDown() 数量-1,当计数器变为0,countDownLatch.await()就会被唤醒,继续往下执行!
8.2 CyclicBarrier(加法计数器)
public class CyclicBarrierDemo { public static void main(String[] args) { /* 集齐7颗龙珠召唤神龙 */ //召唤神龙的线程 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 (Exception e) { e.printStackTrace(); } }).start(); } }}
8.3 Semaphore(信号量)
抢车位!
6车 - 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 { //acquire() 获得 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "抢到车位"); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "离开车位"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); //release() 释放 } },String.valueOf(i)).start(); } }}
原理:
semaphore.acquire(); //获得,如果已经满了,等待,等待被释放为止!
semaphore.release(); // 释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
9、读写锁
写锁:独占锁
读锁:共享锁
读写锁:一个资源可以被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读是共享的
ReadWriteLock
public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 1; i <= 5; i++) { final int temp = i; //写入 new Thread(() -> { myCache.put(temp + "", temp + ""); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int temp = i; //读取 new Thread(() -> { System.out.println("获取" + temp + "缓存数据-> " + 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); try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + "写入OK"); } /** * 取 读 */ public Object get(String key) { System.out.println(Thread.currentThread().getName() + "读取" + key); Object o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取OK"); return o; }}
运行结果:
结果分析:由于是多线程操作,多个线程同时开启。写线程写入数据可能花费一些时间,此时数据还没写入完成,读线程就开始读数据导致读取不到任何数据,这种情况需要加锁控制。
加读写锁:
/** * 独占锁(写锁) 一次只能被一个线程占有 * 共享锁(读锁) 多个线程可以同时占有 * * ReadWriteLock * 读-读 可以共存! * 读-写 不可共存! * 写-写 不可共存! */public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 1; i <= 5; i++) { final int temp = i; //写入 new Thread(() -> { myCache.put(temp + "", temp + ""); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int temp = i; //读取 new Thread(() -> { System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + "")); }, String.valueOf(i)).start(); } }}/** * 自定义缓存 */class MyCache { private volatile Map<String, Object> map = new HashMap<>(); private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * 存 写 */ public void put(String key, Object value) { readWriteLock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + "写入" + key); try { TimeUnit.MILLISECONDS.sleep(200); map.put(key, value); } catch (InterruptedException e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } System.out.println(Thread.currentThread().getName() + "写入OK"); } /** * 取 读 */ public Object get(String key) { readWriteLock.readLock().lock(); Object o = null; try { System.out.println(Thread.currentThread().getName() + "读取" + key); o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取OK"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } return o; }}
此时结果是正确的
结果分析:由于加了读写锁,写锁是独占锁,读锁是共享锁。因此写的过程中不允许有任何操作,当写操作写完之后,可以多个线程共享读。
锁降级:写锁会降级为读锁,读锁不能升级为写锁。
10、阻塞队列
BlockingQueue |
---|
什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
学会使用队列
添加、移除
四组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() { //队列的大小 ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3); System.out.println(arrayBlockingQueue.add("a")); System.out.println(arrayBlockingQueue.add("b")); System.out.println(arrayBlockingQueue.add("c")); //java.lang.IllegalStateException: Queue full 抛出异常 //System.out.println(arrayBlockingQueue.add("d")); System.out.println("**************"); System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); //java.util.NoSuchElementException 抛出异常 //System.out.println(arrayBlockingQueue.remove()); }
/** *有返回值,不抛出异常 */ public static void test2(){ ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3); System.out.println(arrayBlockingQueue.offer("a")); System.out.println(arrayBlockingQueue.offer("b")); System.out.println(arrayBlockingQueue.offer("c")); //System.out.println(arrayBlockingQueue.offer("d")); // false 不抛出异常! System.out.println("*****"); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); // null 不抛出异常! }
/** *等待,阻塞(一直阻塞) */ public static void test3() throws InterruptedException { ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3); //一直阻塞 arrayBlockingQueue.put("a"); arrayBlockingQueue.put("b"); arrayBlockingQueue.put("c"); //arrayBlockingQueue.put("d"); //队列没有位置了,一直阻塞 System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); //没有数据了,一直阻塞 }
/** *等待,阻塞(等待超时) */ public static void test4() throws InterruptedException { ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3); arrayBlockingQueue.offer("a"); arrayBlockingQueue.offer("b"); arrayBlockingQueue.offer("c"); arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS); //等待两秒,超时退出 System.out.println("******"); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); arrayBlockingQueue.poll(2, TimeUnit.SECONDS); //等待两秒,超时退出 }
SynchronousQueue 同步队列
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put()、take()
/** * 同步队列 * SynchronousQueue 和 BlockingQueue 不一样,SynchronousQueue 不存储元素 * put一个元素,必须从里面take取出来,否则不能再put进去值!(存一个,取一个,循环) */public class SynchronousQueueDemo { public static void main(String[] args) { //存一个元素,取一个元素 循环 SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>(); new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "->put 1"); synchronousQueue.put("1"); System.out.println(Thread.currentThread().getName() + "->put 2"); synchronousQueue.put("2"); System.out.println(Thread.currentThread().getName() + "->put 3"); synchronousQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } }, "T1").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } }, "T2").start(); }}
11、线程池(重点)
线程池:三大方式、七大参数、四种拒绝策略
池化技术
程序运行,本质:占用系统资源!优化资源的使用!=> 池化技术
线程池、连接池、内存池、对象池…
创建、销毁,十分浪费资源。
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
线程池的好处
1、降低资源的消耗;
2、提高响应的速度;
3、方便线程管理。
线程复用、可以控制最大并发数、管理线程
线程池:三大方法
/** * Executors 工具类:创建线程池 3大方法 */public class Demo1 { public static void main(String[] args) { //ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程的线程池 //ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程的线程池 ExecutorService threadPool = Executors.newCachedThreadPool();//创建可伸缩线程池 try { for (int i = 0; i < 10; i++) { //使用了线程池之后,用线程池来创建线程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " OK"); }); } } 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, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());} //本质:ThreadPoolExecutor() public ThreadPoolExecutor(int corePoolSize, //核心线程池大小 int maximumPoolSize, //最大核心线程池大小 long keepAliveTime, //超时了没有人调用就会释放 TimeUnit unit, //超时单位 BlockingQueue<Runnable> workQueue, //阻塞队列 ThreadFactory threadFactory, //线程工厂:创建线程,一般不用动 RejectedExecutionHandler handler) { //拒绝策略 if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.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; }
手动创建一个线程池
/** * Executors 工具类:创建线程池 3大方法 * * 4大拒绝策略: * new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常 * new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里! * new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常 * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常! * */public class Demo2 { public static void main(String[] args) { //工具类创建 //ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程的线程池 //ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程的线程池 //ExecutorService threadPool = Executors.newCachedThreadPool();//创建可伸缩线程池 //手动创建线程池 ThreadPoolExecutor ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试和最早的竞争,也不会抛出异常! try { //最大承载:Queue + max //超出最大承载抛出RejectedExecutionException 异常 (默认拒绝策略) for (int i = 0; i < 9; i++) { //使用了线程池之后,用线程池来创建线程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " OK"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完,程序结束,关闭线程池 threadPool.shutdown(); } }}
四种拒绝策略
* 4大拒绝策略: * new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常 * new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里! * new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常 * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!
小结与扩展
线程池最大线程数如何设置?
了解:IO密集型,CPU密集型(调优)
public class Demo1 { public static void main(String[] args) { //手动创建线程池 ThreadPoolExecutor //最大线程到底如何定义? //1、CPU 密集型 电脑处理器数是几,就是几,可以保证CPU的效率最高! //2、IO 密集型 大于 程序中十分耗IO的线程数 ---> 程序中 15个大型任务 io十分占用资源! =》 30 //获取CPU核数 电脑处理器数 System.out.println(Runtime.getRuntime().availableProcessors()); ExecutorService threadPool = new ThreadPoolExecutor( 2, Runtime.getRuntime().availableProcessors(), 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试和最早的竞争,也不会抛出异常! try { //最大承载:Queue + max //超出最大承载 RejectedExecutionException (默认拒绝策略) for (int i = 0; i < 9; i++) { //使用了线程池之后,用线程池来创建线程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " OK"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完,程序结束,关闭线程池 threadPool.shutdown(); } }}
12、四大函数式接口(必须掌握)
新时代程序员:lambda表达式(本质就是函数式接口的实例)、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个抽象方法的接口
//此注解用来判断该接口是否是函数式接口@FunctionalInterfacepublic interface Runnable { public abstract void run();}//简化编程模型,在新版本的框架底层大量应用!
Function函数型接口 |
---|
代码测试
/** * Function 函数型接口 有一个输入,有一个输出 * 只要是 函数式接口 可以用 lambda表达式简化 */public class Demo01 { public static void main(String[] args) { //输出输入的值// Function<String ,String > fun = new Function<String ,String >() {// @Override// public String apply(String str) {// return str;// }// }; Function<String ,String > fun = (str) -> str; //lambda表达式 System.out.println(fun.apply("123")); }}
Predicate断定型接口 |
---|
代码测试
/** * Predicate 断定型接口 有一个输入值 返回值是布尔值! */public class Demo02 { public static void main(String[] args) { //判断字符串是否为空// Predicate<String> predicate = new Predicate<String>() {// @Override// public boolean test(String str) {// return "".equals(str);// }// }; Predicate<String> predicate = str -> "".equals(str); System.out.println(predicate.test("")); System.out.println(predicate.test("123")); }}
Consumer消费型接口 |
---|
代码测试
/** * Consumer 消费型接口 只接收参数,不返回值 */public class Demo03 { public static void main(String[] args) { //接收参数,将其打印出来// Consumer<String> consumer = new Consumer<String>() {// @Override// public void accept(String str) {// System.out.println(str);// }// }; Consumer<String> consumer = str -> System.out.println(str); consumer.accept("hello"); }}
Supplier供给型接口 |
---|
代码测试
/** * Supplier 供给型接口 不需参数,有返回值 */public class Demo04 { public static void main(String[] args) {// Supplier<String> supplier = new Supplier<String>() {// @Override// public String get() {// return "world";// }// }; Supplier<String> supplier = () -> "world"; System.out.println(supplier.get()); }}
13、Stream流式计算
什么是Stream流式计算
大数据:存储 + 计算
存储:集合、MySQL
计算:流式计算~
public class Test { public static void main(String[] args) { User user1 = new User(1, "a", 21); User user2 = new User(2, "b", 22); User user3 = new User(3, "c", 23); User user4 = new User(4, "d", 24); User user5 = new User(5, "e", 25); User user6 = new User(6, "f", 26); //存储交给集合 List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6); //计算交给Stream流 //lambda表达式、链式编程、函数式接口、Stream流式计算 list.stream() .filter(user -> user.getId() % 2 == 0) //找出id为偶数的用户 .filter(user -> user.getAge() > 23) //年龄大于23岁 .map(user -> user.getName().toUpperCase()) //用户名 转为大写 .sorted((u1, u2) -> -u1.compareTo(u2)) //用户名字母到着排序 .limit(1) //只输出一个用户 .forEach(System.out::println); }}
14、ForkJoin
什么是ForkJoin
ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!
大数据中:MapReduce 核心思想->把大任务拆分为小任务!
ForkJoin 特点: 工作窃取!
实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!(里面维护的都是双端队列)
Class ForkJoinPool的使用
ForkJoinTask
ForkJoin的计算类:
/** * 求和计算的任务 * * 如何使用 ForkJoin? * 1.ForkJoinPool 通过它来执行 * 2.计算任务 forkJoinPool.execute(ForkJoinTask<?> task) * 3.计算类要继承ForkJoinTask */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); task1.fork(); //拆分任务,把线程任务压入线程队列 ForkJoinDemo task2 = new ForkJoinDemo(middle, end); task2.fork(); //拆分任务,把线程任务压入线程队列 //结果汇总 return task1.join() + task2.join(); } }}
测试类:
/** * 三六九等 三 六(ForkJoin) 九(Stream并行流) */public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { //test1(); // 419 //test2(); test3();//234 } //普通程序员 public static void test1() { long sum = 0L; long start = System.currentTimeMillis(); for (long i = 0L; i <= 10_0000_0000; i++) { sum += i; } long end = System.currentTimeMillis(); System.out.println("sum = " + sum + " 时间:" + (end - start)); } //会使用forkJoin public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L); ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务 Long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println("sum = " + sum + "时间:" + (end - start)); } public static void test3() { long start = System.currentTimeMillis(); //Stream并行流计算 [] long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("sum = " + sum + " 时间:" + (end - start)); }}
.parallel().reduce(0, Long::sum) 使用一个并行流去计算,提高效率。(并行计算归约求和)
15、异步回调
Future 设计的初衷:对将来的某个事件结果进行建模!
线程异步调用通常使用CompletableFuture类
(1)没有返回值的runAsync异步回调
//没有返回值的 runAsync 异步回调 CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " runAsync => Void"); }); System.out.println("11111"); completableFuture.get(); //阻塞,获取执行结果
(2)有返回值的 supplyAsync 异步回调
/** * 类似异步调用:Ajax * * 异步调用:CompletableFuture * 成功回调 * 失败回调 */public class Demo02 { public static void main(String[] args) throws ExecutionException, InterruptedException { //有返回值的 supplyAsync 异步回调 //成功和失败回调 //返回的是错误信息 CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + " supplyAsync => Integer"); int i = 10 / 0; return 1024; }); //成功回调 System.out.println(completableFuture.whenComplete((t, u) -> { System.out.println("t=>" + t); //正常的返回结果 System.out.println("u=>" + u); //错误信息 java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero }).exceptionally((e -> {//失败回调 System.out.println(e.getMessage()); return 233; // 可以获取到错误的返回结果 })).get()); }}
16、JMM
请你谈谈你对 volatile 的理解
volatile 是Java虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM
JMM:Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
线程中分为 工作内存、主内存
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁
8种操作
read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;JMM对这8种操作给了相应的规定:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题:程序A不知道主内存中的值发生了变化
17、volatile
1)保证可见性
public class JMMDemo { // 如果不加volatile 程序会死循环 // 加了volatile是可以保证可见性的,volatile保证一旦数据被修改,其它线程立马能够感知到 private volatile static int num = 0; public static void main(String[] args) { // main 线程 new Thread(()->{ // 线程1 不知道主内存中的值发生了变化 while (num == 0){ } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num = 1; System.out.println(num); }}
2)不保证原子性
原子性:不可分割
线程A在执行任务的时候,是不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
/** * 不保证原子性 */public class VDemo2 { // volatile 不保证原子性 private volatile static int num = 0; public static void add() { num++; } public static void main(String[] args) { // 20个线程,每个线程调用100次 理论值 2万 for (int i = 0; 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); }}
如果不加lock和synchronized ,怎么样保证原子性?
使用原子类,解决原子性问题
public class VDemo2 { // volatile 不保证原子性 // 原子类的 Integer private volatile static AtomicInteger num = new AtomicInteger(); public static void add() { //num++; //不是原子性操作 num.getAndIncrement(); // +1 操作 底层是CAS保证的原子性 } public static void main(String[] args) { // 20个线程,每个线程调用100次 理论值 2万 for (int i = 0; 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); }}
原子类的底层都直接和操作系统挂钩!在内存中修改值!
Unsafe类是一个很特殊的存在
3)禁止指令重排
指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的。
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1int y=2; //2x=x+5; //3y=x*x; //4//我们期望的执行顺序是 1234 可能执行的顺序会变成2134 1324//可不可能是 4123? 不可能的
可能造成的影响结果:前提:a b x y这四个值 默认都是0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常结果: x = 0; y =0;
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
可能造成的诡异结果:x = 2; y = 1;(概率极低)
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:
1、保证特定的操作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
总结:
volatile 可以保证可见性;不能保证原子性;由于内存屏障,可以保证避免指令重排现像产生!
面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式
18、彻底玩转单例模式
饿汉式、DCL懒汉式
1)饿汉式
/** * 饿汉式单例 * 核心思想:构造器私有化 */public class Hungry { // 可能浪费内存空间 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 static final Hungry HUNGRY = new Hungry(); private Hungry(){} public static Hungry getInstance(){ return HUNGRY; }}
2)DCL懒汉式
/** * 懒汉式单例 * 道高一尺,魔高一丈! */public class LazyMan { private volatile static LazyMan lazyMan; private static boolean flag = false; private LazyMan() { synchronized (LazyMan.class) { if (!flag) { flag = true; } else { throw new RuntimeException("不要试图使用反射破坏异常!"); } } } //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (null == lazyMan) { synchronized (LazyMan.class) { if (null == lazyMan) { lazyMan = new LazyMan(); // 不是一个原子性操作 } } } return lazyMan; } //不加 synchronized 多线程情况下,不一定是单例 public static void main(String[] args) throws Exception {// for (int i = 0; i < 10; i++) {// new Thread(() -> {// LazyMan.getInstance();// }).start();// } //反射! //LazyMan instance = LazyMan.getInstance(); Field flag = LazyMan.class.getDeclaredField("flag"); flag.setAccessible(true); Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan instance = constructor.newInstance(); flag.set(instance,false); LazyMan instance1 = constructor.newInstance(); System.out.println(instance == instance1); } /* 创建对象的步骤: 1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个空间 123 132 线程A 线程B // 此时lazyMan还没有完成构造 */}
3)静态内部类
/** * 静态内部类 */public class Holder { private Holder(){ } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } public static Holder getInstance(){ return InnerClass.HOLDER; }}
单例不安全, 因为反射
4)枚举类
/** * enum 是什么? 本身也是一个Class类 */public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; }}class Test{ public static void main(String[] args) throws Exception { EnumSingle instance1 = EnumSingle.INSTANCE; //EnumSingle instance2 = EnumSingle.INSTANCE; Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); EnumSingle instance2 = constructor.newInstance(); //java.lang.NoSuchMethodException: com.lkl.singleton.EnumSingle. 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
大厂必须要研究底层!有所突破!修内功,基础不牢,地动山摇 操作系统、计算机网络原理
public class CASDemo { //CAS compareAndSet:比较并交换! public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2021); //期望、更新 //public final boolean compareAndSet(int expect, int update) //如果我期望的值达到了,就更新,否则,就不更新,CAS 是CPU的并发原语! System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); }}
Unsafe类
atomicInteger.getAndIncrement(); |
---|
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
循环会耗时;一次性只能保证一个共享变量的原子性;它会存在ABA问题CAS:ABA(狸猫换太子)
主内存中 A=1
线程1:期望值是1,要变成2;
线程2:两个操作:
期望值是1,变成3期望是3,变成1所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;线程1不知道A的值发生了修改!
public class CASDemo2 { //CAS compareAndSet:比较并交换! public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2021); //期望、更新 //public final boolean compareAndSet(int expect, int update) //如果我期望的值达到了,就更新,否则,就不更新,CAS 是CPU的并发原语! // ======================= 捣乱的线程 ============================== System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2022, 2021)); System.out.println(atomicInteger.get()); // ======================= 捣乱的线程 ============================== //======================== 期望的线程 ============================== System.out.println(atomicInteger.compareAndSet(2021, 6666)); System.out.println(atomicInteger.get()); }}
20、原子引用(AtomicReference)
解决ABA问题,引入原子引用!对应的思想:乐观锁!
带版本号的原子操作!
public class CASDemo3 { //CAS compareAndSet:比较并交换! public static void main(String[] args) { //AtomicStampedReference 泛型如果使用包装类,注意对象引用问题 //正常在业务操作,这里泛型都是一个个对象 AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1, 1); new Thread(() -> { int stamp = stampedReference.getStamp(); //获得版本号 System.out.println(Thread.currentThread().getName() + " 1 -> " + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(stampedReference.compareAndSet(1, 2, stampedReference.getStamp(), stampedReference.getStamp() + 1)); System.out.println(Thread.currentThread().getName() + " 2 -> " + stampedReference.getStamp()); System.out.println(stampedReference.compareAndSet(2, 1, stampedReference.getStamp(), stampedReference.getStamp() + 1)); System.out.println(Thread.currentThread().getName() + " 3 -> " + stampedReference.getStamp()); }, "a").start(); new Thread(() -> { int stamp = stampedReference.getStamp(); //获得版本号 System.out.println(Thread.currentThread().getName() + " 1 -> " + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(stampedReference.compareAndSet(1, 6, stampedReference.getStamp(), stampedReference.getStamp() + 1)); System.out.println(Thread.currentThread().getName() + " 2 -> " + stampedReference.getStamp()); }, "b").start(); try { TimeUnit.SECONDS.sleep(3); System.out.println("**************"); System.out.println(stampedReference.getReference()); } catch (InterruptedException e) { e.printStackTrace(); } }}
注意:
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间
整型包装类 |
---|
21、各种锁的理解
1、公平锁、非公平锁
公平锁:非常公平,不能插队,必须先来后到!(效率可能较低)
非公平锁:非常不公平,可以插队(默认都是非公平,效率较高)
public ReentrantLock() { sync = new NonfairSync(); }
带参构造器,可以修改公平状态
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
2、可重入锁
可重入锁(递归锁):拿到外边的锁后,会自动拿到里面的锁(synchronized【隐式】和Lock【显式】都是可重入锁)
synchronized版
public class Demo01 { 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版
public class Demo02 { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); }}class Phone2{ private final Lock lock = new ReentrantLock(); public void sms(){ lock.lock(); //Lock锁必须配对,有加锁就必须有解锁! 否则就会死锁! try { System.out.println(Thread.currentThread().getName() + " sms"); call(); } finally { lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + " call"); } finally { lock.unlock(); } }}
3、自旋锁
spinlock
自定义自旋锁:
public class spinlock { // int 0 // Thread null AtomicReference<Thread> atomicReference = new AtomicReference<>(); //加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + " ==> myLock"); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ } } //解锁 public void myUnLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + " ==> myUnLock"); atomicReference.compareAndSet(thread,null); }}
测试自定义自旋锁:
public class TestSpinLock { public static void main(String[] args) {// Lock lock = new ReentrantLock();// lock.lock();// lock.unlock(); // 底层使用的自旋锁CAS spinlock spinlock = new spinlock(); new Thread(() -> { spinlock.myLock(); try { TimeUnit.SECONDS.sleep(4); } catch (Exception e) { e.printStackTrace(); } finally { spinlock.myUnLock(); } }, "T1").start(); new Thread(() -> { spinlock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlock.myUnLock(); } }, "T2").start(); }}
输出结果 |
---|
结果分析:
两个线程共同操作一把锁,谁先拿到锁谁先执行。T1线程先拿到锁加锁,其次是T2线程,先输入第一行再输出第二行;T1线程4s后释放锁,随之T2线程拿到锁加锁进行操作,3s后释放锁。故:先输入第一二行,4s后输出第三行,3s后输出第四行。
4、死锁
什么是死锁?两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法在执行下去。
两个线程拿着自己锁不放的同时,试图获取对方的锁,就会造成死锁。
死锁测试,怎么排除死锁!
/** * 死锁样例 */public class DeadLockDemo { static Object a = new Object(); static Object b = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (a) { System.out.println(Thread.currentThread().getName() + " 获取到锁a,试图获取锁b"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (b) { System.out.println("获取到锁b"); } } }, "A").start(); new Thread(() -> { synchronized (b) { System.out.println(Thread.currentThread().getName() + " 获取到锁b,试图获取锁a"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (a) { System.out.println("获取到锁a"); } } }, "B").start(); }}
产生死锁的原因:
第一:系统资源不足
第二:进程运行推进顺序不合适
第三:资源分配不当
如何定位死锁,解决问题?
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack
进程进程号 找到死锁信息(jstack是jvm中自带的堆栈跟踪工具)
命令:jstack 进程号
面试,工作中!如何排查问题?
1、日志 90%
2、堆栈信息 10%