17 - JUC并发编程
1. 什么是JUC
java.util.concurrent
2. 进程和线程回顾
2.1 线程 & 进程
程序:是指令和数据的有序集合,其本身没有任何运行的意义,是一个静态的概念。
进程:是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位。
线程:通常一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
Java默认有2个线程:main、gc;
Java开启线程的3种方式:Thread、Runnable、Callable
Java本身其实是开启不了线程的,Java是运行在虚拟机上的,无法直接操作硬件。
2.2 并发 & 并行
并发:多线程操作同一个资源
-
CPU单核,模拟出多条线程,快速交替
并行:
-
CPU多核,多个线程可以同时执行
// 获取CPU的核数 Runtime.getRuntime().availableProcessors();
并发编程的本质:充分利用CPU的资源
2.3 线程的状态
public enum State { // 新生 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待 WAITING, // 超时等待 TIMED_WAITING, // 终止 TERMINATED; }
2.4 wait&sleep函数的区别
-
来自不同的类
wait -> Object
sleep -> Thread
-
关于锁的释放
wait会释放锁,而sleep不会释放
-
使用的范围是不同的
wait必须在同步代码块中使用,而sleep可以在任何地方使用
-
是否需要捕获异常
wait不需要捕获异常,而sleep必须捕获异常
3. Lock锁(重点)
3.1 传统Synchronized
本质:队列、锁
1 package com.wang.demo01; 2 3 // 卖票实例 4 // 线程就是一个单独的资源类,没有任何的附属操作! 5 public class SaleTicketDemo01 { 6 public static void main(String[] args) { 7 Ticket ticket = new Ticket(); 8 new Thread(()->{ 9 for (int i = 0; i < 50; i++) { 10 ticket.sale(); 11 } 12 }, "A").start(); 13 new Thread(()->{ 14 for (int i = 0; i < 50; i++) { 15 ticket.sale(); 16 } 17 }, "B").start(); 18 new Thread(()->{ 19 for (int i = 0; i < 50; i++) { 20 ticket.sale(); 21 } 22 }, "C").start(); 23 } 24 } 25 26 // 线程资源类 OOP 27 class Ticket { 28 // 属性、方法 29 private int num = 50; 30 31 // 卖票方法 32 public synchronized void sale(){ 33 if (num>0){ 34 System.out.println(Thread.currentThread().getName() + "卖出了第" + (51-num) 35 + "张票,剩余" + --num + "张票。"); 36 } 37 } 38 }
3.2 Lock接口
Lock l = ...; l.lock(); // 加锁 try { // access the resource protected by this lock } finally { l.unlock(); // 解锁 }
ReentrantLock(可重入锁,常用),ReentrantReadWriteLock.ReadLock(读锁),ReentrantReadWriteLock.WriteLock(写锁)
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
公平锁:十分公平,先来后到
非公平锁:十分不公平,可以插队(默认)
1 package com.wang.demo01; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 // 使用可重入锁 7 public class SaleTicketDemo02 { 8 public static void main(String[] args) { 9 Ticket2 ticket2 = new Ticket2(); 10 11 new Thread(()->{for (int i = 0; i < 50; i++) {ticket2.sale();}}, "A").start(); 12 new Thread(()->{for (int i = 0; i < 50; i++) {ticket2.sale();}}, "B").start(); 13 new Thread(()->{for (int i = 0; i < 50; i++) {ticket2.sale();}}, "C").start(); 14 } 15 } 16 17 // Lock三部曲 18 // 1. new ReentrantLock(); 19 // 2. 加锁 lock.lock(); 20 // 3. finally -> 解锁 lock.unlock(); 21 class Ticket2{ 22 // 属性、方法 23 private int num = 50; 24 Lock lock = new ReentrantLock(); 25 26 // 卖票方法 27 public void sale(){ 28 lock.lock(); // 加锁 29 try { 30 // 业务代码 31 if (num>0){ 32 System.out.println(Thread.currentThread().getName() + "卖出了第" + (51-num) 33 + "张票,剩余" + --num + "张票。"); 34 } 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } finally { 38 lock.unlock(); // 解锁 39 } 40 } 41 }
3.3 Synchronized和Lock的区别
-
Synchronized是内置的Java关键字,Lock是一个Java类;
-
Synchronized无法判断获取锁的状态,Lock可以判断是否获取了锁;
-
Synchronized会自动释放锁,Lock必须手动释放锁,如果不释放可能会造成死锁问题;
-
Synchronized线程1获得锁并阻塞时,线程2会一直等待,Lock不一定会等待下去;
-
Synchronized可重入锁,不可以中断,非公平,Lock可重入锁,可以响应中断,公平或非公平可以自定义设置;
-
Synchronized适合锁少量的同步代码,Lock适合锁大量的同步代码。
注:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
4. 生产者与消费者
4.1 Synchronized版
1 package com.wang.producerconsumer; 2 3 public class synchronizedPC { 4 public static void main(String[] args) { 5 PC pc = new PC(); 6 7 new Thread(()->{ 8 for (int i = 0; i < 5; i++) { 9 pc.increment(); 10 } 11 }, "A").start(); 12 new Thread(()->{ 13 for (int i = 0; i < 5; i++) { 14 pc.decrement(); 15 } 16 }, "B").start(); 17 new Thread(()->{ 18 for (int i = 0; i < 5; i++) { 19 pc.increment(); 20 } 21 }, "C").start(); 22 new Thread(()->{ 23 for (int i = 0; i < 5; i++) { 24 pc.decrement(); 25 } 26 }, "D").start(); 27 } 28 } 29 30 class PC{ // 资源类 31 private int number = 0; 32 33 // +1 34 public synchronized void increment(){ 35 while (number!=0){ 36 try { 37 this.wait(); // 等待 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 } 42 number++; 43 System.out.println(Thread.currentThread().getName() + " -> " + number); 44 this.notifyAll(); // 通知 45 } 46 // -1 47 public synchronized void decrement(){ 48 while (number==0){ 49 try { 50 this.wait(); // 等待 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 number--; 56 System.out.println(Thread.currentThread().getName() + " -> " + number); 57 this.notifyAll(); // 通知 58 } 59 }
注:上述代码中的while语句不能替换成if语句,防止虚假唤醒
4.2 JUC版
Synchronized | Lock | |
---|---|---|
等待 | wait() | await() |
通知 | notify() | signal() |
1 package com.wang.producerconsumer; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 public class jucPC { 8 public static void main(String[] args) { 9 PC2 pc2 = new PC2(); 10 new Thread(()->{ 11 for (int i = 0; i < 5; i++) { 12 pc2.increment(); 13 } 14 }, "A").start(); 15 new Thread(()->{ 16 for (int i = 0; i < 5; i++) { 17 pc2.decrement(); 18 } 19 }, "B").start(); 20 new Thread(()->{ 21 for (int i = 0; i < 5; i++) { 22 pc2.increment(); 23 } 24 }, "C").start(); 25 new Thread(()->{ 26 for (int i = 0; i < 5; i++) { 27 pc2.decrement(); 28 } 29 }, "D").start(); 30 } 31 } 32 33 class PC2{ // 资源类 34 private int number = 0; 35 private Lock lock = new ReentrantLock(); 36 private Condition condition = lock.newCondition(); 37 38 // +1 39 public void increment(){ 40 lock.lock(); 41 try { 42 while (number!=0){ 43 condition.await(); // 等待 44 } 45 number++; 46 System.out.println(Thread.currentThread().getName() + " -> " + number); 47 condition.signalAll(); // 通知 48 } catch (Exception e) { 49 e.printStackTrace(); 50 } finally { 51 lock.unlock(); 52 } 53 } 54 // -1 55 public void decrement(){ 56 lock.lock(); 57 try { 58 while (number==0){ 59 condition.await(); // 等待 60 } 61 number--; 62 System.out.println(Thread.currentThread().getName() + " -> " + number); 63 condition.signalAll(); // 通知 64 } catch (Exception e) { 65 e.printStackTrace(); 66 } finally { 67 lock.unlock(); 68 } 69 } 70 }
Condition可以精准地通知和唤醒线程:
1 package com.wang.producerconsumer; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 // 精准唤醒,A唤醒B,B唤醒C,C唤醒A 8 public class jucPC2 { 9 public static void main(String[] args) { 10 PC3 pc3 = new PC3(); 11 12 new Thread(()->{ 13 for (int i = 0; i < 5; i++) { 14 pc3.AtoB(); 15 } 16 },"A").start(); 17 new Thread(()->{ 18 for (int i = 0; i < 5; i++) { 19 pc3.BtoC(); 20 } 21 },"B").start(); 22 new Thread(()->{ 23 for (int i = 0; i < 5; i++) { 24 pc3.CtoD(); 25 } 26 },"C").start(); 27 } 28 } 29 30 class PC3{ 31 private int number = 1; // 1A, 2B, 3C 32 private Lock lock = new ReentrantLock(); 33 private Condition condition1 = lock.newCondition(); 34 private Condition condition2 = lock.newCondition(); 35 private Condition condition3 = lock.newCondition(); 36 37 public void AtoB(){ 38 lock.lock(); 39 try { 40 while (number!=1){ 41 condition1.await(); 42 } 43 number = 2; 44 System.out.println(Thread.currentThread().getName() + ": A -> B"); 45 condition2.signal(); 46 } catch (Exception e) { 47 e.printStackTrace(); 48 } finally { 49 lock.unlock(); 50 } 51 } 52 53 public void BtoC(){ 54 lock.lock(); 55 try { 56 while (number!=2){ 57 condition2.await(); 58 } 59 number = 3; 60 System.out.println(Thread.currentThread().getName() + ": B -> C"); 61 condition3.signal(); 62 } catch (Exception e) { 63 e.printStackTrace(); 64 } finally { 65 lock.unlock(); 66 } 67 } 68 69 public void CtoD(){ 70 lock.lock(); 71 try { 72 while (number!=3){ 73 condition3.await(); 74 } 75 number = 1; 76 System.out.println(Thread.currentThread().getName() + ": C -> A"); 77 condition1.signal(); 78 } catch (Exception e) { 79 e.printStackTrace(); 80 } finally { 81 lock.unlock(); 82 } 83 } 84 }
5. 8锁现象
锁的8个问题
-
标准情况下,两个线程是先打印发短信还是打电话? 发短信
1 package com.wang.lock8; 2 3 // 两个方法是同一个对象调用的,用的是同一个锁,谁先拿到谁先执行 4 import java.util.concurrent.TimeUnit; 5 6 public class Test01 { 7 public static void main(String[] args) { 8 Phone phone = new Phone(); 9 10 new Thread(()->{ 11 phone.sendmsg(); 12 },"A").start(); 13 14 try { 15 TimeUnit.SECONDS.sleep(1); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 20 new Thread(()->{ 21 phone.call(); 22 },"B").start(); 23 } 24 } 25 26 class Phone{ 27 // synchronized锁的对象是方法的调用者! 28 public synchronized void sendmsg(){ 29 System.out.println("发短信!"); 30 } 31 32 public synchronized void call(){ 33 System.out.println("打电话!"); 34 } 35 }
-
sendmsg延迟4秒,两个线程先打印发短信还是打电话? 发短信
1 package com.wang.lock8; 2 3 // 两个方法是同一个对象调用的,用的是同一个锁,谁先拿到谁先执行 4 import java.util.concurrent.TimeUnit; 5 6 public class Test01 { 7 public static void main(String[] args) { 8 Phone phone = new Phone(); 9 10 new Thread(()->{ 11 phone.sendmsg(); 12 },"A").start(); 13 14 try { 15 TimeUnit.SECONDS.sleep(1); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 20 new Thread(()->{ 21 phone.call(); 22 },"B").start(); 23 } 24 } 25 26 class Phone{ 27 // synchronized锁的对象是方法的调用者! 28 public synchronized void sendmsg(){ 29 try { 30 TimeUnit.SECONDS.sleep(4); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 System.out.println("发短信!"); 35 } 36 37 public synchronized void call(){ 38 System.out.println("打电话!"); 39 } 40 }
-
增加了一个普通方法(没有synchronized修饰),先打印发短信还是Hello? 普通方法(Hello)
1 package com.wang.lock8; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Test02 { 6 public static void main(String[] args) { 7 Phone2 phone = new Phone2(); 8 9 new Thread(()->{ 10 phone.sendmsg(); 11 },"A").start(); 12 13 try { 14 TimeUnit.SECONDS.sleep(1); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 19 new Thread(()->{ 20 phone.hello(); 21 },"B").start(); 22 } 23 } 24 25 class Phone2{ 26 public synchronized void sendmsg(){ 27 try { 28 TimeUnit.SECONDS.sleep(4); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 System.out.println("发短信!"); 33 } 34 35 public synchronized void call(){ 36 System.out.println("打电话!"); 37 } 38 39 // 没有锁,不是同步方法,不受锁的影响 40 public void hello(){ 41 System.out.println("Hello!"); 42 } 43 }
-
两个对象,两个同步方法, 先打印发短信还是打电话? 打电话
1 package com.wang.lock8; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Test02 { 6 public static void main(String[] args) { 7 // 两个对象,两个调用者,两把锁! 8 Phone2 phone1 = new Phone2(); 9 Phone2 phone2 = new Phone2(); 10 11 new Thread(()->{ 12 phone1.sendmsg(); 13 },"A").start(); 14 15 try { 16 TimeUnit.SECONDS.sleep(1); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 new Thread(()->{ 22 phone2.call(); 23 },"B").start(); 24 } 25 } 26 27 class Phone2{ 28 public synchronized void sendmsg(){ 29 try { 30 TimeUnit.SECONDS.sleep(4); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 System.out.println("发短信!"); 35 } 36 37 public synchronized void call(){ 38 System.out.println("打电话!"); 39 } 40 }
-
两个静态的同步方法,只有一个对象,先打印发短信还是打电话? 发短信
1 package com.wang.lock8; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Test03 { 6 public static void main(String[] args) { 7 Phone3 phone = new Phone3(); 8 9 new Thread(()->{ 10 phone.sendmsg(); 11 },"A").start(); 12 13 try { 14 TimeUnit.SECONDS.sleep(1); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 19 new Thread(()->{ 20 phone.call(); 21 },"B").start(); 22 } 23 } 24 25 // Phone3只有唯一的一个class对象 26 class Phone3{ 27 // 静态方法,类一加载就有了,锁的是Class 28 public static synchronized void sendmsg(){ 29 try { 30 TimeUnit.SECONDS.sleep(4); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 System.out.println("发短信!"); 35 } 36 37 public static synchronized void call(){ 38 System.out.println("打电话!"); 39 } 40 }
-
两个静态的同步方法,两个对象,先打印发短信还是打电话? 发短信
1 package com.wang.lock8; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Test03 { 6 public static void main(String[] args) { 7 // 两个对象的class类模板只有一个,锁的是class 8 Phone3 phone1 = new Phone3(); 9 Phone3 phone2 = new Phone3(); 10 11 new Thread(()->{ 12 phone1.sendmsg(); 13 },"A").start(); 14 15 try { 16 TimeUnit.SECONDS.sleep(1); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 new Thread(()->{ 22 phone2.call(); 23 },"B").start(); 24 } 25 } 26 27 // Phone3只有唯一的一个class对象 28 class Phone3{ 29 // 静态方法,类一加载就有了,锁的是Class 30 public static synchronized void sendmsg(){ 31 try { 32 TimeUnit.SECONDS.sleep(4); 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 System.out.println("发短信!"); 37 } 38 39 public static synchronized void call(){ 40 System.out.println("打电话!"); 41 } 42 }
-
一个静态同步方法,一个普通同步方法,一个对象,先打印发短信还是打电话? 打电话
1 package com.wang.lock8; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Test04 { 6 public static void main(String[] args) { 7 Phone4 phone = new Phone4(); 8 9 new Thread(()->{ 10 phone.sendmsg(); 11 },"A").start(); 12 13 try { 14 TimeUnit.SECONDS.sleep(1); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 19 new Thread(()->{ 20 phone.call(); 21 },"B").start(); 22 } 23 } 24 25 // Phone3只有唯一的一个class对象 26 class Phone4{ 27 // 静态同步方法,类一加载就有了,锁的是Class 28 public static synchronized void sendmsg(){ 29 try { 30 TimeUnit.SECONDS.sleep(4); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 System.out.println("发短信!"); 35 } 36 37 // 普通同步方法,锁的调用者 38 public synchronized void call(){ 39 System.out.println("打电话!"); 40 } 41 }
-
一个静态同步方法,一个普通同步方法,两个对象,先打印发短信还是打电话? 打电话
1 package com.wang.lock8; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Test04 { 6 public static void main(String[] args) { 7 Phone4 phone1 = new Phone4(); 8 Phone4 phone2 = new Phone4(); 9 10 new Thread(()->{ 11 phone1.sendmsg(); 12 },"A").start(); 13 14 try { 15 TimeUnit.SECONDS.sleep(1); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 20 new Thread(()->{ 21 phone2.call(); 22 },"B").start(); 23 } 24 } 25 26 // Phone3只有唯一的一个class对象 27 class Phone4{ 28 // 静态同步方法,类一加载就有了,锁的是Class 29 public static synchronized void sendmsg(){ 30 try { 31 TimeUnit.SECONDS.sleep(4); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 System.out.println("发短信!"); 36 } 37 38 // 普通同步方法,锁的调用者 39 public synchronized void call(){ 40 System.out.println("打电话!"); 41 } 42 }
小结:
-
new:锁的是具体的一个对象;
-
static:锁的是Class,唯一的一个模板。
6. 集合类不安全
6.1 List不安全
ArrayList不安全示例:
1 package com.wang.unsafe; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.UUID; 6 7 // java.util.ConcurrentModificationException,出现并发修改异常 8 public class ListTest { 9 public static void main(String[] args) { 10 // 并发下ArrayList是不安全的 11 List<String> list = new ArrayList<>(); 12 13 for (int i = 1; i <= 10; i++) { 14 new Thread(()->{ 15 list.add(UUID.randomUUID().toString().substring(0,5)); 16 System.out.println(list); 17 }).start(); 18 } 19 } 20 }
ArrayList安全问题解决方案:
1 package com.wang.unsafe; 2 3 import java.util.*; 4 import java.util.concurrent.CopyOnWriteArrayList; 5 6 public class ListTest { 7 public static void main(String[] args) { 8 /* 9 * 解决方案: 10 * 1. 使用Vector解决:List<String> list = new Vector<>(); 11 * 但是List(JDK1.2)实际上是出现在Vector(JDK1.0)之后的 12 * 2. List<String> list = Collections.synchronizedList(new ArrayList<>()); 13 * 3. List<String> list = new CopyOnWriteArrayList<>(); 14 * CopyOnWrite: 写入时复制,计算机程序设计领域的一种优化策略 15 * 多个线程调用时,读取list是固定的,但写入list存在覆盖的情况 16 * 在写入的时候复制可以避免覆盖造成的数据问题! 17 * 问:3比1哪里更好? 18 * 答:Vector使用的synchronized,效率较低;CopyOnWrite使用的lock锁 19 */ 20 List<String> list = new CopyOnWriteArrayList<>(); 21 22 for (int i = 1; i <= 10; i++) { 23 new Thread(()->{ 24 list.add(UUID.randomUUID().toString().substring(0,5)); 25 System.out.println(list); 26 }).start(); 27 } 28 } 29 }
6.2 Set不安全
HashSet不安全示例:
1 package com.wang.unsafe; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 import java.util.UUID; 6 7 // java.util.ConcurrentModificationException 8 public class SetTest { 9 public static void main(String[] args) { 10 Set<String > set = new HashSet<>(); 11 12 for (int i = 1; i <= 30; i++) { 13 new Thread(()->{ 14 set.add(UUID.randomUUID().toString().substring(0,5)); 15 System.out.println(set); 16 }, String.valueOf(i)).start(); 17 } 18 } 19 }
HashSet安全问题解决方案:
1 package com.wang.unsafe; 2 3 import java.util.Collections; 4 import java.util.HashSet; 5 import java.util.Set; 6 import java.util.UUID; 7 import java.util.concurrent.CopyOnWriteArraySet; 8 9 /* 10 * 解决方案: 11 * 1. Set<String > set = Collections.synchronizedSet(new HashSet<>()); 12 * 2. Set<String > set = new CopyOnWriteArraySet<>(); 13 */ 14 public class SetTest { 15 public static void main(String[] args) { 16 // Set<String > set = new HashSet<>(); 17 // Set<String > set = Collections.synchronizedSet(new HashSet<>()); 18 Set<String > set = new CopyOnWriteArraySet<>(); 19 20 for (int i = 1; i <= 30; i++) { 21 new Thread(()->{ 22 set.add(UUID.randomUUID().toString().substring(0,5)); 23 System.out.println(set); 24 }, String.valueOf(i)).start(); 25 } 26 } 27 }
HashSet的底层是什么? HashMap
1 public HashSet() { 2 map = new HashMap<>(); 3 } 4 5 // add,set本质就是map,key是无法重复的! 6 public boolean add(E e) { 7 return map.put(e, PRESENT)==null; 8 } 9 10 private static final Object PRESENT = new Object(); // 不变的值!
6.3 Map不安全
Map基本操作
/**默认初始容量 * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /**默认加载因子 * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap不安全示例:
1 package com.wang.unsafe; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.UUID; 6 7 // java.util.ConcurrentModificationException 8 public class MapTest { 9 public static void main(String[] args) { 10 // HashMap 加载因子、初始化容量 11 // map是这样用的吗? 不是,工作中不用HashMap 12 // 默认等价于什么? new HashMap<>(16,0.75); 13 Map<String, String> map = new HashMap<>(); 14 15 for (int i = 1; i <= 30; i++) { 16 new Thread(()->{ 17 map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); 18 System.out.println(map); 19 }, String.valueOf(i)).start(); 20 } 21 } 22 }
HashMap安全问题解决方案:
1 package com.wang.unsafe; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.UUID; 6 import java.util.concurrent.ConcurrentHashMap; 7 8 public class MapTest { 9 public static void main(String[] args) { 10 // Map<String, String> map = new HashMap<>(); 11 Map<String, String> map = new ConcurrentHashMap<>(); 12 13 for (int i = 1; i <= 30; i++) { 14 new Thread(()->{ 15 map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); 16 System.out.println(map); 17 }, String.valueOf(i)).start(); 18 } 19 } 20 }
7. Callable
Callable接口类似于Runnable,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,Runnable不返回结果,也不能抛出被检查的异常。
-
可以有返回值;
-
可以抛出异常;
-
方法不同,run()/call()。
1 package com.wang.callable; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class CallableTest { 8 public static void main(String[] args) throws ExecutionException, InterruptedException { 9 // new Thread(new MyThread()).start(); // Runnable使用方法 10 /*如何通过new Thread().start()来启动Callable?推导过程如下: 11 * new Thread(new Runnable()).start(); 12 * new Thread(new FutureTask<V>()).start(); FutureTask<V>是Runnable的实现类,作为适配类 13 * new Thread(new FutureTask<V>(Callable)).start(); Callable作为FutureTask<V>的构造函数参数 14 */ 15 MyThread thread = new MyThread(); 16 FutureTask futureTask = new FutureTask(thread); 17 new Thread(futureTask, "A").start(); 18 new Thread(futureTask, "A").start(); // 结果会被缓存,效率高,只打印一个“function call()” 19 20 Integer re = (Integer) futureTask.get(); // 获取Callable的返回结果;get方法可能会阻塞,可能需要等待 21 System.out.println(re); 22 } 23 } 24 25 class MyThread implements Callable<Integer> { 26 27 @Override 28 public Integer call() { 29 System.out.println("function call()"); 30 return 970901; 31 } 32 }
8. 常用的辅助类(必会)
8.1 CountDownLatch
一个减法计数器。允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
1 package com.wang.helpclass; 2 3 import java.util.concurrent.CountDownLatch; 4 5 // 减法计数器 6 public class CountDownLatchDemo { 7 public static void main(String[] args) throws InterruptedException { 8 // 计数总数为6 9 CountDownLatch countDownLatch = new CountDownLatch(6); 10 11 for (int i = 1; i <= 6; i++) { 12 new Thread(()->{ 13 System.out.println(Thread.currentThread().getName() + " -> Go Out"); 14 countDownLatch.countDown(); // 计数减一 15 }, String.valueOf(i)).start(); 16 } 17 18 countDownLatch.await(); // 等待计数器归零,然后再向下执行 19 System.out.println("Close the Door!"); 20 } 21 } 22 // 结果: 23 1 -> Go Out 24 6 -> Go Out 25 5 -> Go Out 26 4 -> Go Out 27 3 -> Go Out 28 2 -> Go Out 29 Close the Door!
解释:
countDownLatch.countDown(); 计数减1
countDownLatch.await(); 等待计数器归零,然后再向下执行
每次有线程调用countDown()方法是数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行之后的代码。
8.2 CyclicBarrier
一个加法计数器。允许一组线程全部等待彼此达到共同屏障点的同步辅助。
1 package com.wang.helpclass; 2 3 import java.util.concurrent.BrokenBarrierException; 4 import java.util.concurrent.CyclicBarrier; 5 6 public class CyclicBarrierDemo { 7 public static void main(String[] args) { 8 // 主线程 9 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { 10 System.out.println("7个子线程运行完毕!"); 11 }); 12 13 // 7个子线程 14 for (int i = 1; i <= 7; i++) { 15 // lambda表达式能操作到循环变量i吗?不能,lambda表达式的本质是new了一个类,需要创建一个final的临时变量 16 final int temp = i; 17 new Thread(()->{ 18 System.out.println(Thread.currentThread().getName() + " -> " + temp); 19 try { 20 cyclicBarrier.await(); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } catch (BrokenBarrierException e) { 24 e.printStackTrace(); 25 } 26 }).start(); 27 } 28 } 29 } 30 // 结果:(如果只有7个线程,而CyclicBarrier()参数为8,则会一直等待) 31 Thread-0 -> 1 32 Thread-5 -> 6 33 Thread-4 -> 5 34 Thread-3 -> 4 35 Thread-2 -> 3 36 Thread-1 -> 2 37 Thread-6 -> 7 38 7个子线程运行完毕!
8.3 Semaphore
一个计数信号量。 在概念上,信号量维持一组许可证。
1 package com.wang.helpclass; 2 3 import java.util.concurrent.Semaphore; 4 import java.util.concurrent.TimeUnit; 5 6 // 抢车位 7 public class SemaphoreDemo { 8 public static void main(String[] args) { 9 // 线程数量:停车位 10 Semaphore semaphore = new Semaphore(3); 11 12 for (int i = 1; i <= 6; i++) { 13 new Thread(()->{ 14 try { 15 semaphore.acquire(); // 获得信号量 16 System.out.println(Thread.currentThread().getName() + " -> 进入车位"); 17 TimeUnit.SECONDS.sleep(2); 18 System.out.println(Thread.currentThread().getName() + "-> 离开车位"); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } finally { 22 semaphore.release(); // 释放信号量 23 } 24 }, String.valueOf(i)).start(); 25 } 26 } 27 } 28 // 结果: 29 1 -> 进入车位 30 3 -> 进入车位 31 2 -> 进入车位 32 3-> 离开车位 33 1-> 离开车位 34 2-> 离开车位 35 5 -> 进入车位 36 4 -> 进入车位 37 6 -> 进入车位 38 4-> 离开车位 39 6-> 离开车位 40 5-> 离开车位
解释:
semaphore.acquire(); 获得(-1),假设已经满了,等待被释放为止
semaphore.release(); 释放(+1),将当前信号量释放,然后唤醒等待的线程
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
9. 读写锁
ReadWriteLock,一个接口。维护一对关联的locks,一个用于只读操作,一个用于写入。读可以被多个线程同时读,写只能由一个线程去写。
读-读:可以共存;读-写:不能共存;写-写:不能共存。
独占锁(写锁):一次只能被一个线程占有;共享锁(读锁):多个线程可以同时占有。
1 package com.wang.rwlock; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.concurrent.locks.ReadWriteLock; 6 import java.util.concurrent.locks.ReentrantReadWriteLock; 7 8 public class ReadWriteLockDemo { 9 public static void main(String[] args) { 10 MyCacheLock myCache = new MyCacheLock(); 11 12 // 写入线程 13 for (int i = 1; i <= 5; i++) { 14 final int temp = i; 15 new Thread(()->{ 16 myCache.put(String.valueOf(temp), String.valueOf(temp)); 17 }, String.valueOf(i)).start(); 18 } 19 20 // 读取线程 21 for (int i = 1; i <= 5; i++) { 22 final int temp = i; 23 new Thread(()->{ 24 myCache.get(String.valueOf(temp)); 25 }, String.valueOf(i)).start(); 26 } 27 } 28 } 29 30 // 自定义缓存 31 // 加锁,可以同时读,不能同时写 32 class MyCacheLock{ 33 private volatile Map<String, Object> map = new HashMap<>(); 34 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁,更加细粒度的控制 35 36 // 写入, 同一时间只有一个线程在写 37 public void put(String key, Object value){ 38 readWriteLock.writeLock().lock(); 39 40 try { 41 System.out.println(Thread.currentThread().getName() + " -> 写入" + key); 42 map.put(key, value); 43 System.out.println(Thread.currentThread().getName() + " -> 写入完成"); 44 } catch (Exception e) { 45 e.printStackTrace(); 46 } finally { 47 readWriteLock.writeLock().unlock(); 48 } 49 } 50 51 // 读取, 所有线程都能同时读 52 public void get(String key){ 53 readWriteLock.readLock().lock(); 54 55 try { 56 System.out.println(Thread.currentThread().getName() + " -> 读取" + key); 57 Object o = map.get(key); 58 System.out.println(Thread.currentThread().getName() + " -> 读取完成"); 59 } catch (Exception e) { 60 e.printStackTrace(); 61 } finally { 62 readWriteLock.readLock().unlock(); 63 } 64 } 65 } 66 67 // 不加锁,可能会出现多个线程同时写入缓存的情况 68 class MyCache{ 69 private volatile Map<String, Object> map = new HashMap<>(); 70 71 // 写入 72 public void put(String key, Object value){ 73 System.out.println(Thread.currentThread().getName() + " -> 写入" + key); 74 map.put(key, value); 75 System.out.println(Thread.currentThread().getName() + " -> 写入完成"); 76 } 77 78 // 读取 79 public void get(String key){ 80 System.out.println(Thread.currentThread().getName() + " -> 读取" + key); 81 Object o = map.get(key); 82 System.out.println(Thread.currentThread().getName() + " -> 读取完成"); 83 } 84 } 85 // 结果: 86 1 -> 写入1 87 1 -> 写入完成 88 3 -> 写入3 89 3 -> 写入完成 90 4 -> 写入4 91 4 -> 写入完成 92 2 -> 写入2 93 2 -> 写入完成 94 5 -> 写入5 95 5 -> 写入完成 96 1 -> 读取1 97 1 -> 读取完成 98 2 -> 读取2 99 5 -> 读取5 100 5 -> 读取完成 101 4 -> 读取4 102 4 -> 读取完成 103 3 -> 读取3 104 3 -> 读取完成 105 2 -> 读取完成
10. 阻塞队列
写入:队列满,必须阻塞等待;
取出:队列空,必须阻塞等待生产。
阻塞队列BolockingQueue,一个接口,不是新东西。一般在多线程并发处理和线程池中会用到!
4组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞,等待 | 阻塞,超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(timeout, TimeUnit) |
移除 | remove() | poll() | take() | poll(timeout, TimeUnit) |
检测队首元素 | element() | peek() | - | - |
1 package com.wang.blockingqueue; 2 3 import java.util.concurrent.ArrayBlockingQueue; 4 import java.util.concurrent.BlockingQueue; 5 import java.util.concurrent.TimeUnit; 6 7 public class Demo { 8 public static void main(String[] args) throws InterruptedException { 9 // test01(); 10 // test02(); 11 // test03(); 12 test04(); 13 } 14 15 // 抛出异常 16 public static void test01(){ 17 ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); 18 19 System.out.println(blockingQueue.add("a")); 20 System.out.println(blockingQueue.add("b")); 21 System.out.println(blockingQueue.add("c")); 22 // System.out.println(blockingQueue.add("d")); // java.lang.IllegalStateException: Queue full 23 System.out.println("--------------------"); 24 System.out.println(blockingQueue.remove()); 25 System.out.println(blockingQueue.remove()); 26 System.out.println(blockingQueue.remove()); 27 // System.out.println(blockingQueue.remove()); // java.util.NoSuchElementException 28 } 29 30 // 不抛出异常,有返回值,返回false/null 31 public static void test02(){ 32 BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); 33 34 System.out.println(blockingQueue.offer("a")); 35 System.out.println(blockingQueue.offer("b")); 36 System.out.println(blockingQueue.offer("c")); 37 // System.out.println(blockingQueue.offer("d")); // false,不抛出异常 38 System.out.println("--------------------"); 39 System.out.println(blockingQueue.poll()); 40 System.out.println(blockingQueue.poll()); 41 System.out.println(blockingQueue.poll()); 42 // System.out.println(blockingQueue.poll()); // null,不抛出异常 43 } 44 45 // 等待,阻塞(一直阻塞) 46 public static void test03() throws InterruptedException { 47 BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); 48 49 blockingQueue.put("a"); 50 blockingQueue.put("b"); 51 blockingQueue.put("c"); 52 // blockingQueue.put("d"); // 队列满,一直阻塞 53 System.out.println("--------------------"); 54 System.out.println(blockingQueue.take()); 55 System.out.println(blockingQueue.take()); 56 System.out.println(blockingQueue.take()); 57 // System.out.println(blockingQueue.take()); // 队列空,一直阻塞 58 } 59 60 // 等待,阻塞(等待超时) 61 public static void test04() throws InterruptedException { 62 BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); 63 64 blockingQueue.offer("a"); 65 blockingQueue.offer("b"); 66 blockingQueue.offer("c"); 67 // blockingQueue.offer("d", 2, TimeUnit.SECONDS); // 等待超过2s退出 68 System.out.println("--------------------"); 69 System.out.println(blockingQueue.poll()); 70 System.out.println(blockingQueue.poll()); 71 System.out.println(blockingQueue.poll()); 72 // System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS)); // 等待超过2s退出,返回null 73 } 74 }
SynchronousQueue同步队列
和其他BlockingQueue不一样,SynchronousQueue容量为1,put一个元素后不能继续put,需要从里面take出来后才能继续put。
1 package com.wang.blockingqueue; 2 3 import java.util.concurrent.BlockingQueue; 4 import java.util.concurrent.SynchronousQueue; 5 import java.util.concurrent.TimeUnit; 6 7 public class SynchronousQueueDemo { 8 public static void main(String[] args) { 9 BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); 10 11 new Thread(()->{ 12 try { 13 System.out.println(Thread.currentThread().getName() + " put 1"); 14 blockingQueue.put("1"); 15 System.out.println(Thread.currentThread().getName() + " put 2"); 16 blockingQueue.put("2"); 17 System.out.println(Thread.currentThread().getName() + " put 3"); 18 blockingQueue.put("3"); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 }, "Thread 1").start(); 23 24 new Thread(()->{ 25 try { 26 TimeUnit.SECONDS.sleep(2); 27 System.out.println(Thread.currentThread().getName() + " take " + blockingQueue.take()); 28 TimeUnit.SECONDS.sleep(2); 29 System.out.println(Thread.currentThread().getName() + " take " + blockingQueue.take()); 30 TimeUnit.SECONDS.sleep(2); 31 System.out.println(Thread.currentThread().getName() + " take " + blockingQueue.take()); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 }, "Thread 2").start(); 36 } 37 } 38 // 结果: 39 Thread 1 put 1 40 Thread 2 take 1 41 Thread 1 put 2 42 Thread 2 take 2 43 Thread 1 put 3 44 Thread 2 take 3
11. 线程池(重点)
线程池知识点:3大方法、7大参数、4种拒绝策略
池化技术
程序运行需要占用系统资源,池化技术可以用于优化资源的使用。
池化技术:事先准备好一些资源,需要使用时直接获取,使用完毕后归还。
线程池的好处:
-
降低资源的消耗;
-
提高响应的速度;
-
方便管理。
线程复用、可以控制最大并发数、管理线程
线程池:3大方法
1 package com.wang.threadpool; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 // 线程池 3大方法 7 public class Demo01 { 8 public static void main(String[] args) { 9 // ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 单个线程 10 // ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池 11 ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的,遇强则强,遇弱则弱 12 13 14 try { 15 for (int i = 1; i <= 10; i++) { 16 // 使用线程池创建线程 17 threadPool.execute(()->{ 18 System.out.println(Thread.currentThread().getName() + " -> ok"); 19 }); 20 } 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } finally { 24 // 线程池使用完毕后需要关闭 25 threadPool.shutdown(); 26 } 27 } 28 }
线程池:7大参数
源码分析
1 public static ExecutorService newSingleThreadExecutor() { 2 return new FinalizableDelegatedExecutorService 3 (new ThreadPoolExecutor(1, 1, 4 0L, TimeUnit.MILLISECONDS, 5 new LinkedBlockingQueue<Runnable>())); 6 } 7 public static ExecutorService newFixedThreadPool(int nThreads) { 8 return new ThreadPoolExecutor(nThreads, nThreads, 9 0L, TimeUnit.MILLISECONDS, 10 new LinkedBlockingQueue<Runnable>()); 11 } 12 public static ExecutorService newCachedThreadPool() { 13 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 14 60L, TimeUnit.SECONDS, 15 new SynchronousQueue<Runnable>()); 16 } 17 18 本质:ThreadPoolExecutor() 19 public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小 20 int maximumPoolSize, // 最大线程池大小 21 long keepAliveTime, // 超时了没有调用就会释放 22 TimeUnit unit, // 超时单位 23 BlockingQueue<Runnable> workQueue,// 阻塞队列 24 ThreadFactory threadFactory, // 线程工厂:创建线程,一般不用动 25 RejectedExecutionHandler handler // 拒绝策略 26 ) { 27 if (corePoolSize < 0 || 28 maximumPoolSize <= 0 || 29 maximumPoolSize < corePoolSize || 30 keepAliveTime < 0) 31 throw new IllegalArgumentException(); 32 if (workQueue == null || threadFactory == null || handler == null) 33 throw new NullPointerException(); 34 this.acc = System.getSecurityManager() == null ? 35 null : 36 AccessController.getContext(); 37 this.corePoolSize = corePoolSize; 38 this.maximumPoolSize = maximumPoolSize; 39 this.workQueue = workQueue; 40 this.keepAliveTime = unit.toNanos(keepAliveTime); 41 this.threadFactory = threadFactory; 42 this.handler = handler; 43 }
手动创建线程池:
1 package com.wang.threadpool; 2 3 import java.util.concurrent.*; 4 5 // 线程池 7大参数 6 public class Demo02 { 7 public static void main(String[] args) { 8 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 9 2, 10 5, 11 3, 12 TimeUnit.SECONDS, 13 new LinkedBlockingDeque<>(3), 14 Executors.defaultThreadFactory(), 15 new ThreadPoolExecutor.AbortPolicy() // 满了之后不处理,抛出异常(java.util.concurrent.RejectedExecutionException) 16 // new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里 17 // new ThreadPoolExecutor.DiscardPolicy() // 队列满,丢掉任务,不会抛出异常 18 // new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满,尝试和最早的竞争,不会抛出异常 19 ); 20 21 try { 22 // 最大承载 = queue capacity + maximunPoolSize 23 for (int i = 1; i <= 10; i++) { 24 // 使用线程池创建线程 25 threadPool.execute(()->{ 26 System.out.println(Thread.currentThread().getName() + " -> ok"); 27 }); 28 } 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } finally { 32 // 线程池使用完毕后需要关闭 33 threadPool.shutdown(); 34 } 35 } 36 }
线程池 4种拒绝策略
小结和扩展
了解:IO密集型和CPU密集型(调优)
线程池的最大大小该如何设置:
-
CPU密集型:核数是几核,就是几,可以保持CPU的效率最高;
Runtime.getRuntime().availableProcessors(); // 获取CPU的核数
-
IO密集型:判断程序中十分耗IO的线程,只要将最大线程数设置大于该线程值即可。
例如:有一个程序,包含15个大型任务,IO十分占用资源
12. 四大函数式接口(必会)
新时代程序员必须掌握:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
例1:Runnable接口
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
FunctionalInterface使用广泛,简化编程模型,在新版本的框架底层大量应用!
例2:foreach的参数:foreach(消费者类的函数式接口)
代码测试:
一、Function 函数式接口:有一个输入参数,有一个输出
1 package com.wang.function; 2 3 import java.util.function.Function; 4 5 /** 6 * Function函数式接口,有一个输入参数,有一个输出 7 * 只要是 函数式接口 ,都可以用lambda表达式简化 8 */ 9 public class Demo01 { 10 public static void main(String[] args) { 11 /* 12 Function function = new Function<String, String>() { 13 @Override 14 public String apply(String o) { 15 return o; 16 } 17 }; 18 */ 19 // lambda表达式简化 20 Function function = (str)->{return str;}; 21 22 System.out.println(function.apply("asd")); 23 } 24 } 25 // 输出: 26 asd
二、Predicate 断定型接口:有一个输入参数,返回值只能是 布尔值!
1 package com.wang.function; 2 3 import java.util.function.Predicate; 4 5 /** 6 * Predicate 断定型接口:有一个输入参数,返回值只能是 布尔值! 7 */ 8 public class Demo02 { 9 public static void main(String[] args) { 10 // 判断字符串是否为空 11 /* 12 Predicate<String> predicate = new Predicate<String>() { 13 @Override 14 public boolean test(String s) { 15 return s.isEmpty(); 16 } 17 }; 18 */ 19 20 // lambda表达式简化 21 Predicate<String> predicate = (str)->{return str.isEmpty();}; 22 23 System.out.println(predicate.test("")); 24 } 25 } 26 // 输出: 27 true
三、Consumer 消费型接口:只有输入,没有返回值
1 package com.wang.function; 2 3 import java.util.function.Consumer; 4 5 /** 6 * Consumer 消费型接口:只有输入,没有返回值 7 */ 8 public class Demo03 { 9 public static void main(String[] args) { 10 // 打印字符串 11 /* 12 Consumer<String> consumer = new Consumer<String>() { 13 @Override 14 public void accept(String s) { 15 System.out.println(s); 16 } 17 }; 18 */ 19 20 // lambda表达式简化 21 Consumer<String> consumer = (s)->{System.out.println(s);}; 22 23 consumer.accept("JungKook"); 24 } 25 } 26 // 输出: 27 JungKook
四、Supplier 供给型接口:没有参数,只有返回值
1 package com.wang.function; 2 3 import java.util.function.Supplier; 4 5 /** 6 * Supplier 供给型接口:没有参数,只有返回值 7 */ 8 public class Demo04 { 9 public static void main(String[] args) { 10 /* 11 Supplier<String> supplier = new Supplier<String>() { 12 @Override 13 public String get() { 14 System.out.println("In!"); 15 return "JungKook"; 16 } 17 }; 18 */ 19 20 // lambda表达式简化 21 Supplier<String> supplier = ()->{ 22 System.out.println("In!"); 23 return "JungKook"; 24 }; 25 26 System.out.println(supplier.get()); 27 } 28 } 29 // 输出: 30 In! 31 JungKook
13. Stream流式计算
什么是Stream流式计算
大数据 = 存储 + 计算
集合、MySQL本质上是存储东西的,计算都应该交给流来操作!
1 package com.wang.stream; 2 3 import java.util.Arrays; 4 import java.util.List; 5 6 /** 7 * 题目要求:一行代码实现 8 * 现有6个用户,筛选: 9 * 1. ID必须是偶数 10 * 2. 年龄必须大于23 11 * 3. 用户名转换为大写字母 12 * 4. 用户名字母倒序 13 * 5. 只输出一个用户 14 */ 15 public class Test { 16 public static void main(String[] args) { 17 User u1 = new User(1, "a", 21); 18 User u2 = new User(2, "b", 22); 19 User u3 = new User(3, "c", 23); 20 User u4 = new User(4, "d", 24); 21 User u5 = new User(5, "e", 25); 22 User u6 = new User(6, "f", 26); 23 // 集合用于存储 24 List<User> list = Arrays.asList(u1, u2, u3, u4, u5, u6); 25 // Stream流用于计算 26 list.stream() 27 .filter(u->{return u.getId()%2==0;}) 28 .filter(u->{return u.getAge()>23;}) 29 .map(u->{return u.getName().toUpperCase();}) 30 .sorted((a, b)->{return b.compareTo(a);}) 31 .limit(1) 32 .forEach(System.out::println); 33 } 34 } 35 // 输出: 36 F
14. ForkJoin分支合并
什么是ForkJoin
在JDK1.7提出,并行执行任务!提高效率,大数据量!
大数据:Map Reduce(把大任务拆分成小任务)
ForkJoin特点:工作窃取
例:有两个线程(A,B)正在执行,将其分别拆分成多个子任务(A1、A2、A3、A4、B1、B2、B3、B4),倘若A在执行到A2时,B线程已经执行完毕,B线程就会窃取A线程的A4任务,提高效率。
维护的是一个双端队列,上例中A线程可以从A1开始获取子任务,而B可以从A4开始获取子任务。
ForkJoin操作
如何使用ForkJoin,步骤如下:
-
通过ForkJoinPool来执行;
-
通过ForkJoinPool.execute(ForkJoinTask task)来计算任务;
-
计算类要继承ForkJoinTask。
1 package com.wang.forkjoin; 2 3 import java.util.concurrent.RecursiveTask; 4 /** 5 * 求和计算任务 6 * 1. 常规计算 7 * 2. ForkJoin 8 * 3. Stream并行流 9 */ 10 public class ForkJoinDemo extends RecursiveTask<Long> { 11 12 private Long start; 13 private Long end; 14 private Long temp = 10000L; // 临界值 15 16 public ForkJoinDemo(Long start, Long end) { 17 this.start = start; 18 this.end = end; 19 } 20 21 // 计算方法 22 @Override 23 protected Long compute() { 24 if ((end - start) < temp){ // 常规计算 25 Long sum = 0L; 26 for (Long i = start; i <= end; i++){ 27 sum += i; 28 } 29 return sum; 30 }else{ // ForkJoin,递归解决 31 long middle = (start + end)/2; 32 ForkJoinDemo task1 = new ForkJoinDemo(start, middle); 33 task1.fork(); // 拆分任务,把任务压入线程队列 34 ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end); 35 task2.fork(); 36 return task1.join() + task2.join(); 37 } 38 } 39 }
test程序:
1 package com.wang.forkjoin; 2 3 import java.util.concurrent.ExecutionException; 4 import java.util.concurrent.ForkJoinPool; 5 import java.util.concurrent.ForkJoinTask; 6 import java.util.stream.LongStream; 7 8 public class Test { 9 public static void main(String[] args) throws ExecutionException, InterruptedException { 10 test01(); 11 test02(); 12 test03(); 13 } 14 15 // 常规计算 16 public static void test01(){ 17 Long sum = 0L; 18 long start = System.currentTimeMillis(); 19 for (Long i = 1L; i <= 10_0000_0000; i++) { 20 sum += i; 21 } 22 long end = System.currentTimeMillis(); 23 System.out.println("sum=" + sum + " time:" + (end-start)); 24 } 25 // ForkJoin 26 public static void test02() throws ExecutionException, InterruptedException { 27 Long sum = 0L; 28 long start = System.currentTimeMillis(); 29 ForkJoinPool forkJoinPool = new ForkJoinPool(); 30 ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L); 31 ForkJoinTask<Long> submit = forkJoinPool.submit(task); // 提交任务 32 sum = submit.get(); 33 long end = System.currentTimeMillis(); 34 System.out.println("sum=" + sum + " time:" + (end-start)); 35 } 36 // Stream并行流 37 public static void test03(){ 38 Long sum = 0L; 39 long start = System.currentTimeMillis(); 40 sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); 41 long end = System.currentTimeMillis(); 42 System.out.println("sum=" + sum + " time:" + (end-start)); 43 } 44 } 45 // 输出: 46 sum=500000000500000000 time:5881 47 sum=500000000500000000 time:4136 48 sum=500000000500000000 time:172
15. 异步回调
Future的设计初衷
1 package com.wang.future; 2 3 import java.util.concurrent.CompletableFuture; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.TimeUnit; 6 7 /** 8 * 异步调用CompletableFuture 9 * 异步执行 10 * 成功回调 11 * 失败回调 12 */ 13 public class Demo01 { 14 public static void main(String[] args) throws ExecutionException, InterruptedException { 15 // 无返回值的异步回调 16 // CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ 17 // try { 18 // TimeUnit.SECONDS.sleep(2); 19 // } catch (InterruptedException e) { 20 // e.printStackTrace(); 21 // } 22 // System.out.println(Thread.currentThread().getName() + " CompletableFuture.runAsync-->void"); 23 // }); 24 // System.out.println("1"); 25 // completableFuture.get(); // 阻塞获取执行结果 26 // System.out.println("2"); 27 28 // 有返回值的异步回调,分为成功和失败回调 29 CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { 30 System.out.println(Thread.currentThread().getName() + " CompletableFuture.supplyAsync-->Integer"); 31 int i = 10/0; // 错误语句,致使失败回调 32 return 1; 33 }); 34 35 System.out.println(completableFuture.whenComplete((t,u)->{ 36 System.out.println("t:" + t); // 正常返回结果 37 System.out.println("u:" + u); // 错误信息 38 }).exceptionally((e)->{ 39 System.out.println(e.getMessage()); 40 return -1; 41 }).get()); 42 } 43 } 44 // 输出: 45 // 无返回值的异步回调 46 1 47 ForkJoinPool.commonPool-worker-1 CompletableFuture.runAsync-->void 48 2 49 // 有返回值的异步回调 50 // 成功回调 51 ForkJoinPool.commonPool-worker-1 CompletableFuture.supplyAsync-->Integer 52 t:1 53 u:null 54 1 55 // 失败回调 56 ForkJoinPool.commonPool-worker-1 CompletableFuture.supplyAsync-->Integer 57 t:null 58 u:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero 59 java.lang.ArithmeticException: / by zero 60 -1
16. JMM
Volatile是Java虚拟机提供的轻量级的同步机制
-
保证可见性
-
不保证原子性
-
禁止指令重排
什么是JMM
JMM:Java内存模型,不存在的东西,是一个概念,一个约定。
关于JMM的一些同步的约定:
-
线程解锁前,必须把共享变量立刻刷回主存;
-
线程加锁前,必须读取主存中的最新值到工作内存中;
-
加锁和解锁是同一把锁。
注:线程有自己的工作内存,不会直接操作主存。
线程 工作内存、主内存
8大原子操作:
-
lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
-
unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
-
read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
-
load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
-
use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
-
assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
-
store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
-
write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:
-
不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
-
不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
-
不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
-
一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
-
一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
-
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
-
如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
-
对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
问题:线程B修改了主存的值,但是线程A不能及时可见。
1 package com.wang.jmm; 2 3 import java.util.concurrent.TimeUnit; 4 5 // 问题:主线程修改了主存的值,但是线程不能及时可见,因而线程一直处于循环 6 public class Demo01 { 7 private static int num = 0; 8 public static void main(String[] args) { 9 new Thread(()->{ 10 while (num==0) {} 11 }).start(); 12 13 try { 14 TimeUnit.SECONDS.sleep(1); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 num = 1; 19 System.out.println(num); 20 } 21 }
17. Volatile
特性一 保证可见性
1 package com.wang.jmm; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class Demo01 { 6 // 不加volatile,程序就会死循环;加volatile,可以保证可见性 7 private volatile static int num = 0; 8 public static void main(String[] args) { 9 new Thread(()->{ 10 while (num==0) {} 11 }).start(); 12 13 try { 14 TimeUnit.SECONDS.sleep(1); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 num = 1; 19 System.out.println(num); 20 } 21 }
特性二 不保证原子性
原子性:不可分割
1 package com.wang.jmm; 2 3 // 不保证原子性 4 public class Demo02 { 5 // 加volatile也不能保证num结果为20000 6 private volatile static int num = 0; 7 // 加synchronized可以解决 8 public static void add(){ 9 num++; 10 } 11 12 public static void main(String[] args) { 13 // 理论上num结果为20000 14 for (int i = 1; i <= 20; i++) { 15 new Thread(()->{ 16 for (int j = 0; j < 1000; j++) { 17 add(); 18 } 19 }).start(); 20 } 21 22 while (Thread.activeCount()>2){ // 排除main和gc线程 23 Thread.yield(); 24 } 25 26 System.out.println(Thread.currentThread().getName() + " " + num); 27 } 28 }
通过javap -c *.class反汇编查看num++语句的底层实现:
如果不加lock和synchronized,如何保证原子性?
使用原子类,解决原子性问题。Package java.util.concurrent.atomic,一个小型工具包,支持单个变量上的无锁线程安全编程。
1 package com.wang.jmm; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 // 使用原子类保证原子性 6 public class Demo03 { 7 // 原子类的Integer 8 private static AtomicInteger num = new AtomicInteger(); 9 public static void add(){ 10 // num++; // 不是一个原子性操作 11 num.getAndIncrement(); // AtomicInteger的+1方法,CAS 12 } 13 14 public static void main(String[] args) { 15 // 理论上num结果为20000 16 for (int i = 1; i <= 20; i++) { 17 new Thread(()->{ 18 for (int j = 0; j < 1000; j++) { 19 add(); 20 } 21 }).start(); 22 } 23 24 while (Thread.activeCount()>2){ // 排除main和gc线程 25 Thread.yield(); 26 } 27 28 System.out.println(Thread.currentThread().getName() + " " + num); 29 } 30 } 31 // 输出: 32 main 20000
原子类的底层直接与操作系统底层挂钩,在内存中修改值。
特性三 禁止指令重排
什么是指令重排:写的程序,计算机并不是按照写的那样执行的。
源代码 -- 编译器优化的重排 -- 指令并行可能会重排 -- 内存系统也会重排 --> 执行
处理器在进行指令重排时需要考虑数据之间的依赖性!
volatile可以避免指令重排:
内存屏障:禁止当前指令的前后指令交换执行顺序
-
保证特定操作的执行顺序;
-
可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)。
18. 单例模式
volatile内存屏障使用最广泛的场景!
饿汉式
1 package com.wang.single; 2 3 // 饿汉式单例 4 public class Hungry { 5 6 // 可能会浪费空间 7 private byte[] data1 = new byte[1024*1024]; 8 private byte[] data2 = new byte[1024*1024]; 9 private byte[] data3 = new byte[1024*1024]; 10 private byte[] data4 = new byte[1024*1024]; 11 12 private Hungry(){ 13 14 } 15 16 private final static Hungry HUNGRY = new Hungry(); 17 18 public static Hungry getInstance(){ 19 return HUNGRY; 20 } 21 22 public static void main(String[] args) { 23 Hungry instance1 = Hungry.getInstance(); 24 Hungry instance2 = Hungry.getInstance(); 25 System.out.println(instance1.hashCode()); 26 System.out.println(instance2.hashCode()); 27 } 28 } 29 // 输出: 30 460141958 31 460141958
懒汉式
1 package com.wang.single; 2 3 // 懒汉式单例 4 public class LazyMan { 5 private LazyMan(){ 6 7 } 8 9 private static LazyMan lazyMan; 10 11 public static LazyMan getInstance(){ 12 if (lazyMan==null){ 13 lazyMan = new LazyMan(); 14 } 15 return lazyMan; 16 } 17 18 public static void main(String[] args) { 19 LazyMan instance1 = LazyMan.getInstance(); 20 LazyMan instance2 = LazyMan.getInstance(); 21 System.out.println(instance1.hashCode()); 22 System.out.println(instance2.hashCode()); 23 } 24 } 25 // 输出: 26 460141958 27 460141958
使用反射破坏单例模式:
1 package com.wang.single; 2 3 import java.lang.reflect.Constructor; 4 5 // 懒汉式单例 6 public class LazyMan { 7 private LazyMan(){ 8 9 } 10 11 private static LazyMan lazyMan; 12 13 public static LazyMan getInstance(){ 14 if (lazyMan==null){ 15 lazyMan = new LazyMan(); 16 } 17 return lazyMan; 18 } 19 20 public static void main(String[] args) throws Exception { 21 LazyMan instance1 = LazyMan.getInstance(); 22 LazyMan instance2 = LazyMan.getInstance(); 23 System.out.println(instance1.hashCode()); 24 System.out.println(instance2.hashCode()); 25 26 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); 27 declaredConstructor.setAccessible(true); 28 LazyMan instance3 = declaredConstructor.newInstance(); 29 System.out.println(instance3.hashCode()); 30 } 31 } 32 // 输出: 33 460141958 34 460141958 35 1163157884
DCL懒汉式:需加volatile
1 package com.wang.single; 2 3 // 双重检测锁模式的懒汉式单例——DCL懒汉式 4 public class DCLLazyMan { 5 private DCLLazyMan(){ 6 7 } 8 9 private volatile static DCLLazyMan dclLazyMan; 10 11 public static DCLLazyMan getInstance(){ 12 if (dclLazyMan==null){ 13 synchronized (DCLLazyMan.class){ 14 if (dclLazyMan==null){ 15 /** 16 * 1. 分配内存空间 17 * 2. 执行构造方法,初始化对象 18 * 3. 把这个对象指向这个空间 19 * 线程A指令重排为132,可能导致线程B判断该对象不为空,但对象初始化未完成 20 * 为防止指令重排,须在对象dclLazyMan前加关键字volatile 21 */ 22 dclLazyMan = new DCLLazyMan(); // 不是一个原子性操作 23 } 24 } 25 } 26 return dclLazyMan; 27 } 28 29 public static void main(String[] args) { 30 DCLLazyMan instance1 = DCLLazyMan.getInstance(); 31 DCLLazyMan instance2 = DCLLazyMan.getInstance(); 32 System.out.println(instance1.hashCode()); 33 System.out.println(instance2.hashCode()); 34 } 35 } 36 // 输出: 37 460141958 38 460141958
静态内部类的单例模式
1 package com.wang.single; 2 3 // 静态内部类实现单例模式 4 public class StaticClass { 5 private StaticClass(){ 6 7 } 8 9 public static class InnerClass{ 10 private static final StaticClass STATIC_CLASS = new StaticClass(); 11 } 12 13 public static StaticClass getInstance(){ 14 return InnerClass.STATIC_CLASS; 15 } 16 17 public static void main(String[] args) { 18 StaticClass instance1 = StaticClass.getInstance(); 19 StaticClass instance2 = StaticClass.getInstance(); 20 System.out.println(instance1.hashCode()); 21 System.out.println(instance2.hashCode()); 22 } 23 } 24 // 输出: 25 460141958 26 460141958
注:以上方法会出现单例不安全的不问题(反射),引入枚举方法!
枚举的单例模式
1 package com.wang.single; 2 3 // 枚举的单例模式,反射不能破坏枚举单例 4 public enum EnumSingle { 5 INSTANCE; 6 7 public EnumSingle getInstance(){ 8 return INSTANCE; 9 } 10 } 11 12 class Test{ 13 public static void main(String[] args) { 14 EnumSingle instance1 = EnumSingle.INSTANCE; 15 EnumSingle instance2 = EnumSingle.INSTANCE; 16 System.out.println(instance1.hashCode()); 17 System.out.println(instance2.hashCode()); 18 } 19 } 20 // 输出: 21 460141958 22 460141958
19. 深入理解CAS
什么是CAS
1 package com.wang.cas; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 /** 6 * CAS:compare and swap,比较并交换! 7 * 原子类的底层运用了CAS,是CPU的并发原语 8 */ 9 public class CASDemo { 10 public static void main(String[] args) { 11 AtomicInteger atomicInteger = new AtomicInteger(2020); 12 13 // public final boolean compareAndSet(int expect, int update) 14 // 是期望值则更新,否则不更新 15 System.out.println(atomicInteger.compareAndSet(2020, 2021)); 16 System.out.println(atomicInteger.get()); 17 System.out.println(atomicInteger.compareAndSet(2020, 2021)); 18 System.out.println(atomicInteger.get()); 19 } 20 } 21 // 输出: 22 true 23 2021 24 false 25 2021
CAS:比较当前工作内存中的值和主存中的值,如果这个值是期望的,那么执行操作,否则一直循环。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
缺点:
-
循环会耗时;
-
一次性只能保证一个共享变量的原子性;
-
ABA问题
Unsafe类
valueOffset对象,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量,可以简单的把valueOffset理解为value变量的内存地址。
而unsafe的compareAndSwapInt方法的参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。
CAS的自旋,循环体中做了三件事:1. 获取当前主存中的值;2. 当前值+1,计算出目标值;3. 进行CAS操作,如果成功则跳出循环,如果失败则重复1和2。
CAS: ABA问题(狸猫换太子)
1 package com.wang.cas; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 public class ABAProblem { 6 public static void main(String[] args) { 7 AtomicInteger atomicInteger = new AtomicInteger(2020); 8 9 // 期望线程想要修改2020为2021,但捣乱线程的存在使得期望线程修改的2020不是原来的2020 10 // ------------------- 捣乱线程 ------------------- 11 System.out.println(atomicInteger.compareAndSet(2020, 2021)); 12 System.out.println(atomicInteger.get()); 13 System.out.println(atomicInteger.compareAndSet(2021, 2020)); 14 System.out.println(atomicInteger.get()); 15 // ------------------- 期望线程 ------------------- 16 System.out.println(atomicInteger.compareAndSet(2020, 6666)); 17 System.out.println(atomicInteger.get()); 18 } 19 } 20 // 输出: 21 true 22 2021 23 true 24 2020 25 true 26 6666
CAS参考链接:https://www.sohu.com/a/314272265_120104204
20. 原子引用
原子引用:解决ABA问题,带版本号的原子操作!对应的思想就是乐观锁!
1 package com.wang.cas; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.atomic.AtomicStampedReference; 5 6 public class AtomicRefDemo { 7 public static void main(String[] args) { 8 // AtomicInteger atomicInteger = new AtomicInteger(2020); 9 // AtomicStampedReference 注意:如果泛型是一个包装类,注意对象的引用问题 10 AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1); 11 12 new Thread(()->{ 13 int stamp = atomicInteger.getStamp(); // 获得版本号 14 System.out.println("A1->" + stamp); 15 try { 16 TimeUnit.SECONDS.sleep(2); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 System.out.println("A:" + atomicInteger.compareAndSet(1, 2, 22 atomicInteger.getStamp(), atomicInteger.getStamp() + 1)); 23 System.out.println("A2->" + atomicInteger.getStamp()); 24 System.out.println("A:" + atomicInteger.compareAndSet(1, 2, 25 atomicInteger.getStamp(), atomicInteger.getStamp() + 1)); 26 System.out.println("A3->" + atomicInteger.getStamp()); 27 }, "A").start(); 28 29 new Thread(()->{ 30 int stamp = atomicInteger.getStamp(); // 获得版本号 31 System.out.println("B1->" + stamp); 32 try { 33 TimeUnit.SECONDS.sleep(2); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 System.out.println("B:" + atomicInteger.compareAndSet(1, 6, 38 atomicInteger.getStamp(), atomicInteger.getStamp() + 1)); 39 System.out.println("B2->" + atomicInteger.getStamp()); 40 }, "B").start(); 41 } 42 } 43 // 输出: 44 A1->1 45 B1->1 46 A:true 47 A2->2 48 A:false 49 B:false 50 B2->2 51 A3->2
注:Integer使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
所有的相同类型的包装类对象之间值得比较,全部使用equals方法比较。
说明:对于Integer var = ?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用“==”进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用对象,这是一个大坑,推荐使用equals方法进行判断。
21. 各种锁的理解
-
公平锁、非公平锁
公平锁:不能插队,必须先来后到;非公平锁:可以插队(默认)。
public ReentrantLock() { // 默认为非公平锁 sync = new NonfairSync(); } public ReentrantLock(boolean fair) { // true代表为公平锁 sync = fair ? new FairSync() : new NonfairSync(); }
-
可重入锁(递归锁)
拿到了外面的锁之后,就可以自动获得里面的锁。
Synchronized版
1 package com.wang.lock; 2 3 public class SynchronizedDemo { 4 public static void main(String[] args) { 5 Phone phone = new Phone(); 6 7 new Thread(()->{phone.msg();}, "A").start(); 8 new Thread(()->{phone.msg();}, "B").start(); 9 } 10 } 11 12 class Phone{ 13 public synchronized void msg(){ 14 System.out.println(Thread.currentThread().getName() + "msg"); 15 call(); 16 } 17 18 public synchronized void call(){ 19 System.out.println(Thread.currentThread().getName() + "call"); 20 } 21 } 22 // 输出: 23 Amsg 24 Acall 25 Bmsg 26 Bcall
Lock版:锁必须配对,否则会发生死锁
1 package com.wang.lock; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class LockDemo { 7 public static void main(String[] args) { 8 Phone2 phone2 = new Phone2(); 9 10 new Thread(()->{phone2.msg();}, "A").start(); 11 new Thread(()->{phone2.msg();}, "B").start(); 12 } 13 } 14 15 class Phone2{ 16 Lock lock = new ReentrantLock(); 17 18 public void msg(){ 19 lock.lock(); 20 try{ 21 System.out.println(Thread.currentThread().getName() + "msg"); 22 call(); 23 }catch (Exception e){ 24 e.printStackTrace(); 25 }finally { 26 lock.unlock(); 27 } 28 } 29 30 public void call(){ 31 lock.lock(); 32 try{ 33 System.out.println(Thread.currentThread().getName() + "call"); 34 }catch (Exception e){ 35 e.printStackTrace(); 36 }finally { 37 lock.unlock(); 38 } 39 } 40 } 41 // 输出: 42 Amsg 43 Acall 44 Bmsg 45 Bcall
-
自旋锁
使用自旋锁自定义锁:
package com.wang.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; // 自旋锁 public class SpinlockDemo{ public static void main(String[] args) { /** * ReentrantLock reentrantLock = new ReentrantLock(); * reentrantLock.lock(); * reentrantLock.unlock(); */ // 底层使用的自旋锁(CAS)实现 MySpinlonk mySpinlonk = new MySpinlonk(); new Thread(()->{ mySpinlonk.myLock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } finally { mySpinlonk.myUnLock(); } }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ mySpinlonk.myLock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } finally { mySpinlonk.myUnLock(); } }, "B").start(); } } class MySpinlonk { // int->0 Thread->null AtomicReference<Thread> atomicReference = new AtomicReference<>(); // 加锁 public void myLock(){ Thread thread = Thread.currentThread(); // 自旋锁 while (!atomicReference.compareAndSet(null,thread)){ } System.out.println(Thread.currentThread().getName() + "-> mylock"); } // 解锁 public void myUnLock(){ Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread,null); System.out.println(Thread.currentThread().getName() + "->myunlock"); } } // 输出: A-> mylock A-> myunlock B-> mylock B-> myunlock
-
死锁
1 package com.wang.lock; 2 3 import java.util.concurrent.TimeUnit; 4 5 public class DeadLockDemo { 6 public static void main(String[] args) { 7 String lockA = "lockA"; 8 String lockB = "lockB"; 9 10 new Thread(new MyThread(lockA, lockB), "Thread1").start(); 11 new Thread(new MyThread(lockB, lockA), "Thread2").start(); 12 } 13 } 14 15 class MyThread implements Runnable{ 16 private String lock1; 17 private String lock2; 18 19 public MyThread(String lock1, String lock2) { 20 this.lock1 = lock1; 21 this.lock2 = lock2; 22 } 23 24 @Override 25 public void run() { 26 synchronized (lock1){ 27 System.out.println(Thread.currentThread().getName() + " hold " + lock1 + ", wait for " + lock2); 28 try { 29 TimeUnit.SECONDS.sleep(2); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 synchronized (lock2){ 34 System.out.println(Thread.currentThread().getName() + " hold " + lock2 + ", wait for " + lock1); 35 } 36 } 37 } 38 } 39 // 输出: 40 Thread2 hold lockB, wait for lockA 41 Thread1 hold lockA, wait for lockB 42 等待……
解决:
-
使用 “jps -l” 定位进程号
-
使用 "jstack 进程号" 找到死锁问题
-