JUC高并发编程
1、JUC简介
-
在Java中,线程部分是一个重点,JUC:就是Java.util.concurrent工具包的检查,这是一个处理线程的工具包,JDK1.5开始出现
-
进程与线程:
- 进程:之在系统中正在运行的一个应用程序;程序一旦运行就是进程,进程--资源分配的最小单元
- 线程:系统分配处理器时间资源的基本单元或者说是进程之内独立执行的一个执行流。程序执行的最小单位。
-
线程的状态:
- New 新建
- Runnable 准备就绪
- Blocked 阻塞
- Waiting (等待,不见不散)
- TimedWaiting (过时不候)
- Terminated (终结)
-
Wait和Sleep 区别
- sleep是Thread的静态方法 ,wait是Object的方法,任何对象都能实列都能调用
- sleep不会释放锁,他也不需要占用锁,wait会释放锁,但调用他的前提是当前线程占有锁(即代码要在synchronized中)
- 他们都可以被 interrupted 方法中断
-
并发与并行
-
先了解一下:串行与并行
- 串行模式:一次只能获取一个任务并执行这个任务。
- 并行模式:同时获取多个任务并同时去执行所得到的这些任务。
-
并发:指的是多个程序可以同时运行的想象,更细化的是多线程可以同时运行或多指令可以同时运行
-
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
例如:春运抢票,电商秒杀
-
并行:多项工作一起执行,然后再汇总
例如:泡方便面,电水壶烧水,一边撕调料包倒入桶中
-
-
管程
- Monitor(操作系统中叫:监视器,Java中通常叫做:锁):是一种同步机制,保证同一个时间,只有一个线程访问被保护的数据或者代码
- Jvm中的同基于进入和退出,使用管程对象实现的
-
用户线程和守护线程
-
用户线程:自定义的线程,平时使用的到的大部分都是用户线程
案例:主线程结束了:用户线程还在运行,jvm存活
package com.codetip.codejuc.juc; public class Test01 { public static void main(String[] args) { // isDaemon 表示是用户线程还是守护线程 // true :守护线程 false 用户线程 new Thread(() -> { System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon()); // 设置循环不让其终止 while (true) { } }, "aa").start(); System.out.println(Thread.currentThread().getName() + " Over"); } }
-
守护线程:后台中一种特定的线程,比如:垃圾回收
案例:没有了用户线程,都是守护线程了,jvm就会结束了
package com.codetip.codejuc.juc; public class Test01 { public static void main(String[] args) { // isDaemon 表示是用户线程还是守护线程 // true :守护线程 false 用户线程 Thread aa = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon()); while (true) { } }, "aa"); // 设置线程为守护线程 aa.setDaemon(true); aa.start(); System.out.println(Thread.currentThread().getName() + " Over"); } } // 执行结果如下图
-
2、Lock接口
-
复习Synchronized:Synchronized是Java中的一个关键字,是一种同步锁,修饰对象(作用范围)有以下几种
-
synchronized 作用范围
// 修饰代码块 synchronized(this){ // 修饰一个代码块,被修饰的代码块为同步方法,其作用范围是 大括号{}里面的代码,作用的对象是调用这个代码块的对象。 } // 修饰一个方法 public synchronized add(){ // 修饰一个代码块,被修饰的代码块为同步方法,其作用范围是整个方法,作用的对象是调用这个方法的对象 } // 修饰一个静态方法 public static synchronized save(){ // 修饰一个静态方法,作用范围是整个静态方法,作用对象是这个类的所有对象 } // 修饰一个类 public synchronized class test001(){ // 作用范围是synchronized后面大括号括起来的内容,作用对象是这个类的所有对象 }
-
多线程编程步骤
- 创建资源类,创建资源类的方法和属性
- 在资源类中操作方法:1、判断 2、干活 3、通知
- 创建多个线程,调用资源类的操作方法
-
创建线程的几种方式
- 集成Thread类(一般不用,java中是单继承,继承是很珍贵的)
- 实现Runnable接口
- 使用Callable接口(后面讲解)
- 使用线程池(后面讲解)
-
Synchronize实现买票的例子
-
三个售票员,卖30张票
package com.codetip.codejuc.juc.sync; // 第一步、创建资源类,定义属性和操作方法 class Ticket { // 票数 private int number = 30; // 售票方法 public synchronized void sale() { // 买票过程 if (number > 0) { System.out.println(Thread.currentThread().getName() + ":卖出一张票: " + 1 + " 剩余:" + --number); } } } public class SaleTicked { public static void main(String[] args) { Ticket ticket = new Ticket(); // 线程一 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 40; i++) { ticket.sale(); } } }, "aa").start(); // 线程二 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 40; i++) { ticket.sale(); } } }, "bb").start(); // 线程三 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 40; i++) { ticket.sale(); } } }, "cc").start(); } }
-
-
-
Lock接口
- 介绍:为锁和等待条件提供一个框架的接口和类,它不同于内置的同步和监视器,详细介绍:查看API文档(百度一下)
- 线程中调用start 方法是否会立即创建线程,不一定。里面有一个native 关键字,执行到这里调用操作系统的命令了,如果当时系统空闲可能会立即创建一个线程。
使用lock重写售票代码:
package com.codetip.codejuc.juc.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; // 第一步、创建资源类,定义属性和操作方法 class LockTicket { private final Lock lock = new ReentrantLock(); // 票数 private int number = 30; // 售票方法 public void sale() { try { // 上锁 lock.lock(); // 售票过程 if (number > 0) { System.out.println(Thread.currentThread().getName() + ":卖出一张票: " + 1 + " 剩余:" + --number); } } finally { // 解锁 lock.unlock(); } } } public class LockTicked { public static void main(String[] args) { LockTicket ticket = new LockTicket(); // 线程一 new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "aa").start(); // 线程二 new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "bb").start(); // 线程三 new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "cc").start(); } }
3、线程间通信
-
案例:有两个线程,实现对一个初始值是0的变量。 一个线程对值加1 一个线程对值减1
// 使用 synchronized 关键字 package com.codetip.codejuc.juc.number; // 第一步 创建资源类,定义属性和方法 class Share { // 属性 private int num = 0; // 方法 加1 public synchronized void incr() throws InterruptedException { // 判断,干活,通知 if (num != 0) { // 判断num 是否等于 0 如果不是0 则等待 this.wait(); } // 如果num 是 0 就加1 操作 num++; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 this.notifyAll(); } // 方法减1 public synchronized void decr() throws InterruptedException { // 判断,干活,通知 if (num != 1) { // 判断num 是否等于 1 如果不是1 则等待 this.wait(); } // 如果num 是 0 就减1 操作 num--; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 this.notifyAll(); } } public class Number { public static void main(String[] args) { Share share = new Share(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "a").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "b").start(); } }
结果如下:
-
虚假唤醒问题:在代码中在添加两个线程c 和 d
package com.codetip.codejuc.juc.number; // 第一步 创建资源类,定义属性和方法 class Share { // 属性 private int num = 0; // 方法 加1 public synchronized void incr() throws InterruptedException { // 判断,干活,通知 if (num != 0) { // 判断num 是否等于 0 如果不是0 则等待 this.wait(); } // 如果num 是 0 就加1 操作 num++; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 this.notifyAll(); } // 方法减1 public synchronized void decr() throws InterruptedException { // 判断,干活,通知 if (num != 1) { // 判断num 是否等于 1 如果不是1 则等待 this.wait(); } // 如果num 是 0 就减1 操作 num--; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 this.notifyAll(); } } public class Number { public static void main(String[] args) { Share share = new Share(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "a").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "b").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "c").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "d").start(); } }
执行结果:
看到和预期不一样的效果
出现的原因在:wait这里。API中wait中的具体说明:对于摸一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应该始终在循环中使用
其实原因就是:A正常执行后,会唤醒其他锁,但唤醒后线程无法确定是哪个在执行,而且wait方法的特点 在哪里睡在哪里醒,所以有些线程没有再次执行判断方法。
修改后代码如下:(修改if为while循环)
package com.codetip.codejuc.juc.number; // 第一步 创建资源类,定义属性和方法 class Share { // 属性 private int num = 0; // 方法 加1 public synchronized void incr() throws InterruptedException { // 判断,干活,通知 while (num != 0) { // 判断num 是否等于 0 如果不是0 则等待 this.wait(); } // 如果num 是 0 就加1 操作 num++; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 this.notifyAll(); } // 方法减1 public synchronized void decr() throws InterruptedException { // 判断,干活,通知 while (num != 1) { this.wait(); } // 如果num 是 1 就减1 操作 num--; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 this.notifyAll(); } } public class Number { public static void main(String[] args) { Share share = new Share(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "a").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "b").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "c").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "d").start(); } }
执行结果如下:
-
通过lock接口的实现
创建Lock类
创建lock 的监视类,
Lock lock = new ReentrantLock(); // lock的监视器 Condition condition = lock.newCondition();
package com.codetip.codejuc.juc.number;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;// 第一步 创建资源类,定义属性和方法class ShareL { // 属性 private int num = 0; Lock lock = new ReentrantLock(); // lock的监视器 Condition condition = lock.newCondition(); // 方法 加1 public void incr() throws InterruptedException { // 判断,干活,通知 lock.lock(); try { while (num != 0) { // 判断num 是否等于 0 如果不是0 则等待 condition.await(); } // 如果num 是 0 就加1 操作 num++; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 condition.signalAll(); } finally { lock.unlock(); } } // 方法减1 public void decr() throws InterruptedException { lock.lock(); try { // 判断,干活,通知 while (num != 1) { condition.await(); } // 如果num 是 1 就减1 操作 num--; System.out.println(Thread.currentThread().getName() + "值为:" + num); // 通知其他线程 condition.signalAll(); } finally { lock.unlock(); } }}public class LockNumber { public static void main(String[] args) { ShareL share = new ShareL(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "a").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "b").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "c").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "d").start(); }}
4、线程间定制化通信
让线程按照指定顺序执行
案例:启动三个线程,AA打印五次,BB打印10次 CC打印15次 进行10轮测试
方案:设计一个标志位 flag ==1 打印,并修改标志位,通知BB ,以此类推 AA flag==2 BB flag ==3 CC
package com.codetip.codejuc.juc.custom;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class ShareResource { // 定制标志位 private int flag = 1; // 1:AA 2:BB 3:CC // 创建锁 Lock lock = new ReentrantLock(); // 创建三个Condition (相当于监听) Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); // 打印五次 public void print5(int loop) throws InterruptedException { // 第一步上锁 lock.lock(); try { // 放入循环中防治虚假唤醒 while (flag != 1) { c1.await(); } for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "循环的值:" + i + " 当前是第" + loop + "轮"); } // 修改标志位通知 BB flag = 2; c2.signal(); } finally { lock.unlock(); } } // 打印五次 public void print10(int loop) throws InterruptedException { // 第一步上锁 lock.lock(); try { // 放入循环中防治虚假唤醒 while (flag != 2) { c2.await(); } for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + "循环的值:" + i + " 当前是第" + loop + "轮"); } // 修改标志位通知 CC flag = 3; c3.signal(); } finally { lock.unlock(); } } // 打印五次 public void print15(int loop) throws InterruptedException { // 第一步上锁 lock.lock(); try { // 放入循环中防治虚假唤醒 while (flag != 3) { c3.await(); } for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + "循环的值:" + i + " 当前是第" + loop + "轮"); } // 修改标志位通知 AA flag = 1; c1.signal(); } finally { lock.unlock(); } }}public class ThreadPrint { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(() -> { for (int i = 0; i <= 10; i++) { try { shareResource.print5(i); } catch (InterruptedException e) { e.printStackTrace(); } } }, "AA").start(); new Thread(() -> { for (int i = 0; i <= 10; i++) { try { shareResource.print10(i); } catch (InterruptedException e) { e.printStackTrace(); } } }, "BB").start(); new Thread(() -> { for (int i = 0; i <= 10; i++) { try { shareResource.print15(i); } catch (InterruptedException e) { e.printStackTrace(); } } }, "CC").start(); }}
运行如下: