什么是JUC
源码+官方文档
JUC是 java util concurrent
面试高频问JUC~!
java.util 是Java的一个工具包
业务:普通的线程代码 Thread
Runnable: 没有返回值、效率相比于Callable 相对较低!
线程和进程
进程:一个程序,允许一个java程序会进程里面会出现一个java.exe;数据+代码+pcb
一个进程可以包含多个线程,至少包含一个线程!
Java默认有几个线程?2个线程! main线程、GC线程
线程:开了一个进程qq,聊天打字,消息提示(线程负责的)
对于Java而言:Thread、Runable、Callable进行开启线程的。
JAVA真的可以开启线程吗? 开不了的! 原因Java没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
并发、并行
并发: 多线程操作同一个资源。
CPU 只有一核,模拟出来多条线程,那么我们就可以使用CPU快速交替,来模拟多线程。
并行: 多个人并排行走。
CPU多核,多个线程可以同时执行。
public class Test { public static void main(String[] args) { //获取cpu的核数 System.out.println(Runtime.getRuntime().availableProcessors()); } }
并发编程的本质:充分利用CPU的资源!
线程的6个状态
public enum State { //创建 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待 WAITING, //超时等待 TIMED_WAITING, //终止 TERMINATED; }
面试题:谈一谈wait和sleep区别?
区别 | wait | sleep |
---|---|---|
操作的类 | Object | Thread |
锁的释放 | 会释放锁 | 抱着锁睡觉 |
范围 | 同步代码块中 | 任何地方 |
异常捕获 | 不需要捕获异常 | 需要捕获异常 |
Lock锁(重点)
synchronized锁问题
package com.zmz.day01; /** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest * @Author: 张晟睿 * @Date: 2021/9/5 14:01 * @Version: 1.0 */ //资源类 属性 + 方法 oop class Ticket{ private int num = 50; //卖票方式 synchronized 本质:队列 锁 public synchronized void sale(){ if(num > 0){ System.out.println(Thread.currentThread().getName()+ " 卖出了第"+ num +" 张票,剩余:"+ --num +" 张票"); } } } public class TicketTest { public static void main(String[] args) { //多线陈操作 //并发:多个线程操作同一个资源ticket Ticket ticket = new Ticket(); //@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式 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(); } }
Lock接口
公平锁: 公平,必须先来后到~;
非公平锁: 不公平,可以插队;(默认为非公平锁)
使用Lock进行操作
Lock
实现提供比使用synchronized
方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
。锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如
ReadWriteLock
的读锁。使用
synchronized
方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内。虽然
synchronized
方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock
接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用
synchronized
方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语:Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。
Lock
实现提供了使用synchronized
方法和语句的附加功能,通过提供非阻塞尝试来获取锁(tryLock()
),尝试获取可被中断的锁(lockInterruptibly()
) ,以及尝试获取可以超时(tryLock(long, TimeUnit)
)。一个
Lock
类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。 如果一个实现提供了这样的专门的语义,那么实现必须记录这些语义。请注意,
Lock
实例只是普通对象,它们本身可以用作synchronized
语句中的目标。 获取Lock
实例的监视器锁与调用该实例的任何lock()
方法没有特定关系。 建议为避免混淆,您不要以这种方式使用Lock
实例,除了在自己的实现中。除非另有说明,传递任何参数的
null
值将导致NullPointerException
被抛出。内存同步
所有
Lock
实施必须执行与内置监视器锁相同的内存同步语义,如The Java Language Specification (17.4 Memory Model) 所述 :不成功的锁定和解锁操作以及重入锁定/解锁操作,不需要任何内存同步效果。
成功的
lock
操作具有与成功锁定动作相同的内存同步效果。成功的
unlock
操作具有与成功解锁动作相同的内存同步效果。
实施注意事项
锁定采集(可中断,不可中断和定时)的三种形式在性能特征,排序保证或其他实施质量方面可能不同。 此外,在给定的
Lock
课程中,中断正在获取锁的能力可能不可用。 因此,不需要实现对所有三种形式的锁获取完全相同的保证或语义,也不需要支持正在进行的锁获取的中断。 需要一个实现来清楚地记录每个锁定方法提供的语义和保证。 它还必须遵守此接口中定义的中断语义,只要支持锁获取的中断,即全部或仅在方法输入。由于中断通常意味着取消,并且检查中断通常是不频繁的,所以实现可以有利于通过正常方法返回来响应中断。 即使可以显示中断发生在另一个动作可能已经解除了线程之后,这是真的。 一个实现应该记录这个行为。
package com.zmz.day01;/** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest2 * @Author: 张晟睿 * @Date: 2021/9/5 16:15 * @Version: 1.0 */ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *@ClassName TicketTest2 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ class Ticket2{ /* * 加锁三步 * 1.实例化lock对象 * 2.lock加锁 * 3.unlock解锁 * */ Lock l = new ReentrantLock(); private int num = 50; //卖票方式 synchronized 本质:队列 锁 public void sale(){ //加锁 l.lock(); try { //业务代码 if(num > 0){ System.out.println(Thread.currentThread().getName()+ " 卖出了第"+ num +" 张票,剩余:"+ --num +" 张票"); } } catch (Exception e) { e.printStackTrace(); } finally { //解锁 l.unlock(); } } } public class TicketTest2 { public static void main(String[] args) { //多线陈操作 //并发:多个线程操作同一个资源ticket Ticket ticket = new Ticket(); //@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式 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(); } }
区别 | synchronized | lock |
---|---|---|
名称 | 属于关键字 | 属于对象 |
状态 | 不可以获取锁的状态 | 可以获取锁的状态 |
锁的管理 | 自动释放锁 | 需要手动加锁以及释放锁 |
线程 | 自己抱着锁 | 等待 |
可重入锁,不可以中断的,非公平的 | 可重入的,可以判断锁,可以自己设置公平锁和非公平锁 | |
代码同步 | 适合少量的代码同步 | 适合大量的代码同步 |
生产者消费者问题
synchronized版
package com.zmz.day01; /** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest3 * @Author: 张晟睿 * @Date: 2021/9/5 16:35 * @Version: 1.0 */ /** *@ClassName TicketTest3 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ public class TicketTest3 { 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 number = 0; // +1操作 public synchronized void increment() throws InterruptedException { if(number != 0 ){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } // -1操作 public synchronized void decrement() throws InterruptedException{ if (number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
问题存在,A线程B线程,现在如果我有四个线程A B C D!该怎么去解决问题
if判断改为While判断就可以解决虚假唤醒的问题。
package com.zmz.day01; /** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: TicketTest3 * @Author: 张晟睿 * @Date: 2021/9/5 16:35 * @Version: 1.0 */ /** *@ClassName TicketTest3 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ //线程之间的通讯问题:生产者和消费者的问题! 等待唤醒,通知唤醒 //线程交替执行 A B操作同一个资源 public class TicketTest3 { 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.decrement(); } 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 number = 0; // +1操作 public synchronized void increment() throws InterruptedException { while(number != 0 ){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } // -1操作 public synchronized void decrement() throws InterruptedException{ while (number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
JUC版本的解决A B C D多线程的问题
package com.zmz.day01; /** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: JucTest1 * @Author: 张晟睿 * @Date: 2021/9/5 19:34 * @Version: 1.0 */ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *@ClassName JucTest1 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ public class JucTest1 { public static void main(String[] args) { Data2 data = new Data2(); new Thread(()->{for(int i=0;i<10;i++) { data.increment(); } },"A").start(); new Thread(()->{for(int i=0;i<10;i++) { data.decrement(); }},"B").start(); new Thread(()->{for(int i=0;i<10;i++) { data.increment(); } },"C").start(); new Thread(()->{for(int i=0;i<10;i++) { data.decrement(); } },"D").start(); } } class Data2{ private int number = 0; //lock锁 Lock l = new ReentrantLock(); Condition condition = l.newCondition(); public void increment() { l.lock(); try { //业务 while (number!=0){ //等待操作 condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程 我+1完毕了 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } public void decrement() { l.lock(); try { //业务 while (number==0){ //等待操作 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程 我-1完毕了 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } }
Condition的优势:精准通知、唤醒的线程
package com.zmz.day01; /** * @ProjectName: Juc * @Package: com.zmz.day01 * @ClassName: JucTest2 * @Author: 张晟睿 * @Date: 2021/9/5 19:52 * @Version: 1.0 */ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *@ClassName JucTest2 *@Description *@Author 张晟睿 *@Date 2021/9/5 **/ public class JucTest2 { public static void main(String[] args) { Data3 data3 = new Data3(); new Thread(()->{ for(int i=0;i<10;i++){ data3.printA(); } },"A").start(); new Thread(()->{ for(int i=0;i<10;i++){ data3.printB(); } },"B").start(); new Thread(()->{ for(int i=0;i<10;i++){ data3.printC(); } },"C").start(); } } class Data3{ private Lock l = new ReentrantLock(); Condition condition1 = l.newCondition(); Condition condition2 = l.newCondition(); Condition condition3 = l.newCondition(); private int flag = 1; public void printA(){ l.lock(); //判断 -> 执行 -> 通知 try { while(flag != 1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName() + "->A" ); flag = 2; //唤醒指定线程 condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } public void printB(){ l.lock(); //判断 -> 执行 -> 通知 try { while(flag != 2){ //等待 condition2.await(); } System.out.println(Thread.currentThread().getName() + "->BB" ); flag = 3; //唤醒指定线程 condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } public void printC(){ l.lock(); //判断 -> 执行 -> 通知 try { while(flag != 3){ //等待 condition3.await(); } System.out.println(Thread.currentThread().getName() + "->CCC" ); flag = 1; //唤醒指定线程 condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { l.unlock(); } } }
作者:Java厂长