JUC并发编程
1、什么是JUC
JUC:就是我们Java原生的并发包,和一些常用的工具类!
学完之后,很多知识,但是不知道怎么去用!每学习一个知识点,学完之后,可以替换工作中用到的代码!
2、线程基础知识回顾
什么是进程和线程?
进程:QQ.exe
线程:打字、自动保存.....
一个进程可以包含多个线程,一个进程至少有一个线程! Java程序至少有两个线程: GC、Main
并发、并行
并发:多个线程操作同一个资源,交替执行的过程!
并行:多个线程同时执行!只有在多核CPU下才能完成!
关于最高效率:所有CPU同时执行!
所以我们使用多线程或者并发编程的目的:提高效率,让CPU一直工作,达到最高处理性能!
线程有几种状态
线程有 6 种状态!
public enum State { // java能够创建线程吗? 不能! // 新建 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待 WAITING, // 延时等待 TIMED_WAITING, // 终止! TERMINATED; }
wait/Sleep区别
1、类不同!
wait : Obejct 类 Sleep Thread 在juc编程中,线程休眠怎么实现!Thread.Sleep // 时间单位 TimeUnit.SECONDS.sleep(3);
2、会不会释放资源!
sleep:抱着锁睡得,不会释放锁!wait 会释放锁!
3、使用的范围是不同的;
wait 和 notify 是一组,一般在线程通信的时候使用!
sleep 就是一个单独的方法,在那里都可以用!
4、关于异常;
sleep 需要捕获异常!
3、Lock锁
synchronized 传统的方式!
笔记:
代码:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * JUC之后的操作 * Lock锁 + lambda表达式! */ public class Demo02 { public static void main(String[] args) { // 1、新建资源类 Ticket2 ticket = new Ticket2(); // 2、线程操作资源类 , 所有的函数式接口都可以用 lambda表达式简化! // lambda表达式 (参数)->{具体的代码} new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"A").start(); new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"B").start(); new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"C").start(); } } // 依旧是一个资源类 class Ticket2{ // 使用Lock,它是一个对象 // ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...) // ReentrantLock 默认是非公平锁! // 非公平锁: 不公平 (插队,后面的线程可以插队) // 公平锁: 公平(只能排队,后面的线程无法插队) private Lock lock = new ReentrantLock(); private int number = 30; public void saleTicket(){ lock.lock(); // 加锁 try { // 业务代码 if (number>0){ System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } }
问题:
Lock 锁
代码:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * JUC之后的操作 * Lock锁 + lambda表达式! */ public class Demo02 { public static void main(String[] args) { // 1、新建资源类 Ticket2 ticket = new Ticket2(); // 2、线程操作资源类 , 所有的函数式接口都可以用 lambda表达式简化! // lambda表达式 (参数)->{具体的代码} new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"A").start(); new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"B").start(); new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"C").start(); } } // 依旧是一个资源类 class Ticket2{ // 使用Lock,它是一个对象 // ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...) // ReentrantLock 默认是非公平锁! // 非公平锁: 不公平 (插队,后面的线程可以插队) // 公平锁: 公平(只能排队,后面的线程无法插队) private Lock lock = new ReentrantLock(); private int number = 30; public void saleTicket(){ lock.lock(); // 加锁 try { // 业务代码 if (number>0){ System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } }
Synchronized 和 Lock 区别
1、Synchronized 是一个关键字、Lock 是一个对象
2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;
3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。
4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃
5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;
6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;
4、生产者消费者问题
面试手写题:单例模式、排序算法、死锁、生产者消费者
线程和线程之间本来是不能通信的,但是有时候我们需要线程之间可以协调操作:
Synchronized 普通版
// Synchronized 版 /* 目的:有两个线程:A B ,还有一个值初始为0, 实现两个线程交替执行,对该变量 + 1,-1;交替10次 */ public class Demo03 { public static void main(String[] args) { Data data = new Data(); // +1 new Thread(()->{ for (int i = 1; i <=10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); // -1 new Thread(()->{ for (int i = 1; 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(); //唤醒所有线程 } }
四条线程可以实现交替吗?不能,会产生虚假唤醒问题!
注意if 和 while的区别:
// Synchronized 版 /* 目的: 有两个线程:A B ,还有一个值初始为0, 实现两个线程交替执行,对该变量 + 1,-1;交替10次 传统的 wait 和 notify方法不能实现精准唤醒通知! */ public class Demo03 { public static void main(String[] args) { Data data = new Data(); // +1 new Thread(()->{ for (int i = 1; i <=10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i = 1; i <=10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); // -1 new Thread(()->{ for (int i = 1; i <=10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i = 1; 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 挂钩!
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* 实现线程交替执行! 主要的实现目标:精准的唤醒线程! 三个线程:A B C 三个方法:A p5 B p10 C p15 依次循环 */ public class Demo04 { public static void main(String[] args) { Data2 data = new Data2(); new Thread(()->{ for (int i = 1; i <= 10; i++) { try { data.print5(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i = 1; i <= 10; i++) { try { data.print10(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i = 1; i <= 10; i++) { try { data.print15(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); } } // 资源类 class Data2{ private int number = 1; // 1A 2B 3C private Lock lock = new ReentrantLock(); // 实现精准访问 private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void print5() throws InterruptedException { lock.lock(); try { // 判断 while (number!=1){ condition1.await(); } // 执行 for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "" + i); } // 通知第二个线程干活! number = 2; condition2.signal(); // 唤醒 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); // 一定要解锁 } } public void print10() throws InterruptedException { lock.lock(); try { // 判断 while (number!=2){ condition2.await(); } // 执行 for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + "" + i); } // 通知3干活 number = 3; condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15() throws InterruptedException { lock.lock(); try { // 判断 while (number!=3){ condition3.await(); } // 执行 for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + "" + i); } // 通知 1 干活 number = 1; condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
新的技术出来,一定是可以替换一些旧技术的!