java 多线程

java多线程

1、Process与Thread

  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • 进程是执行程序的一次执行过程,是一个动态概念。是系统资源分配的单位
  • 一个进程中可以包含若干个线程,一个进程中至少有一个线程。线程是CPU调度和执行的单位

注意:很多多线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在统一时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉

2、核心概念

  • 线程就是独立的执行路径
  • 在程序运行时,即使自己没有创建线程,后台也会有多个线程,如主线程,gc线程
  • main() 称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后的顺序是不能人为的干预的
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU调度时间并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

3、并发与并行

  • 并发(concurrent):是同一时间应对(dealing with)多件事情的能力(轮流交替
  • 并行(parallel):是同一时间动手做(doing)多件事情的能力(同时

4、三种创建方式

  • 继承Thread(不建议使用:避免OOP单继承局限性)

    • 自定义线程类继承Thread

    • 重写run()方法,编写线程执行体

    • 创建线程对象,调用start()方法启动线程

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 线程开启不一定立即执行,由CPU调度执行
      public class ThreadDemo extends Thread {
      @Override
      public void run() {
      for (int i = 0; i < 20; i++) {
      System.out.println("我在多线程" + i);
      }
      }
      public static void main(String[] args) {
      ThreadDemo threadDemo = new ThreadDemo();
      threadDemo.start();

      for (int i = 0; i < 2000; i++) {
      System.out.println("我在主线程"+i);
      }
      }
      }// 主线程与多线程执行结果交替出现
  • 实现Runnable接口(推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用)

    • 定义类实现Runnable接口

    • 实现run()方法,编写线程执行体

    • 创建线程对象,调用start()方法启动线程

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class RunnableDemo implements Runnable{
      @Override
      public void run() {
      for (int i = 0; i < 20; i++) {
      System.out.println("我在学习多线程" + i);
      }
      }
      public static void main(String[] args) {
      RunnableDemo runnableDemo = new RunnableDemo();
      // 创建线程对象,通过线程对象来开启我们的线程,代理
      new Thread(runnableDemo).start();

      for (int i = 0; i < 2000; i++) {
      System.out.println("我在主线程"+i);
      }
      }
      }

      龟兔赛跑实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      public class Race implements Runnable{
      // 胜利者
      private static String winner;
      @Override
      public void run() {
      for (int i = 0; i < 10001; i++) {

      // 模拟兔子休息
      if (Thread.currentThread().getName().equals("兔子") && i%10 == 0){
      try {
      Thread.sleep(10);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      // 判断比赛是否结束
      boolean flag = gameOver(i);
      if(flag){
      break;
      }
      System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
      }
      }
      // 判断是否完成比赛
      private boolean gameOver(int steps){
      // 判断是否由胜利者
      if(winner != null){
      // 已经存在胜利者
      return true;
      }
      if(steps >= 10000){
      winner = Thread.currentThread().getName();
      System.out.println("Winner is "+winner);
      return true;
      }
      return false;
      }
      public static void main(String[] args) {
      Race race = new Race();
      new Thread(race,"乌龟").start();
      new Thread(race,"兔子").start();
      }
      }
  • 实现Callable接口

    • 实现Callable接口,需要返回值类型

    • 重写call方法,需要抛出异常

    • 创建目标对象

    • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);

    • 提交执行:Future result1 = ser.submit(t1);

    • 获取结果:boolean r1 = result1.get()

    • 关闭服务:ser.shutdownNow();

      实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      import java.util.concurrent.*;
      public class CallableDemo implements Callable<Boolean> {
      private String name;

      public CallableDemo(String name) {
      this.name = name;
      }

      public CallableDemo() {
      }
      @Override
      public Boolean call() throws Exception {
      for (int i = 0; i < 100; i++) {
      System.out.println(name + "看了" + i + "个视频");
      }
      return true;
      }
      public static void main(String[] args) throws ExecutionException, InterruptedException {
      CallableDemo callableDemo1 = new CallableDemo("小明");
      CallableDemo callableDemo2 = new CallableDemo("小智");
      CallableDemo callableDemo3 = new CallableDemo("小爱");

      // 创建执行服务
      ExecutorService executorService = Executors.newFixedThreadPool(3);

      // 提交执行
      Future<Boolean> submit1 = executorService.submit(callableDemo1);
      Future<Boolean> submit2 = executorService.submit(callableDemo2);
      Future<Boolean> submit3 = executorService.submit(callableDemo3);

      // 获取结果
      Boolean aBoolean1 = submit1.get();
      Boolean aBoolean2 = submit2.get();
      Boolean aBoolean3 = submit3.get();

      // 关闭服务
      executorService.shutdown();

      System.out.println("小明的观看结果"+aBoolean1);
      System.out.println("小智的观看结果"+aBoolean2);
      System.out.println("小爱的观看结果"+aBoolean3);
      }
      }

5、静态代理

概念

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色
  • 好处:
    • 代理对象可以做很多真实对象做不了的事情
    • 真实对象专注做自己的事情

6、Lamda表达式

定义

  • 避免匿名内部类定义过多、代码看起来简洁、去掉一堆没有意义的代码,只留下核心的逻辑

  • 其实质属于函数式编程的概念

    1
    2
    3
    (params) -> expression[表达式]
    (params) -> statement[语句]
    (params) -> { statements }

(Functional Interface)函数式接口

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

    1
    2
    3
    public interface Runnable{
    public abstract void run();
    }
  • 对于函数式接口,可以通过lambda表达式来创建接口的对象

总结:

  • lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,则必须用代码块包裹

  • 使用lambda表达式的前提式接口为函数式接口

  • 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class LambdaDemo {
    public static void main(String[] args) {
    ILove iLove = (a, b) -> {
    System.out.println("I love you " + a + b);
    };
    iLove.love(13, 14);
    }
    interface ILove {
    void love(int a, int b);
    }
    }

7、线程状态

停止线程

  • 不推荐使用JDK提供的 stop() 方法、destroy()方法。
  • 推荐让线程自己停下来
  • 建议使用一个标志位进行终止变量,当 flag = false ,则终止线程运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadStopDemo implements Runnable {
// volatile关键字修饰的变量看到的随时是自己的最新值。在线程1中对变量v的最新修改,对线程2是可见的。
private volatile boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("线程在执行... " + i++ + " 次");
}
}
public void stop() {
this.flag = false;
}
public static void main(String[] args) throws InterruptedException {
ThreadStopDemo threadStopDemo = new ThreadStopDemo();
new Thread(threadStopDemo).start();

for (int i = 0; i < 5; i++) {
Thread.sleep(5);
if (i == 4) {
// 调用stop方法切换标志位,让线程停止
threadStopDemo.stop();
System.out.println("线程该停止了");
}
}
}
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常 InterruptException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

礼让线程

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功!看CPU心情

Join

  • Join合并线程,待此线程执行完成后,再执行其它线程,其它线程阻塞

线程状态观测 Thread.State

  • NEW

    尚未启动的线程处于此状态

  • RUNNABLE

    在java虚拟机中执行的线程处于此状态

  • BLOCKED

    被阻塞等待监视器锁定的线程处于此状态

  • WAITING

    正在等待另一个线程执行特定动作的线程处于此状态

  • TIMED_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

  • TERMINATED

    已退出的线程处于此状态

一个线程可以在给定时间点处于一个状态,这些状态是不反映任何操作系统线程状态的虚拟机状态

7.1、线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY = 1
    • Thread.MAX_PRIORITY = 10
    • Thread.NORM_PRIORITY = 5
  • 使用以下方式改变或获取优先级
    • getPriority()
    • setPriority(int xxx)

优先级的设定建议在start()调度前

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度

7.2、守护线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕,如:后台记录操作日志,监控内存,垃圾回收等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DaemonDemo {
public static void main(String[] args) {
God god = new God();
You you = new You();

Thread thread = new Thread(god);
thread.setDaemon(true); // 默认为false,表示为用户线程,正常的线程都是用户线程
thread.start(); // 守护线程启动

new Thread(you).start();// 用户线程启动
}
}

//守护线程
class God implements Runnable{

@Override
public void run() {
while (true){
System.out.println("上帝保佑着你...");
}
}
}

class You implements Runnable{

@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("第"+i+"天,你也活的很开心");
}
System.out.println("---------Goodbye! World---------");
}
}

