多线程

导航

  • 多线程
    • 1 线程、进程、多线程概述
    • 2 创建线程 (重点)
      • 2.1 继承Thread类(Thread类也实现了Runnable接口)
      • 2.2 实现Runnable接口(无消息返回)
      • 2.3 实现callable接口(有消息返回)
      • 2.4 线程如何停止?
    • 3 线程的一些方法
      • 3.1 线程休眠__sleep
        • 3.1.1 利用线程休眠来模拟网络延时,放大问题
        • 3.1.2 利用sleep方法来模拟倒计时
      • 3.2 线程礼让__yield
      • 3.3 线程强制执行__join
      • 3.4 观测线程状态
        • 3.4.1 线程的几种状态
      • 3.5 线程优先级(priority)
        • 改变、获取优先级
      • 3.6 线程守护
    • 4 线程同步 (重点)
      • 4.1 概述
      • 4.2 并发
      • 4.3 同步方法和同步代码块
        • 4.3.1 同步方法
        • 4.3.2 同步方法解决买票问题
        • 4.3.3 同步代码块
        • 4.3.4 同步代码块解决取款问题
      • 4.4 死锁
        • 4.4.1 死锁代码
        • 4.4.2 解决死锁
      • 4.5 Lock(锁)
        • 4.5.1 并发案例
        • 4.5.2 Lock解决问题
      • 4.6 synchronized 和 Lock 比较
    • 5 线程通信
      • 5.1 生产者消费者模式
      • 5.2 管程法
      • 5.3 信号灯法
    • 6 线程池
      • 6.1 线程池创建
      • 6.2 ThreadPoolExecutor
        • 6.2.1 ThreadPoolExecutor七个参数
        • 6.2.2 临时线程什么时候创建?什么时候会开始拒绝任务?
        • 6.2.3 创建线程池对象实例
      • 6.3 Executors
      • 6.4 阿里巴巴Java开发手册建议

1 线程、进程、多线程概述

  1. 线程:是操作系统中能够进行运算调度的最小单位,包含在进程中,是进程中的实际运作单位。
  2. 进程:是程序执行一次的过程。
  3. 多线程:一个进程可以并发出多个线程,这就是多线程。

2 创建线程 (重点)

2.1 继承Thread类(Thread类也实现了Runnable接口)

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("开启线程:" + Thread.currentThread().getName());}public static void main(String[] args) {new MyThread().start();new MyThread().start();new MyThread().start();}
}

第一次输出:

开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-0

第二次输出:

开启线程:Thread-1
开启线程:Thread-0
开启线程:Thread-2

可以发现 每次输出,每次输出,顺序不同,说明他们是同时在执行(如果是单核单cpu,实际上不是同时)。

2.2 实现Runnable接口(无消息返回)

public class MyThread implements Runnable {@Overridepublic void run() {System.out.println("开启线程:" + Thread.currentThread().getName());}public static void main(String[] args) {new Thread(new MyThread()).start();new Thread(new MyThread()).start();new Thread(new MyThread()).start();}
}

第一次输出:

开启线程:Thread-0
开启线程:Thread-1
开启线程:Thread-2

第二次输出:

开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-0

结论同上,开启线程基本上是同时进行的(如果是单核单cpu,实际上不是同时)。

2.3 实现callable接口(有消息返回)

实现callable接口创建线程要用到FutureTask类。

public class MyThread implements Callable<String> {@Overridepublic String call() {System.out.println("开启线程:" + Thread.currentThread().getName());return Thread.currentThread().getName();}public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<String> f1 = new FutureTask<>(new MyThread());FutureTask<String> f2 = new FutureTask<>(new MyThread());FutureTask<String> f3 = new FutureTask<>(new MyThread());new Thread(f1).start();new Thread(f2).start();new Thread(f3).start();System.out.println("f1的返回值是:" + f1.get());System.out.println("f2的返回值是:" + f2.get());System.out.println("f3的返回值是:" + f3.get());}
}

