1. 什么是JUC
JUC就是java.util.concurrent下面的类包,专门用于多线程的开发。
2. 线程和进程
1. 概念
-
进程: 运行的程序, 一个线程往往可以包含多个线程
java默认有两个线程: main线程和GC线程
-
线程
2. 并发和并行
-
并发: 多个线程操作同一资源
CPU一核, 快速交替, 模拟出来多条线程,
-
并行: 多个线程同时执行
CPU多核, 多个线程同时执行
3. 线程的多个状态
-
源码
public enum State { // 新生 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待, 死死等待 WAITING, // 等待, 超时放弃 TIMED_WAITING, // 终止 TERMINATED; }
4. wait和sleep的区别
-
来自不同的类:
wait --> Object
sleep --> Thread
-
关于锁的释放
wait会释放锁, sleep不会释放锁
-
使用的范围不同
wait必须在同步代码块红使用
sleep可以在任何地方使用
-
是否需要捕获异常
wait不需要捕获异常
sleep需要捕获异常
3. Lock锁(重点)
1. synchronized 关键字
-
代码
public class SaleTicketDemo01 { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "C").start(); } } // 资源类: OOP class Ticket { private int num = 50; public synchronized void sale(){ if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第 " + (num --) + "张票, 剩余: " + num); } } }
2. lock锁
-
代码
public class SaleTicketDemo02 { public static void main(String[] args) { Ticket2 ticket = new Ticket2(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "C").start(); } } class Ticket2 { private int num = 50; // 步骤1: new ReentrantLock(); Lock lock = new ReentrantLock(); public void sale(){ // 步骤2: 加锁, lock() lock.lock(); // 加锁 try { // 业务代码 if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第 " + (num --) + "张票, 剩余: " + num); } } catch (Exception e) { e.printStackTrace(); } finally { // 步骤3: 解锁, unlock() lock.unlock(); // 解锁 } } }
3. synchronized 和 Lock的区别
-
synchronized是内置的java关键字; lock是一个java类
-
synchronized无法判断获取锁的状态; lock可以判断是否获取到了锁
-
synchronized会自动释放锁; lock必须手动释放锁, 如果不释放 会出现死锁
-
synchronized如果线程1阻塞, 线程2会一直等待; lock锁不一定会等待下去.
-
synchronized可重入锁, 不可以中断, 非公平; Lock可重入锁, 可以判断锁, 非公平(可以自己设置)
-
synchronized适合少量的代码同步问题, lock适合锁大量的代码
4. 生产者和消费者
1. 传统方式
-
普通情况下可以用if判断
public class Provider { 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 = 5; // +1 public synchronized void increment() throws InterruptedException { if (num != 0) { // 等待 this.wait(); } // 业务 num++; System.out.println(Thread.currentThread().getName() + " => " + num); // 通知其他线程 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { if (num == 0) { // 等待 this.wait(); } // 业务 num--; System.out.println(Thread.currentThread().getName() + " => " + num); // 通知其他线程 this.notifyAll(); } }
-
如果是多个生产者和消费者, 则需要用while
if可能会导致虚假唤醒
public class Provider { 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 = 5; // +1 public synchronized void increment() throws InterruptedException { while (num != 0) { // 等待 this.wait(); } // 业务 num++; System.out.println(Thread.currentThread().getName() + " => " + num); // 通知其他线程 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { // 这里不能用if while (num == 0) { // 等待 this.wait(); } // 业务 num--; System.out.println(Thread.currentThread().getName() + " => " + num); // 通知其他线程 this.notifyAll(); } }
2. JUC方式
-
代码1
这个代码和上一个代码执行的结果一样, 但是不能精准的通知和唤醒线程;
public class Provider { 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 = 5; 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); // 通知其他线程 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() throws InterruptedException { // 加锁 lock.lock(); try { // 这里不能用if while (num == 0) { // 等待 condition.await(); } // 业务 num--; System.out.println(Thread.currentThread().getName() + " => " + num); // 通知其他线程 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
-
区别
-
精准通知和唤醒线程
这才是使用新技术的原因
// A执行完调用B; B执行完调用C; C执行完调用D; D执行完再掉A public class Provider { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.printA(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.printB(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.printC(); } }, "C").start(); } } // 等待, 业务, 通知 class Data { Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); private int number = 1; public void printA(){ lock.lock(); try { while (number != 1) { condition1.await(); } System.out.println(Thread.currentThread().getName() + "=> AAA"); // 唤醒指定的线程B number = 2; condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB(){ lock.lock(); try { while (number != 2) { condition2.await(); } System.out.println(Thread.currentThread().getName() + "=> BBB"); // 唤醒指定的线程C number = 3; condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC(){ lock.lock(); try { while (number != 3) { condition3.await(); } System.out.println(Thread.currentThread().getName() + "=> CCC"); // 唤醒指定的线程A number = 1; condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
5. 锁的相关问题
锁是什么, 如何判断锁的是谁?
1. 问题1: 加锁方法谁先执行
-
代码
public class Test01 { // 一个线程打电话, 一个线程发消息 // 先执行sendMsm还是先执行call public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sendMsm(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }, "B").start(); } } class Phone{ // synchronized: 锁的对象是方法的调用者 // 两个方法用的是同一个锁, 谁先拿到谁执行 public synchronized void sendMsm(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendMsm"); } public synchronized void call(){ System.out.println("call"); } }
-
说明
synchronized: 锁的对象是方法的调用者
两个方法用的是同一个锁, 谁先拿到谁执行
2. 问题2: 加锁方法和普通方法谁先执行
-
代码
public class Test02 { // 一个线程打电话, 一个线程发消息 // 先执行sendMsm还是先执行call public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(() -> { phone.sendMsm(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.hello(); }, "C").start(); } } class Phone2{ public synchronized void sendMsm(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendMsm"); } public void hello(){ System.out.println("hello"); } }
-
说明
hello是普通方法, 不受进程的影响.
所以hello先执行
3. 问题3: 不同对象调用, 谁先执行
-
代码
public class Test02 { // 一个线程打电话, 一个线程发消息 // 先执行sendMsm还是先执行call public static void main(String[] args) { Phone2 phone1 = new Phone2(); Phone2 phone2 = new Phone2(); new Thread(() -> { phone1.sendMsm(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }, "B").start(); } } class Phone2{ public synchronized void sendMsm(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendMsm"); } public synchronized void call(){ System.out.println("call"); } }
-
说明
先执行call, 再执行sendMsm
因为是不同的对象, 所以不是同一个锁
4. 问题4: 静态同步方法
-
代码
public class Test03 { // 一个线程打电话, 一个线程发消息 // 先执行sendMsm还是先执行call public static void main(String[] args) { Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); new Thread(() -> { phone1.sendMsm(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }, "B").start(); } } class Phone3{ public static synchronized void sendMsm(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendMsm"); } public static synchronized void call(){ System.out.println("call"); } }
-
说明
调用同一个对象或者不同对象都是先执行sendMsm
因为静态方法再类一加载就有了, 锁的是Class
5. 问题5: 静态同步方法和普通同步方法
-
代码
public class Test03 { public static void main(String[] args) { Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); new Thread(() -> { phone1.sendMsm(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }, "B").start(); } } class Phone3{ public static synchronized void sendMsm(){ System.out.println("sendMsm"); } public synchronized void call(){ System.out.println("call"); } }
-
说明
先执行静态同步方法, 再执行普通同步方法