8、线程的同步

多个线程操作同一个资源

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候需要线程同步。

线程同步其实就是一直等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用

队列和锁

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其它线程必须等待,使用后释放锁即可。存在一下问题:
    • 一个线程持有锁会导致其它所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

8.1、同步方法

synchronized 方法和synchronized 块

同步方法

1
public synchronized voiidd method(init args){}
  • synchronized 方法控制对对象的访问,每个对象对应一把,每个 synchronized 方法都必须获得调用该方法的对象的才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

缺陷: 若将一个大的方法声明为 synchronized 将会影响效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class SaveThread {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();

new Thread(station, "悲惨的我").start();
new Thread(station, "牛逼的手速党").start();
new Thread(station, "可恶的黄牛党").start();

}
}
class BuyTicket implements Runnable {
// 票
private int ticketNum = 10;
//标志位
private boolean flag = true;

@Override
public void run() {
// 买票
while (flag) {
// 模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
// synchronized 同步方法,锁的是this
private synchronized void buy() {
// 判断是否有票
if (ticketNum <= 0) {
flag = false;
return;
}
// 买票
System.out.println(Thread.currentThread().getName() + "买到第 " + ticketNum-- + " 张票");
}
}

同步块

1
synchronized(Obj){}

Obj称之为同步监视器

  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或是class

同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中的代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问呢,发现同步监视器没有锁,然后锁定并访问

锁的对象是变化的量,需要增删改的对象

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}

9、死锁

多个线程各自占有一些共享资源,并且互相等待其它线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放锁,都停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题,避免 synchronized 嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLockDemo {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");

g1.start();
g2.start();
}
}