第一次输出:

开启线程:Thread-1
开启线程:Thread-0
开启线程:Thread-2
f1的返回值是:Thread-0
f2的返回值是:Thread-1
f3的返回值是:Thread-2

第二次输出:

开启线程:Thread-0
开启线程:Thread-2
f1的返回值是:Thread-0
开启线程:Thread-1
f2的返回值是:Thread-1
f3的返回值是:Thread-2

结论同上,但实现callable接口可以返回消息。

2.4 线程如何停止?

虽然jdk提供了stop方法和destroy方法,但是更推荐的是,用外部标志位来告诉程序是否继续运行。

public class Test{public static void main(String[] args) throws InterruptedException {NeedStop ns = new NeedStop();new Thread(ns).start();Thread.sleep(10);ns.flag = false;}
}class NeedStop implements Runnable {boolean flag = true;@Overridepublic void run() {int i = 0;while (flag) {System.out.println("运行了 " + i++ + " 次");}}
}

当 flag 变为 false时,线程便终止了,这是很安全的做法。

3 线程的一些方法

3.1 线程休眠__sleep

例:如买票的系统,假如没有处理并发问题,就可能会存在多个人买到同一张票,或者余票为负等情况。

3.1.1 利用线程休眠来模拟网络延时,放大问题

public class Account {String cardId;int RMB;public Account(String cardId, int RMB) {this.cardId = cardId;this.RMB = RMB;}public static void main(String[] args) throws InterruptedException {Account ac = new Account("123456", 100000);new Thread(new DrawMoney(ac, 100000), "小明").start();new Thread(new DrawMoney(ac, 100000),"小红").start();Thread.sleep(1000);System.out.println("剩余 " + ac.RMB + " 元");}
}class DrawMoney implements Runnable {Account ac;int money;public DrawMoney(Account ac, int money) {this.ac = ac;this.money = money;}public void drawMoney(Account ac) throws InterruptedException {if (money <= ac.RMB) {System.out.println(Thread.currentThread().getName() + "取走了 " + money + " 元");Thread.sleep(10);ac.RMB -= money;}else {System.out.println("余额不足!");}}@Overridepublic void run() {try {drawMoney(ac);} catch (InterruptedException e) {e.printStackTrace();}}
}

多次输出发现:

可能会出现小明小红同时取到钱,余额为负数的情况。

当小红进入if判断时,ac.RMB还没来得及扣除。

3.1.2 利用sleep方法来模拟倒计时

package com.stop;public class SleepTest {public static void main(String[] args) throws InterruptedException {System.out.println("倒计时:");for (int i = 10; i > 0; i--) {Thread.sleep(1000);System.out.println(i);}}
}

一秒输出一个数。

3.2 线程礼让__yield

  1. 让当前线程暂停,但不阻塞
  2. 将线程从运行=>就绪
  3. 让cpu重新调度,礼让不一定成功

相当于大家重回同一起跑线,重新争夺资源

public class YieldTest implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "正在执行");Thread.yield();System.out.println(Thread.currentThread().getName() + "=>结束");}public static void main(String[] args) {new Thread(new YieldTest(), "a").start();new Thread(new YieldTest(), "b").start();}
}

如果礼让成功,则a(或b)开始和结束不会连续出现。

3.3 线程强制执行__join

执行join会让其他线程阻塞,待当前线程结束后,其他线程才能执行,如同霸道的插队。

public class JoinTest implements Runnable{@Overridepublic void run() {for (int i = 10; i > 0; i--) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("vip来了" + i);}}public static void main(String[] args) throws InterruptedException {Thread test = new Thread(new JoinTest(), "vip");test.start();for (int i = 0; i < 500; i++) {if (i == 250) {test.join();}System.out.println("main" + i);}}
}

当main中i == 250时,就会被阻塞然后让咱们的vip线程执行完毕,再继续执行main线程。

3.4 观测线程状态

3.4.1 线程的几种状态

  1. NEW(新建)

    线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

  2. RUNNABLE(就绪)

    线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

  • 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
  1. BLOCKED(阻塞于锁)

    同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

  2. WAITING(等待)

    进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

    等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。

  3. TIMED WAITING(超时等待)

    其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  4. TERMINATED(终止)

    终止线程的线程状态。 线程已完成执行。

public class StatusTest implements Runnable{@Overridepublic void run() {try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("//");}public static void main(String[] args) throws InterruptedException {Thread test = new Thread(new StatusTest());System.out.println(test.getState());test.start();System.out.println(test.getState());Thread.State state = test.getState();while (state != Thread.State.TERMINATED) {Thread.sleep(1000);state = test.getState();System.out.println(state);}test.start();  // 线程死亡后不可以再次启动}
}

线程从 NEW= >RUNNABLE=> TIME_WAITTING阻塞了10秒=>TERMINNATED

最后再次启动线程时 将报错。

3.5 线程优先级(priority)

线程调度按照优先级决定应该先调谁,所有就绪状态的线程都会被监控。具体调谁具体还得看CPU心情。

改变、获取优先级

改变:setPriority(int xxx)

获取:getPriority()

优先级最高为10

最低为1

默认优先级是5

public class PriorityTest implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "  " + Thread.currentThread().getPriority());}public static void main(String[] args) {Thread t1 = new Thread(new PriorityTest());Thread t2 = new Thread(new PriorityTest());Thread t3 = new Thread(new PriorityTest());Thread t4 = new Thread(new PriorityTest());Thread t5 = new Thread(new PriorityTest());t1.setPriority(10);t2.setPriority(8);t4.setPriority(3);t5.setPriority(1);t1.start();t2.start();t3.start();t4.start();t5.start();}}

3.6 线程守护

线程分为用户线程和守护(daemon)线程,JVM只确保用户线程执行完毕而不用等待守护线程执行完毕

守护线程如:监控内存、垃圾回收等

Thread种的**setDaemon(boolean on)**方法可以设置线程是否为守护线程,默认为false,用户线程。

public class DaemonTest extends Thread{@Overridepublic void run() {while (true) {System.out.println("====守护====");}}public static void main(String[] args) {Thread daemon = new DaemonTest();daemon.setDaemon(true);daemon.start();new NormalThread().start();}
}class NormalThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("====用户====");}}
}

这段代码将会在用户主线程结束时,守护线程结束,不会死循环。

4 线程同步 (重点)

4.1 概述

在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。

“同”字从字面上容易理解为一起动作

其实不是,“同”字应是指协同、协助、互相配合。

如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

4.2 并发

在同一时刻,有多个线程同时访问 某一个(一些)资源,带来数据的不安全性 、不稳定性、不确定性。

同步就是为了解决并发问题。

下面这段代码就存在着并发问题:

public class Account {String cardId;int RMB;public Account(String cardId, int RMB) {this.cardId = cardId;this.RMB = RMB;}public static void main(String[] args) throws InterruptedException {Account ac = new Account("123456", 100000);new Thread(new DrawMoney(ac, 100000), "小明").start();new Thread(new DrawMoney(ac, 100000),"小红").start();Thread.sleep(1000);System.out.println("剩余 " + ac.RMB + " 元");}
}class DrawMoney implements Runnable {Account ac;int money;public DrawMoney(Account ac, int money) {this.ac = ac;this.money = money;}public void drawMoney(Account ac) throws InterruptedException {if (money <= ac.RMB) {System.out.println(Thread.currentThread().getName() + "取走了 " + money + " 元");Thread.sleep(10);ac.RMB -= money;}else {System.out.println("余额不足!");}}@Overridepublic void run() {try {drawMoney(ac);} catch (InterruptedException e) {e.printStackTrace();}}
}

多次输出发现:
可能会出现小明小红同时取到钱,余额为负数的情况。

