JUC(重点)
JUC是指java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks三个包
基础
一个进程可以包含至少一个线程,Java中默认至少是两个线程的,一个main线程,一个GC线程。
Java本身是不可以创建线程的,而是调用本地方法start0开启线程,是C/C++编写的。
并发:多线程操作同一个资源,单核处理器处理多个任务,快速交替,看似同时执行,假同时执行
并行:多核处理器处理多个任务,真同时执行
Runtime.getRuntime().availableProcessors()//获取CPU核数
并发编程的本质:充分利用CPU的资源
线程六个状态:
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED
wait和sleep
- 来自不同类,wait来自Object类,sleep来自Thread类,一般用TimeUnit去睡眠
- sleep不释放锁,wait会释放锁
- wait只能使用在同步代码块中,wait总得有资源让等才行,sleep可以在任何地方使用
- wait不需要捕获异常,sleep必须捕获异常
new Thread((参数列表)->{ }, “线程名”).start();
Synchronized加锁,为对象加锁,为class加锁。
Lock锁(重点)
Lock接口
Lock接口的实现类包括可重入锁、写锁、读锁
- 公平锁:先来后到,需要排队
- 非公平锁:可以插队,默认是非公平锁
使用方法
没有synchronized的锁的自动释放,官方文档推荐用法如下:
private int num = 30;
Lock lock = new ReentrantLock();//可重入锁
public void sale(){
lock.lock();//加锁
try{//业务代码
if(num > 0){
System.out.println(Thread.currentThread().getName() + " : " + (--num));
}
}catch (Exception e){
e.printStackTrace();
} finally{
lock.unlock();//释放锁
}
}
Synchronized 和Lock区别
- Synchronized是内置关键字,Lock是一个Java类。
- Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁。
- Synchronized会自动释放锁,Lock必须手动释放锁,否则可能会出现死锁。
- Synchronized线程1获得锁,线程2一直等待,但是Lock锁就不一定会一直等,通过tryLock去尝试获取锁,等不到就结束,不能崩掉。
- Synchronized可重入锁,不可以中断,非公平; Lock可重入锁,可以判断锁,默认非公平(可以自己设置),ReentrantLock(true)就可设置为公平的。
- Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
生产者消费者问题
面试常问:单例模式、排序算法、生产者消费者问题、死锁问题
Synchronized的生产者消费者
//判断等待 业务 通知
class Data{
private int num = 0;
public synchronized void increment() throws InterruptedException {
if(num!=0){//if 判断会产生虚假唤醒,应当使用while进行循环判断
//等待
this.wait();
}
num++;//业务
System.out.println(Thread.currentThread().getName() + " : " + num);
//通知其他线程+1完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(num == 0){
//等待
this.wait();
}
num--;//业务
System.out.println(Thread.currentThread().getName() + " : " + num);
//通知其他线程 -1完毕
this.notifyAll();
}
}
多个线程如果产生虚假唤醒,会出现错误,应该将if判断改为while判断,线程wait之后,一般是需要等待其他线程调用notify,notifyAll方法后,线程才会从wait中返回,但是虚假唤醒则是通过其他方式从wait中返回。官方文档推荐使用循环判断条件。将以上的代码中的if改成while
JUC版本的生产者消费者
用Lock替换Synchronized的方法语句,用Condition取代对象监视器(wait、notify、notifyAll)官方文档推荐用法:
//判断等待 业务 通知
class Data1{
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
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();
}
}
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 (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
但是这个打印的结果是随机的,如果要顺序执行呢,通过建立多个监视器,每个监视监视一个资源,进行指定唤醒。
//判断等待 业务 通知
class Data2{
private int number = 1;
private Lock lock = new ReentrantLock();
//设置多个同步监视器,每个监视一个资源
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();//获取锁
try{
while(number !=1){
condition1.await();//等待
}
System.out.println(Thread.currentThread().getName() + " : " + "A");
number=2;
//唤醒指定的线程, 唤醒condition2
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
public void printB(){
lock.lock();
try{
while(number != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + " : " + "B");
number=3;
condition3.signal();//唤醒condition3
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
while(number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + " : " + "C");
number = 1;
condition1.signal();//唤醒condition1
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}