//口红
class Lipstick {

}

//镜子
class Mirror {

}

class Makeup extends Thread {
// 需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();

int choice;//选择
String girlName;//使用化妆品的人

Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}

@Override
public void run() {
// 化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
// synchronized (mirror) {//一秒钟后想获得镜子
// System.out.println(this.getName() + "获得镜子的锁");
// }
}
synchronized (mirror) {//一秒钟后想获得镜子
System.out.println(this.getName() + "获得镜子的锁");
}
} else {
synchronized (mirror) {//获得镜子的锁
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(2000);
// synchronized (lipstick) {//一秒钟后想获得口红
// System.out.println(this.getName() + "获得口红的锁");
// }
}
synchronized (lipstick) {//一秒钟后想获得口红
System.out.println(this.getName() + "获得口红的锁");
}
}
}
}

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

上面列出了死锁的四个必要条件,只要破除其中任意一个或多个条件就可以避免死锁发生

10、Lock(锁)

  • 显示定义同步锁对象来实现同步,同步锁对象使用Lock对象充当

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

  • ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class A {
    private final ReentrantLock lock = new ReenTrantLock();
    public void m(){
    lock.lock();
    try{
    //保证线程安全的代码
    }
    finally{
    lock.unlock();
    //如果同步代码有异常,要将unlock()写入finally语句块
    }
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 锁
public class Lock {
public static void main(String[] args) {
SaleSheet saleSheet = new SaleSheet();

new Thread(saleSheet, "小明").start();
new Thread(saleSheet, "小智").start();
new Thread(saleSheet, "小黑").start();
}
}

class SaleSheet implements Runnable {
// 票
private int tickets = 10;

// 定义lock锁
private final ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();//加锁
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "张票");
} else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
}

