写在前面
- 本文在学习JUC过程中,自己总结的一份JUC知识点,涵盖知识点,代码Demo
- 源码 GITHUB 地址:源码地址
- 将会持续更新
- 如果有什么地方总结的不完善或出现错误,欢迎留言补充和改正,共同进步!
JUC目录
JUC 基础
1. 什么是线程和进程?举例说明
-
进程是系统中正在运行的一个程序,是系统资源分配的独立实体,每个进程都拥有独立的地址空间,程序一旦运行就是进程。
-
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
-
线程通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。
-
线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
-
举例:QQ / WeChat 就是一个进程,每一个聊天窗口就是一个个线程。
2. 线程的状态有哪些?
-
线程有六种状态:新建,运行,阻塞,等待,定时等待,终结
-
java.lang.Thread.State
枚举类 -
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
3. wait 和 sleep 的区别?
- wait() 是Object 包提供的方法,sleep() 是Thread包下提供的方法
java.lang.Thread#sleep()
java.lang.Object#wait()
- wait 是放开锁睡眠,睡眠结束从新争取资源
- sleep 线程不会释放对象锁睡眠,睡眠结束不需要争取资源,整个程序在其睡眠期间阻塞等待
4. 什么是并发,什么是并行?
- 并发:同一时刻多个线程访问同一个资源,多个线程对一个点。如:秒杀
- 并行:多项任务同时进行。(一边… 一边…,泡方便面,电水壶烧水,一边撕调料倒入桶中)
5. JUC 常见异常
java.util.ConcurrentModificationException
:并发修改异常java.util.NoSuchElementException
:常发生在阻塞队列java.util.concurrent.RejectedExecutionException
:触发线程池默认拒绝策略,AbortPolicy
JUC Lock接口
1. Lock 和 Synchronized 区别在哪?
- Lock是个接口,而 synchronized 是java关键字,synchronized 是内置语言实现
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象的发生;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很有可能造成死锁现象,因此使用Lock时需要在finally 块中释放锁。
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到,不能判断锁状态
- 在性能上来说,如果资源竞争不激烈的话,两者的性能是差不多的;而当资源竞争非常激烈(即有大量线程同时竞争)时,Lock的性能要远远优于synchronized
- 少量同步可用 synchronized,大量同步时一般使用 Lock
2. 经典买票问题
-
synchronized
public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); int number = 2000; new Thread(() -> { for (int i = 0; i < number; i++) { ticket.sale(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < number; i++) { ticket.sale(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < number; i++) { ticket.sale(); } }, "C").start(); } } class Ticket { private int number = 300; synchronized void sale() { if (number > 0) { System.out.println(Thread.currentThread().getName() + "剩余:" + --number + "张"); } } }
-
Lock
public class SaleTicket02 { public static void main(String[] args) { Ticket02 ticket = new Ticket02(); int number = 4000; new Thread(() -> { for (int i = 0; i < number; i++) { ticket.sale(); } }, "AA").start(); new Thread(() -> { for (int i = 0; i < number; i++) { ticket.sale(); } }, "BB").start(); new Thread(() -> { for (int i = 0; i < number; i++) { ticket.sale(); } }, "CC").start(); } } class Ticket02 { private Lock lock = new ReentrantLock(); private int number = 3000; void sale() { lock.lock(); try { if (number > 0) { System.out.println(Thread.currentThread().getName() + "剩余:" + --number + "张"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
JUC 线程间通信
1. 多线程的虚假唤醒
- TODO 待完善
2. 面试题一:
问题: 现在两个线程。可以操作一个初始值为0的变量,实现一个线程对改变量加一,一个线程对该变量减一。实现交替执行10轮后,变量初始值为0。
-
synchronized 方式:
class Operation { private int number = 0; synchronized void increment() throws InterruptedException { // 必须为while,防止线程虚假唤醒 while (number != 0) { this.wait(); } System.out.println(Thread.currentThread().getName() + ",当前数+1, 为: " + ++number); this.notifyAll(); } synchronized void decrement() throws InterruptedException { while (number != 1) { this.wait(); } System.out.println(Thread.currentThread().getName() + ",当前数-1, 为: " + --number); this.notifyAll(); } } public class ThreadWaitNotifyDemo { public static void main(String[] args) { int num = 11; Operation operation = new Operation(); new Thread(() -> { for (int i = 1; i < num; i++) { try { operation.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "AA").start(); new Thread(() -> { for (int i = 1; i < num; i++) { try { operation.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "BB").start(); } }
-
Lock 方式
public class ThreadWaitNotifyDemo02 { public static void main(String[] args) { int num = 10; Operation02 operation02 = new Operation02(); new Thread(() -> { for (int i = 0; i < num; i++) { operation02.increment(); } }, "AA").start(); new Thread(() -> { for (int i = 0; i < num; i++) { operation02.decrement(); } }, "BB").start(); } } class Operation02 { private int number = 0; private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); void increment() { lock.lock(); try { while (number != 0) { condition.await(); } System.out.println(Thread.currentThread().getName() + ",\t" + ++number); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } void decrement() { lock.lock(); try { while (number != 1) { condition.await(); } System.out.println(Thread.currentThread().getName() + ",\t" + --number); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
3. 面试题二:
两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B…5152Z,要求用线程间通信。
public class ThreadWaitNotifyDemo03 {
public static void main(String[] args) {
Handler handler = new Handler();
new Thread(handler::printNum, "打印1-52").start();
new Thread(handler::printChar, "打印A-Z").start();
}
}
class Handler {
private static int num = 1;
private Lock lock = new ReentrantLock();
private Condition priNum = lock.newCondition();
private Condition priChr = lock.newCondition();
void printNum() {
lock.lock();
try {
int total = 53;
for (int i = 1; i < total; i++) {
while (num