当小红进入if判断时,ac.RMB还没来得及扣除。

4.3 同步方法和同步代码块

解决并发问题的 synchronized

synchronized 可以给代码加锁,同一时刻只有一个线程可以执行被锁定的代码

synchronized又分为同步方法和同步代码块

4.3.1 同步方法

若给方法加上锁

public synchronized void methodName() {}

每次只能有一个线程执行这个方法

4.3.2 同步方法解决买票问题

现有以下代码:

public class Tickets implements Runnable {private  int ticketNum = 10;boolean flag =true;public void buy() throws InterruptedException {if (ticketNum <= 0) {flag = false;return;}System.out.println(Thread.currentThread().getName() + " 拿到第 " + ticketNum + "张 票");Thread.sleep(100);ticketNum--;}@Overridepublic void run() {while (flag) {try {buy();} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {Tickets station = new Tickets();new Thread(station, "小明").start();new Thread(station, "小红").start();new Thread(station, "小张").start();}
}

会出现多人拿到同一张票或者拿到不存在的票的安全问题,此时,同步方法可以解决这个问题。

只需要给buy()方法加上锁,那么同一时间,只能有一个线程执行buy()。因为小明、小红、小张,都是同一个Tickets对象,所以this是相同的,同一时间只能有一个线程进入这个方法。

4.3.3 同步代码块

synchronized(obj) {

​ code……

}

obj填锁对象。谁先拿到obj谁就先执行,其余线程只能排队。

4.3.4 同步代码块解决取款问题

之前取款的例子

public class Account {String cardId;int RMB;public Account(String cardId, int RMB) {this.cardId = cardId;this.RMB = RMB;}public static void main(String[] args) throws InterruptedException {Account ac = new Account("123456", 100000);new Thread(new DrawMoney(ac, 100000), "小明").start();new Thread(new DrawMoney(ac, 100000),"小红").start();Thread.sleep(1000);System.out.println("剩余 " + ac.RMB + " 元");}
}class DrawMoney implements Runnable {Account ac;int money;public DrawMoney(Account ac, int money) {this.ac = ac;this.money = money;}public void drawMoney(Account ac) throws InterruptedException {if (money <= ac.RMB) {System.out.println(Thread.currentThread().getName() + "取走了 " + money + " 元");Thread.sleep(10);ac.RMB -= money;}else {System.out.println("余额不足!");}}@Overridepublic void run() {try {drawMoney(ac);} catch (InterruptedException e) {e.printStackTrace();}}
}

此时再用同步方法并不能解决并发问题,因为小明和小红不是同一个this(不同的DrawMoney实例),所以可以同时执行

同步代码块 可以解决这个问题,用synchronized把关键的代码加上锁(很显然是29~36行)

public void drawMoney(Account ac) throws InterruptedException {synchronized (ac) {    if (money <= ac.RMB) {System.out.println(Thread.currentThread().getName() + "取走了 " + money + " 元");Thread.sleep(10);ac.RMB -= money;}else {System.out.println("余额不足!");}}
}

那么在同一时间内,只能有一个线程能够对ac账户进行操作。

4.4 死锁

当两个线程拿着对方需要的锁而不释放时,因为双方都拿不到锁,所以就成了死锁,线程就阻塞在那里。

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。产生死锁的原因,主要包括:

  • 系统资源不足;
  • 程序执行的顺序有问题;
  • 资源分配不当等。

如果系统资源充足,进程的资源请求都能够得到满足,那么死锁出现的可能性就很低;否则,

就会因争夺有限的资源而陷入死锁。其次,程序执行的顺序与速度不同,也可能产生死锁。产生死锁的四个必要条件:

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

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

4.4.1 死锁代码

public class DeadLock implements Runnable {public static final String lock1 = "Lock_1";public static final String lock2 = "Lock_2";boolean flag;public DeadLock(boolean flag) {this.flag = flag;}public void dead() throws InterruptedException {if (flag) {synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " 拿到了lock1");Thread.sleep(1000);synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " 拿到了lock2");}}} else {synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " 拿到了lock2");Thread.sleep(1000);synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " 拿到了lock1");}}}}@Overridepublic void run() {try {dead();} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {new Thread(new DeadLock(true)).start();new Thread(new DeadLock(false)).start();}
}