synchronized 与 Lock 的对比

  • Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁synchronized 是隐式锁,出了作用域自动释放

  • Lock 只有代码块锁,synchronized 有代码块锁和方法锁

  • 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  • 优先使用顺序:

    • Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

wait() 方法的问题

  1. 一个线程占有了一个对象的锁以后,cpu 资源仍然能被其它线程抢走。

  2. 当其它线程抢走 cpu 后,发现获取不到对象锁,则此线程会进入阻塞状态,并且进入由于获取不到锁而阻塞的阻塞队列中。

  3. 当占有锁的线程,释放锁 的时候,就会立即唤醒等待对象锁的其它线程(在阻塞队列中的)。

    • 释放锁包含了所有释放锁的情况

      1. 线程退出同步块

        1
        2
        3
        synchronized(obj){//线程进入同步块时,会尝试获取锁

        }//线程退出同步块时,会释放占有的对象锁,且唤醒其它等待锁的线程(都在阻塞队列中)
      2. 线程调用了 wait 方法时,会释放 cpu 释放锁,因为 wait 也会引起锁的释放,而一旦释放锁,也就会立即唤醒那些等待锁的线程。

  4. 线程阻塞的原因,有三大类:

    1. 尝试获取锁,得不到锁时,就会进入等待锁的阻塞队列中
      • 只有占有锁的线程释放锁时才会唤醒这个队列中的线程
      • notify() 不会唤醒该队列中的线程
    2. 正在占有锁的线程,调用了 wait() 就进入了 wait 阻塞队列中
      • 只有 obj.notify() 方法,才会唤醒这个队列中的线程
      • 一般的释放锁,无法唤醒该队列中的线程
    3. 正在执行中的线程,调用了 sleep() 或者 IO,就进入了另一个阻塞队列
      • 睡眠时间到,或者 IO 阻塞结束,线程才得以继续进入可运行的状态
    4. 线程唤醒之后进入就绪队列

11、线程协作

生产者消费者问题

线程通信

方法名 作用
wait() 表示线程一直等待,直到其它线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常

并发协作模型

并发协作模型”生产者/消费者模式” –> 管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们直接有个”缓冲区”

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//测试:生产者、消费者 利用缓冲区来解决
//生产者、消费者、产品、缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}

//生产者
class Productor extends Thread {
SynContainer container;

public Productor(SynContainer container) {
this.container = container;
}
// 生产

@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了" + i + "只鸡");
}
}
}

//消费者
class Consumer extends Thread {
SynContainer container;

public Consumer(SynContainer container) {
this.container = container;
}
// 消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了------->第" + container.pop().id + "只鸡");
}
}
}

//产品
class Chicken {
int id;

public Chicken(int id) {
this.id = id;
}
}

//缓冲区
class SynContainer {
// 需要一个容器大小
Chicken[] chickens = new Chicken[10];
// 容器计数器
int count = 0;

// 生产者放入产品
public synchronized void push(Chicken chicken) {
// 如果容器满了,就需要等待消费者消费
while (count == chickens.length-1) {
// 通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,我们就需要丢入产品
chickens[count] = chicken;
count++;
this.notifyAll();
// 可以通知消费者消费了

}

// 消费者消费产品
public synchronized Chicken pop() {
// 判断能否消费
while (count == 0) {
// 等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 如果可以消费
count--;
Chicken chicken = chickens[count];
this.notifyAll();
// 吃完了,通知生产者生产
return chicken;
}
}

并发协作模型”生产者/消费者模式” –> 信号灯法

12、线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中.可以避免频繁创建销毁,实现重复利用.

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API: ExecutorServiceExecutors

ExecutorService:真正的线程池接口,常见子类 ThreadPoolExecutor

  • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  • Future submit(Callable task):执行任务,有返回值,一般用来执行 Callable
  • void shutdown():关闭连接池

Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池

  • Copyrights © 2022-2023 hqz

请我喝杯咖啡吧~

支付宝
微信