Java多线程(含生产者消费者模式详解)
多线程
导航
- 多线程
- 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 线程、进程、多线程概述
- 线程:是操作系统中能够进行运算调度的最小单位,包含在进程中,是进程中的实际运作单位。
- 进程:是程序执行一次的过程。
- 多线程:一个进程可以并发出多个线程,这就是多线程。
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
- 让当前线程暂停,但不阻塞
- 将线程从运行=>就绪
- 让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 线程的几种状态
NEW(新建)
线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
RUNNABLE(就绪)
线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
- 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
BLOCKED(阻塞于锁)
同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
WAITING(等待)
进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
TIMED WAITING(超时等待)
其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
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 接口代表线程池
创建方式一般有两种:
- 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
- 使用Executors调用方法返回不同特点的线程池对象
6.2 ThreadPoolExecutor
6.2.1 ThreadPoolExecutor七个参数
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
创建一个新
ThreadPoolExecutor
给定的初始参数。
一共七个参数
- corePoolSize:指定线程池的数量(核心线程)。不能小于0。
- maximumPoolSize:指定线程池可支持的最大线程数。最大数量>=核心线程数量。
- keepAliveTime指定临时线程的最大存活时间。不能小于0。
- unit:指定存活时间单位(秒、分、时、天)。
- workQueue:指定任务队列。不能为null。
- threadFactor:指定用哪线程工厂创建线程。不能为nul。
- 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多线程(含生产者消费者模式详解)相关推荐
- java中的生产者消费者模式详解
方式 一: Synchronized方式 注:此种方式会造成资源的浪费: 利用锁的notifyAll()方法会将所有的线程都唤醒,会造成资源的浪费 class Resource {private St ...
- 单线程下的生产者--消费者模式详解,wait和sleep的区别
1. 单线程下的生产者--消费者模式 1.1 该模式下,一个线程生产数据,另一个线程处理数据.当数据还没被处理,那么生产数据的线程进入等待状态:如果数据还没生产,那么处理数据的线程进入等待状态,代码及 ...
- 生产者消费者模式详解(转载)
★简介 在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等).产生数据的模块,就形象地称为生产者:而处理 ...
- 【java并发系列】java多线程实现生产者消费者模式
大家好,我是walker 一个从文科自学转行的程序员~ 爱好编程,偶尔写写编程文章和生活 欢迎关注公众号[I am Walker],回复"电子书",就可以获得200多本编程相关电子 ...
- C++11 并发指南九(综合运用: C++11 多线程下生产者消费者模型详解)
前面八章介绍了 C++11 并发编程的基础(抱歉哈,第五章-第八章还在草稿中),本文将综合运用 C++11 中的新的基础设施(主要是多线程.锁.条件变量)来阐述一个经典问题--生产者消费者模型,并给出 ...
- java多线程中的join方法详解
java多线程中的join方法详解 方法Join是干啥用的? 简单回答,同步,如何同步? 怎么实现的? 下面将逐个回答. 自从接触Java多线程,一直对Join理解不了.JDK是这样说的:join p ...
- Java线程实现生产者—消费者模式
在这里插入代码片# Java 线程实现生产者-消费者模式 ##思路:实现类似消费者生产者线程之间通讯的功能,每创建一个工人,就让这个工人干活,干一段时间,工人自动消失,然后又去创建一个工人干活: 代码 ...
- java consumed_Java设计模式—生产者消费者模式(阻塞队列实现)
生产者消费者模式是并发.多线程编程中经典的 真实世界中的生产者消费者模式 生产者和消费者模式在生活当中随处可见,它描述的是协调与协作的关系.比如一个人正在准备食物(生产者),而另一个人正在吃(消费者) ...
- Java线程实现生产者消费者模式
1 什么是生产者消费者模式 想一个现实生活中的例子,啤酒商---超市---消费者也就是我们,啤酒商生产了啤酒,然后将啤酒销售给了超市,我们消费之又会到超市将啤酒买回来自己喝,那么啤酒商和消费者之间是什 ...
最新文章
- Atitit 知识图谱的数据来源
- python二叉树遍历算法_分享python实现的二叉树定义与遍历
- C语言按两个字节读写二进制文件,C语言 读写二进制文件(示例代码)
- 【另类见解】秒杀并非高不可攀
- 微信小程序组件间通信(二)
- python实现rsa加密解密代码_使用python实现rsa算法代码
- 如何下载安装Python
- 【Linux应用】udhcpc命令获取到ip后,但是没有生效(没有设置进去)
- pads图标logo库制作方法
- esp8266连接阿里云 (课程设计 附源码)
- sqlServer2014用sql server身份认证登录
- android平板电脑卡槽在哪,外观|增加SIM卡槽_酷比魔方 IWORK8_平板电脑评测-中关村在线...
- 【css】使用 canvas 画一个圆、贝塞尔曲线画对话气泡
- 倾斜摄影测量(无人机影像)的三维建模和DSM,DOM的生成(挖坑)
- HTML+CSS大作业——二次元漫画(8页) 漫画网页设计制作 简单静态HTML网页作品 我的漫画网页作业成品 学生漫画网站模板
- 比ping更强大的fping
- 字节跳动 面试 复盘 回顾 2021 过客局
- Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim
- 存储过程基本语法结构
- 如何在win7系统中屏蔽2345这个流氓