一个线程在 第 12 行拿到了lock1,在他休眠时另一个线程在 20 行拿到了lock2,拿到lock1的线程休眠结束后,需要拿lock2的锁,可是lock2在另一个线程手里,所以lock1就等待lock2释放,而lock2那边也是同理,在等待lock1释放,互相阻塞在那里,这就是死锁。

解决方法也很简单,lock1 用完 就释放掉,lock2同理

4.4.2 解决死锁

于是我们不再继续嵌套书写synchronized

public class Solute implements Runnable{public static final String lock1 = "Lock_1";public static final String lock2 = "Lock_2";boolean flag;public Solute(boolean flag) {this.flag = flag;}public void dead() throws InterruptedException {if (flag) {synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " 拿到了lock1");Thread.sleep(1000);}synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " 拿到了lock2");}} else {synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " 拿到了lock2");Thread.sleep(1000);}synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " 拿到了lock1");}}}@Overridepublic void run() {try {dead();} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {new Thread(new Solute(true)).start();new Thread(new Solute(false)).start();}
}

lock1唤醒后,释放lock1,lock2同理。

4.5 Lock(锁)

作用和synchronized类似,都是用来解决并发问题。Lock是一个接口,而synchronized是一个关键字。Lock是接口意味着它有许多方法,在复杂的情况下比synchronized要方便。

4.5.1 并发案例

继续用经典的买票案例:

package com.locklock;public class TestLock implements Runnable {private int ticketsNum;private boolean flag;public TestLock(int ticketsNum, boolean flag) {this.ticketsNum = ticketsNum;this.flag = flag;}public void buyTickets() throws InterruptedException {if (ticketsNum > 0) {Thread.sleep(100);ticketsNum--;System.out.println(Thread.currentThread().getName() + " 来买了第 " + ticketsNum + " 张票");} else {flag = false;System.out.println("已售罄!");}}@Overridepublic void run() {while (flag) {try {buyTickets();} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {TestLock testLock = new TestLock(10,true);new Thread(testLock, "xm").start();new Thread(testLock, "xh").start();new Thread(testLock, "xz").start();}
}

4.5.2 Lock解决问题

已经知道,刚刚这段代码有问题,会出现多个人拿到同一张票,或者拿到无效票(<=0)的情况。

按照之前的方法,只需把buyTichets()方法变成同步方法即可。

现在我们不用synchronized关键字,用Lock里面的方法。

package com.locklock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class TestLock implements Runnable {private int ticketsNum;private boolean flag;Lock lock = new ReentrantLock();public TestLock(int ticketsNum, boolean flag) {this.ticketsNum = ticketsNum;this.flag = flag;}public void buyTickets() throws InterruptedException {try {lock.lock();if (ticketsNum > 0) {Thread.sleep(100);ticketsNum--;System.out.println(Thread.currentThread().getName() + " 来买了第 " + ticketsNum + " 张票");} else {flag = false;System.out.println("已售罄!");}} finally {lock.unlock();}}@Overridepublic void run() {while (flag) {try {buyTickets();} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {TestLock testLock = new TestLock(10,true);new Thread(testLock, "xm").start();new Thread(testLock, "xh").start();new Thread(testLock, "xz").start();}
}

Lock接口创建ReentrantLock实例(多态),把关键的代码块用lock()方法和unlock()方法包裹起来,和synchronized (this) {}类似。

最好用try环绕代码、finally环绕unlock(),这样即使上面代码有异常,也会释放锁。

4.6 synchronized 和 Lock 比较

synchronized Lock
类型 关键字 接口
范围 锁方法和代码块 只能锁代码块
形式 隐式锁,作用于外自动释放 显示锁,手动释放
性能比较 底层指令来控制锁,少量同步 性能更好,大量同步

使用优先级:

  • Lock锁 > 同步代码块 > 同步方法

5 线程通信

线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺

5.1 生产者消费者模式

生产者消费者模式并不是 GOF 提出的 23 种设计模式之一,23 种设计模式都是建立在面向对象的基础之上的,但其实面向过程的编程中也有很多高效的编程模式,生产者消费者模式便是其中之一。

在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。单单抽象出生产者和消费者,还够不上是生产者-消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

为了不至于太抽象,我们举一个寄信的例子(虽说这年头寄信已经不时兴,但这个例子还是比较贴切的)。假设你要寄一封平信,大致过程如下:

1、你把信写好——相当于生产者制造数据

2、你把信放入邮筒——相当于生产者把数据放入缓冲区

3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区

4、邮递员把信拿去邮局做相应的处理——相当于消费者处理数据

生产者:负责生产数据的模块。

消费者:负责处理数据的模块。

缓冲区:消费者要通过缓冲区才能使用生产者生产的数据。

5.2 管程法

通过变量的值控制

public class  ProducerCustorm {public static void main(String[] args) {SynContainer container = new SynContainer();new Producer(container).start();new Consumer(container).start();}
}// 生产者
class Producer extends Thread {final SynContainer container;public Producer(SynContainer container) {this.container = container;}@Overridepublic void run() {for (int i = 0; i < 50; i++) {container.push(new Chicken(container.id));}}
}// 消费者
class Consumer extends Thread {SynContainer container;public Consumer(SynContainer container) {this.container = container;}@Overridepublic void run() {for (int i = 0; i <50; i++) {container.pop();}}
}// 产品
class Chicken {int id; //产品编号public Chicken(int id) {this.id = id;}
}// 缓冲区
class  SynContainer {int id = 1; // 产品编号// 需要一个容器Chicken[] chickens = new Chicken[10];// 计数器int count = 0;// 生产者放入产品public synchronized void push(Chicken chicken) {// 如果容器 满了 则消费者消费if (count == chickens.length) {// 生产者等待消费者消费try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 被消费者通知生产if (count < chickens.length) {chickens[count++] = chicken;id++;System.out.println(Thread.currentThread().getName() +  ": 生产了" + chicken.id + "只鸡");// 通知消费者消费this.notifyAll();}}// 消费者消费产品public synchronized void pop() {if (count == 0) {// 等待生产者生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 如果可以消费if ( count != 0 ) {Chicken chicken = chickens[--count];System.out.println("消费了第" + chicken.id + "只鸡");// 通知生产者生产this.notifyAll();}}
}

用一个数值和别的线程传递信息,告诉他们是否可以继续就绪。

5.3 信号灯法

通过标志位控制

public class ProducerAndConsumer {public static void main(String[] args) {Buffered buffered = new Buffered();new Producers(buffered, "厨师").start();new Consumer(buffered, "顾客").start();}}// 生产者
class Producers extends Thread {Buffered buffered;public Producers(Buffered buffered, String name) {super(name);this.buffered = buffered;}@Overridepublic void run() {for (int i = 0; i < 50; i++) {buffered.push(new Bread(i));}System.out.println(Thread.currentThread().getName() +  "工作了一天,该休息了!");}
}// 消费者
class Consumer extends Thread {Buffered buffered;public Consumer(Buffered buffered, String name) {super(name);this.buffered = buffered;}@Overridepublic void run() {for (int i = 0; i < 50; i++) {buffered.pop();}}
}// 缓冲区
class Buffered {// 产品Bread bread;// 信号灯boolean flag = false;//生产者生产public synchronized void push(Bread bread) {if (flag) {System.out.println(Thread.currentThread().getName() + ": 已经有面包了,休息会儿!");try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}if (!flag) {System.out.println(Thread.currentThread().getName() + ": 没东西吃了,赶快做……");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.bread = bread;System.out.println("厨师做了:" + (bread.getId()+1) + " 个面包");System.out.println("现在有东西吃了!");this.flag = !this.flag;this.notifyAll();}}//消费者消费public synchronized void pop() {if (!flag) {System.out.println(Thread.currentThread().getName() + ": 面包呢?");try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}if (flag) {System.out.println("销售了:" + (bread.getId()+1) + " 个面包");this.flag = !this.flag;System.out.println(Thread.currentThread().getName() + ": 面包吃完了!");this.notifyAll();}}}// 产品
class Bread {private int id;public Bread(int id) {this.id = id;}public int getId() {return id;}public void setId(int id) {this.id = id;}
}

就是设置一个标志位,然后告诉其他线程是否可以工作,协同处理一个数据。

6 线程池

如果有请求就新建一个线程,那么会创建很多线程,严重影响性能,线程池里面的线程可以复用,提高了性能。

6.1 线程池创建

ExecutorService 接口代表线程池

创建方式一般有两种:

  1. 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
  2. 使用Executors调用方法返回不同特点的线程池对象

6.2 ThreadPoolExecutor

6.2.1 ThreadPoolExecutor七个参数

ThreadPoolExecutor(

int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

创建一个新 ThreadPoolExecutor给定的初始参数。

一共七个参数

  1. corePoolSize:指定线程池的数量(核心线程)。不能小于0。
  2. maximumPoolSize:指定线程池可支持的最大线程数。最大数量>=核心线程数量。
  3. keepAliveTime指定临时线程的最大存活时间。不能小于0。
  4. unit:指定存活时间单位(秒、分、时、天)。
  5. workQueue:指定任务队列。不能为null。
  6. threadFactor:指定用哪线程工厂创建线程。不能为nul。
  7. handler:指定线程忙、任务满的时候,新任务来了怎么办。不能为null。

6.2.2 临时线程什么时候创建?什么时候会开始拒绝任务?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
  • 核心线程和临时线程都在忙,任务队列也满了,新的任务来的时候才会开始拒绝。

6.2.3 创建线程池对象实例

知道了一些原理过后,我们开始尝试创建对象

ExecutorService pool = new ThreadPoolExecutor(3,10,5,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

拒绝策略:

策略
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出异常(默认)
ThreadPoolExecutor.DiscardPolicy 丢弃任务,不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 不进入线程池、由主线程调用任务的run方法

使用线程池:

//对于实现了Runnable接口的类
pool.execute(Runnable target);
//与下面这个效果相同
new Thread(Runnable target).start();
//对于实现了Callable接口的类
Future<T> r1 = pool.submit(Callable target);
//获取Call方法的值
r1.get();

6.3 Executors

一个工具类,提供了简单创建线程池的方法。

查看帮助文档,有很多静态方法可以调用。

最常用的是

public static ExecutorService newFixedThreadPool(int nThreads)

6.4 阿里巴巴Java开发手册建议

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

  • 说明:Executors 返回的线程池对象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
    ,
    new ArrayBlockingQueue<>(5),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy());

Java多线程(含生产者消费者模式详解)相关推荐

  1. java中的生产者消费者模式详解

    方式 一: Synchronized方式 注:此种方式会造成资源的浪费: 利用锁的notifyAll()方法会将所有的线程都唤醒,会造成资源的浪费 class Resource {private St ...

  2. 单线程下的生产者--消费者模式详解,wait和sleep的区别

    1. 单线程下的生产者--消费者模式 1.1 该模式下,一个线程生产数据,另一个线程处理数据.当数据还没被处理,那么生产数据的线程进入等待状态:如果数据还没生产,那么处理数据的线程进入等待状态,代码及 ...

  3. 生产者消费者模式详解(转载)

    ★简介 在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等).产生数据的模块,就形象地称为生产者:而处理 ...

  4. 【java并发系列】java多线程实现生产者消费者模式

    大家好,我是walker 一个从文科自学转行的程序员~ 爱好编程,偶尔写写编程文章和生活 欢迎关注公众号[I am Walker],回复"电子书",就可以获得200多本编程相关电子 ...

  5. C++11 并发指南九(综合运用: C++11 多线程下生产者消费者模型详解)

    前面八章介绍了 C++11 并发编程的基础(抱歉哈,第五章-第八章还在草稿中),本文将综合运用 C++11 中的新的基础设施(主要是多线程.锁.条件变量)来阐述一个经典问题--生产者消费者模型,并给出 ...

  6. java多线程中的join方法详解

    java多线程中的join方法详解 方法Join是干啥用的? 简单回答,同步,如何同步? 怎么实现的? 下面将逐个回答. 自从接触Java多线程,一直对Join理解不了.JDK是这样说的:join p ...

  7. Java线程实现生产者—消费者模式

    在这里插入代码片# Java 线程实现生产者-消费者模式 ##思路:实现类似消费者生产者线程之间通讯的功能,每创建一个工人,就让这个工人干活,干一段时间,工人自动消失,然后又去创建一个工人干活: 代码 ...

  8. java consumed_Java设计模式—生产者消费者模式(阻塞队列实现)

    生产者消费者模式是并发.多线程编程中经典的 真实世界中的生产者消费者模式 生产者和消费者模式在生活当中随处可见,它描述的是协调与协作的关系.比如一个人正在准备食物(生产者),而另一个人正在吃(消费者) ...

  9. Java线程实现生产者消费者模式

    1 什么是生产者消费者模式 想一个现实生活中的例子,啤酒商---超市---消费者也就是我们,啤酒商生产了啤酒,然后将啤酒销售给了超市,我们消费之又会到超市将啤酒买回来自己喝,那么啤酒商和消费者之间是什 ...

最新文章

  1. Atitit 知识图谱的数据来源
  2. python二叉树遍历算法_分享python实现的二叉树定义与遍历
  3. C语言按两个字节读写二进制文件,C语言 读写二进制文件(示例代码)
  4. 【另类见解】秒杀并非高不可攀
  5. 微信小程序组件间通信(二)
  6. python实现rsa加密解密代码_使用python实现rsa算法代码
  7. 如何下载安装Python
  8. 【Linux应用】udhcpc命令获取到ip后,但是没有生效(没有设置进去)
  9. pads图标logo库制作方法
  10. esp8266连接阿里云 (课程设计 附源码)
  11. sqlServer2014用sql server身份认证登录
  12. android平板电脑卡槽在哪,外观|增加SIM卡槽_酷比魔方 IWORK8_平板电脑评测-中关村在线...
  13. 【css】使用 canvas 画一个圆、贝塞尔曲线画对话气泡
  14. 倾斜摄影测量(无人机影像)的三维建模和DSM,DOM的生成(挖坑)
  15. HTML+CSS大作业——二次元漫画(8页) 漫画网页设计制作 简单静态HTML网页作品 我的漫画网页作业成品 学生漫画网站模板
  16. 比ping更强大的fping
  17. 字节跳动 面试 复盘 回顾 2021 过客局
  18. Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim
  19. 存储过程基本语法结构
  20. 如何在win7系统中屏蔽2345这个流氓

热门文章

  1. suricata源码之-流表管理
  2. Multisim基础 网络名称 整理混乱的电路图
  3. InputStream 简介
  4. PFC-FLAC3D Coupling Examples
  5. hdu1242 Rscue hdu 1253胜利大逃亡
  6. Yii2 Codeception初探之Specify方法
  7. labview霍夫曼树_霍夫曼树的应用
  8. 花狐狸股市操作系统说明书
  9. Doris Compaction 流程
  10. 【01】初识YGOPro