多线程
狂神Java基础之多线程学习笔记
多线程概念
线程简介
Process(进程)与Thread(线程)
程序: 程序时指令和数据的有序集合, 本身没有任何运行的含义, 是一个静态的概念;
进程: 执行程序的一次执行过程, 是一个动态的概念, 是系统资源分配的单位;
线程: 通常一个进程中可以包含若干个线程, 至少有一个线程, 线程是CPU调度和执行的单位.
注意: 很多多线程是模拟出来的, 真正的多线程是指有多个CPU, 即多核, 如服务器. 如果是模拟出来的多线程, 即在一个CPU的情况下, 在同一个时间点, CPU只能执行一个代码, 因为切换的很快, 所以就有同时执行的错觉.
多线程核心概念
♦ 线程就是独立的执行路径;
♦ 在程序运行时, 即使没有自己创建线程, 后台也会有多个线程, 如主线程, gc线程;
♦ main()称之为主线程, 为系统的入口, 用于执行整个程序;
♦ 在一个进程中, 如果开辟了多个线程, 线程的运行由调度器安排调度, 调度器是与操作系统紧密相关的, 先后顺序是不能人为干预的;
♦ 对同一份资源操作时, 会存在资源抢夺问题, 需要加入并发控制;
♦ 线程会带来额外的开销, 如CPU调度时间, 并发控制开销;
♦ 每个线程在自己的工作内存交互, 内存控制不当会造成数据不一致.
线程创建
线程有三种创建方式:
- 继承Thread类
- 实现Runnable接口(最优)
- 实现Callable接口
继承Thread类创建线程
♦ 自定义线程类继承Thread类;
♦ 重写run()方法, 编写线程执行体;
♦ 创建线程对象, 调用start()方法启动线程.
/**
* 总结: 调用start()方法才会开启子线程
* 注意, 线程开启不一定立即执行, 由CPU进行调度安排
*/
//创建线程方式一: 继承Thread类, 重写run()方法, 调用start开启线程
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 1; i <= 500; i++) {
System.out.println("测试线程运行第" + i + "下");
}
}
public static void main(String[] args) {
//main线程, 主线程
//创建一个线程对象
TestThread1 test = new TestThread1();
//调用start方法开启线程
/*
* 调用start(), 子线程来执行run()方法, 即主线程与子线程"并行"交替执行
*/
test.start();
//调用run方法开启线程
/*
调用run(), 主线程来执行run()方法, 执行完后再执行后面的语句, 即只有主线程一条执行路径
*/
//test.run();
for (int i = 1; i <= 1000; i++) {
System.out.println("主线程运行第" + i + "下");
}
}
}
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread, 实现多线程同步下载图片
public class TestThread2 extends Thread {
private String url; //网络图片地址
private String name; //保存的文件名
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader wd = new WebDownloader();
wd.downloader(url, name);
System.out.println("下载图片的文件名为:" + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://img0.baidu.com/it/u=3072076516,914193103&fm=253&fmt=auto&app=138&f=JPEG?w=1080&h=469", "C:/Users/imkid/Desktop/pic1.jpg");
TestThread2 t2 = new TestThread2("https://img2.baidu.com/it/u=2896585310,1938945066&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=763", "C:/Users/imkid/Desktop/pic2.jpg");
TestThread2 t3 = new TestThread2("https://img1.baidu.com/it/u=4211852519,111165463&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=625", "C:/Users/imkid/Desktop/pic3.jpg");
t1.start();
t2.start();
t3.start();
/*
* 可以看到下载顺序是图片大小由小到大, 而不是按照代码顺序.
*/
}
}
class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常, downloader方法出现问题.");
}
}
}
实现Runnable接口创建线程
/**
* 总结: 推荐使用实现Runnable接口的方法, 这样可以避免java单继承的局限性,
* 并且灵活方便, 方便同一个对象被多个线程使用, 例如"一份资源, 多个代理"
*/
//创建线程方式2: 实现Runnable接口, 重写run()方法, 执行线程需要传入Runnable接口实现类, 调用start()方法.
public class TestThread3 implements Runnable {
public void run() {
//run方法线程体
for (int i = 1; i <= 500; i++) {
System.out.println("测试线程运行第" + i + "下");
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类对象
TestThread3 test = new TestThread3();
//创建线程对象, 通过线程对象来开启我们的线程, 这就是代理
//Thread thread = new Thread(test);
//thread.start();
new Thread(test).start();
for (int i = 1; i <= 1000; i++) {
System.out.println("主线程运行第" + i + "下");
}
}
}
两者对比
线程不安全示例
//多个线程同时操作同一个对象
//买火车票的例子
//发现问题: 多个线程操作同一个资源的情况下, 线程不安全, 数据紊乱
public class TestThread4 implements Runnable {
private int ticket_nums = 10;
@Override
public void run() {
while (true) {
if (ticket_nums <= 0) {
break;
}
//cpu执行太快可能不会出现问题, 所以人工干预下以便暴露并发问题
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticket_nums-- + "票");
}
}
public static void main(String[] args) {
TestThread4 test = new TestThread4();
new Thread(test, "A").start();
new Thread(test, "B").start();
new Thread(test, "C").start();
}
}
模拟龟兔赛跑
//模拟龟兔赛跑
public class Race implements Runnable {
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟兔子睡觉, 跑30米休息一次
if (Thread.currentThread().getName().equals("兔子") && (i % 30 == 0)) {
System.out.println("兔子开始休息了");
try {
Thread.sleep(10);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "--> 跑了" + i + "步");
if (gameOver(i)) System.exit(0);
}
}
//判断是否完成比赛
private boolean gameOver(int step) {
if (winner != null) {
return true;
}
if (step >= 100) {
winner = Thread.currentThread().getName();
System.out.println("胜利者是" + winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}
实现Callable接口创建线程
import java.util.concurrent.*;
//实现Callable接口
public class TestCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "打印了" + i);
}
return 1;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable();
TestCallable t2 = new TestCallable();
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(2);
//提交执行
Future<Integer> f1 = service.submit(t1);
Future<Integer> f2 = service.submit(t2);
//获取结果
int i1 = f1.get();
int i2 = f2.get();
/*//也可以不获取结果直接执行
service.submit(t1);
service.submit(t2);*/
//关闭服务
service.shutdown();
System.out.println(i1 + i2);
}
}
静态代理
代理模式总结:
真实对象和代理对象都要实现同一个接口;
代理对象要代理真实角色;
好处:
代理对象可以做很多真实对象做不了的事;
真实对象专注做自己的事情;
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany wc = new WeddingCompany(new Someone());
wc.marry();
}
}
interface Marry {
void marry();
}
class Someone implements Marry {
@Override
public void marry() {
System.out.println("某人结婚");
}
}
class WeddingCompany implements Marry {
private Marry customer;
public WeddingCompany(Someone customer) {
this.customer = customer;
}
@Override
public void marry() {
System.out.println("结婚前");
customer.marry();
System.out.println("结婚后");
}
}
Lambda表达式
函数式接口: 任何接口, 如果只包含唯一一个抽象方法, 那么它就是一个函数式接口(Functional Interface). 例如Runnable接口只有一个run()方法, 它就是一个函数式接口.
对于函数式接口, 可以通过Lambda表达式来创建该接口的对象
为什么要使用Lambda表达式?
- 避免匿名内部类定义过多
- 可以让代码看起来更简洁
- 去掉一堆无意义的代码, 只留下核心逻辑
语法格式:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
public class TestLambda {
public static void main(String[] args) {
Move dog = () -> {
System.out.println("If dog:");
System.out.println("Then run");
};
dog.move();
Calculate add = (a, b) -> {
System.out.println("加法");
return a + b;
};
System.out.println(add.calculate(1, 2));
add = (a, b) -> a * b;
System.out.println(add.calculate(3, 4));;
}
}
interface Move {
void move();
}
interface Calculate {
int calculate(int a, int b);
}
线程状态
线程有五大状态
观测线程状态
Thread.State 线程状态 , 线程可以处于以下状态之一:
• NEW
尚未启动的线程处于此状态。
• RUNNABLE
在Java虚拟机中执行的线程处于此状态。
• BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
• WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
• TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
• TERMINATED
已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("**********");
}
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
System.out.println(thread.getState());
while (state != Thread.State.TERMINATED) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
state = thread.getState(); //更新线程状态
System.out.println(state);
}
}
}
线程相关操作
停止线程
- 不推荐jdk提供的stop()、destroy()方法[已废弃]
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量, if (!flag) 线程终止
public class TestStop implements Runnable{
//1. 线程中定义线程体使用的标记
private boolean flag = true;
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
//2. 线程体使用该标记
if (!flag) break;
System.out.printf("线程运行%d次\n", i);
}
}
//3. 对外提供方法改变标记
public void stop() {
flag = false;
}
public static void main(String[] args) {
TestStop test = new TestStop();
new Thread(test).start();
for (int i = 1; i < 1000; i++) {
if (i == 500) {
//调用stop()方法切换标志位, 让线程停止
test.stop();
System.out.println("线程停止了");
}
System.out.printf("main线程运行%d次\n", i);
}
}
}
线程休眠
- sleep(time)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时, 倒计时等
- 每一个对象都有一个锁, sleep不会释放锁
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟计时
public class TestSleep {
public static void main(String[] args) {
Date date = new Date(System.currentTimeMillis());
//Date date = new Date(); //等价于上一条语句
System.out.println(date); //Mon Sep 19 22:08:05 CST 2022
//运行十秒程序结束
new Thread(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.exit(0);
}).start();
while (true) {
System.out.println(new SimpleDateFormat("yyyy.MM.dd hh:mm:ss").format(date));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
date.setTime(System.currentTimeMillis());
}
}
}
线程礼让
- 线程礼让, 让当前正在执行的线程暂停, 但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度, 礼让不一定成功, 看CPU心情
public class TestYield implements Runnable {
@Override
public void run() {
System.out.printf("线程%s开始执行\n", Thread.currentThread().getName());
//线程礼让
Thread.yield();
System.out.printf("线程%s执行完毕\n", Thread.currentThread().getName());
}
public static void main(String[] args) {
TestYield test = new TestYield();
new Thread(test, "A").start();
new Thread(test, "B").start();
}
}
线程A开始执行
线程B开始执行
线程B执行完毕
线程A执行完毕
线程合并(强制立即执行)
- join()合并线程, 待此线程执行完成后, 再执行其他线程, 其他线程阻塞
- 可以想象成插队
public class TestJoin implements Runnable {
@Override
public void run() {
System.out.println("VIP线程来了");
for (int i = 1; i <= 200; i++) {
System.out.println("VIP线程输出了:" + i);
}
}
public static void main(String[] args) {
TestJoin test = new TestJoin();
Thread thread = new Thread(test, "vip");
thread.start();
for (int i = 1; i < 400; i++) {
if (i == 200) {
System.out.println("VIP线程来了");
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.printf("%s输出了%d\n", Thread.currentThread().getName(), i);
}
}
}
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志, 监控内存, 垃圾回收等待…
public class TestDaemon {
public static void main(String[] args) {
Thread thread = new Thread(new Daemon());
thread.setDaemon(true); //默认是false表示用户线程, 正常的线程都是用户线程
thread.start();
new Thread(new NormalThread()).start();
}
}
class NormalThread implements Runnable {
@Override
public void run() {
System.out.println("普通线程开始运行, 5s后结束");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("普通线程结束运行");
}
}
class Daemon implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守护线程正在运行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程同步
并发: 同一个对象
被多个线程
同时操作, 例如多人一起抢票
♦ 处理多线程问题时, 多个线程访问同一个对象, 并且某些线程还想修改这个对象. 这时候就需要线程同步. 线程同步其实就是一种等待机制, 多个需要同时访问此对象的线程进入这个对象的等待池
形成队列, 等待前面线程使用完毕, 下一个线程再使用.
♦ 由于同一进程的多个线程共享同一块存储空间, 在带来方便的同时也带来了访问冲突问题, 为了保证数据在方法中被访问时的正确性, 在访问时加入锁机制synchronized
, 当一个线程获得对象的排它锁, 独占资源, 其他线程必须等待, 使用后释放锁即可, 但是存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下, 加锁, 释放锁会导致比较多的上下文切换和调度延时, 引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁, 会导致优先级导致, 引起性能问题.
同步方法与synchronized关键字
♦ 由于可以通过private关键字来保证数据对象只能被方法访问, 所以只需要针对方法提出一套机制, 这套机制就是synchronized关键字, 它包括两种用法: synchronized方法和synchronized块:
同步方法: public synchronized void method(int args) {}
♦ 互斥锁: java中引入了对象互斥锁的概念, 来保证共享数据操作的完整性, 每一个对象都对应于一个可称为"互斥锁"的标记, 这个标记用来保证在任一时刻, 只能有一个线程访问该对象.
♦ 关键字synchronized来与对象的互斥锁联系, 当某个对象用synchronized修饰时, 表明该对象在任一时刻只能由一个线程访问. 每个对象对应一把锁, 每个synchronized方法都必须获得调用该方法的对象的锁才能执行, 否则线程会阻塞, 方法一旦执行, 就独占该锁, 直到方法返回才释放锁, 后面被阻塞的线程才能获得这个锁, 继续执行.
缺陷: 若将一个大的方法申明为synchronized将会影响效率
♦ 方法里需要修改的内容才需要锁, 锁的太多浪费资源
♦ 同步的局限性: 导致程序的执行效率降低
♦ 非静态的同步方法的锁可以是this, 也可以是其他对象(要求是同一个对象)
♦ 静态的同步方法的锁为当前类本身
public synchronized static void fun() {
...
}
public synchronized void fun() {
synchronized (ClassName.class) {
...
}
}
• 注意事项和细节
- 同步方法如果没有使用static修饰, 默认锁对象为this
- 如果方法使用static修饰, 默认锁对象为当前类.class
- 实现的落地步骤:
• 需要先分析上锁的代码
• 选择同步代码块或同步方法
• 要求多个线程的锁对象为同一个即可 - synchronized锁为非公平锁
买票案例:
public class BuyTicket implements Runnable {
private int ticket_num = 50;
@Override
public void run() {
while (ticket_num > 0) { //此处判断完之后, 当前线程下的ticket_num还是可能改变, 所以下面的方法里面还需要判断一次
buyTicket();
try {
Thread.sleep(20); //sleep方法要在同步代码块之外
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("票卖完了");
}
private synchronized void buyTicket() { //synchronized方法
if (ticket_num > 0) { //这里需要加个判断, 不加判断的话, AB线程如果同时进入循环, A先执行buyTicket方法, 票卖完了, B还会继续卖, 因为B已经进入循环了, 不会再判断循环条件
System.out.println(Thread.currentThread().getName() + "售出了一张票, 还剩" + --ticket_num + "张");
}
}
private void buyTicket1() {
synchronized (this) { //synchronized代码块
if (ticket_num > 0) {
System.out.println(Thread.currentThread().getName() + "售出了一张票, 还剩" + --ticket_num + "张");
}
}
}
public static void main(String[] args) {
BuyTicket user = new BuyTicket();
new Thread(user, "A").start();
new Thread(user, "B").start();
new Thread(user, "C").start();
}
}
取钱案例:
public class Bank extends Thread{ //继承Thread类因为开启线程的对象不是同一个对象所以无法直接用同步方法
private User user; //户头
private static int account; //储蓄
private static final Object o = new Object(); //同步监视器
Bank(User user, int account) {
this.user = user;
this.account = account;
}
public void run() {
withDraw();
}
public void withDraw() { //取钱方法
synchronized (o) { //同步监视器
if (account - user.getWithdraw_money() <= 0) {
System.out.println("余额不足, 无法提取");
return;
}
account -= user.getWithdraw_money();
System.out.println(user.getName() + "取出了" + user.getWithdraw_money() + "元, 账户余额 " + account + "元");
}
}
public static void main(String[] args) {
Bank a = new Bank(new User("A", 1000), 4000);
Bank b = new Bank(new User("B", 1000), 4000);
Bank c = new Bank(new User("C", 2000), 4000);
Bank d = new Bank(new User("D", 1000), 4000);
a.start();
b.start();
c.start();
d.start();
}
}
class User {
private String name;
private int withdraw_money;
User(String name, int withdraw_money) {
this.name = name;
this.withdraw_money = withdraw_money;
}
String getName() {
return name;
}
int getWithdraw_money() {
return withdraw_money;
}
}
Lock(锁)
class A {
private final ReentrantLock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
//可能出现异常的代码
} finally {
lock.unlock(); //如果同步代码有异常, 要将unlock()写入finally语句块
}
}
}
synchronized与Lock的对比
死锁
♦ 多个线程各自占有一些共享资源, 并且互相等待其他线程占有的资源才能运行, 而导致两个或者多个线程都在等待对方释放资源, 都停止执行的情形. 某一个同步块同时拥有"两个以上对象的锁"时, 就可能会发生"死锁"的问题.
♦ 产生死锁的四个必要条件:
- 互斥条件: 一个资源每次只能被一个进程使用
- 请求与保持条件: 一个进程因请求资源而阻塞时, 对已获得的资源保持不放
- 不剥夺条件: 进程已获得的资源, 在未使用完之前, 不能强行剥夺
- 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
♦ 避免死锁: 破坏以上任意一个或者多个条件, 例如不要在锁里面加锁
死锁示例:
public class TestDeadLock {
public static void main(String[] args) {
Resource resource = new Resource(true);
Resource resource2 = new Resource(false);
Thread timer = new Thread(() -> { //计时器, 一定时间后程序没有停止就说明阻塞了
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("5s过去了程序还没有停止, 说明产生了死锁, 线程阻塞了");
System.exit(0);
});
timer.setDaemon(true);
timer.start();
resource.start();
resource2.start();
}
}
class Resource extends Thread {
private final boolean flag;
private final static Object o1 = new Object();
private final static Object o2 = new Object();
Resource(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "得到了资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "得到了资源2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "得到了资源2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "得到了资源1");
}
}
}
}
}
线程协作
注意: 必须是获得的锁对象调用这些方法
生产者-消费者问题
解决方式1------管程法
//测试: 生产者消费者模型 ---> 利用缓冲区解决: 管程法
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
Producer producer = new Producer(container);
Consumer consumer = new Consumer(container);
Consumer consumer2 = new Consumer(container);
producer.start();
new Thread(consumer, "壹号").start();
new Thread(consumer2, "贰号").start();
}
}
class Producer extends Thread {
private final SynContainer container;
Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) { //总共会生产100件产品
container.push(new Product(i));
try {
Thread.sleep((long)(Math.random() * 1000)); //生产时间随机
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Consumer implements Runnable {
private final SynContainer container;
Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "消费了产品" + container.pop().getNum());
try {
Thread.sleep((long)(500 + Math.random() * 500)); //消费时间随机
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Product {
private final int num; //产品标号
Product(int i) {
num = i;
}
public int getNum() {
return num;
}
}
class SynContainer {
Product[] products = new Product[10]; //容器大小
private int count = 0;
public synchronized void push(Product product) { //生产
if (count == 10) { //等待消费者消费
try {
this.wait(); //线程进入等待状态, 等待notify唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products[count++] = product;
System.out.println("生产者生产了一件产品, 产品编号" + product.getNum() + ", 容器里现在有" + count + "件产品");
this.notify(); //唤醒任一正在等待中的线程, 主流的HotSpot虚拟机默认先进先出
}
public synchronized Product pop() { //消费
while (count == 0) {
/*
等待生产者生产, 用while不用if, 因为两个消费者线程可能会同时卡在wait处.
例如壹号先wait释放锁, 贰号获得锁进入同步方法也停在wait处释放锁, 然后生产者获得锁生产了一件产品, 通知壹号继续执行,
壹号执行完后贰号执行, 这时候数组就越界了
*/
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Product p = products[--count];
System.out.println("消费者消费了一件产品, 产品编号" + p.getNum() + ", 容器里现在有" + count + "件产品");
this.notifyAll(); //唤醒所有等待中的线程(实际上是被唤醒的线程执行完毕后自动唤醒下一个线程), 主流的HotSpot虚拟机默认后进先出
return p;
}
}
解决方式2------信号灯法
public class TestSignal {
public static void main(String[] args) {
Signal signal = new Signal();
Car car = new Car(signal);
Human human = new Human(signal);
new Thread(car).start();
new Thread(human).start();
}
}
class Signal {
boolean flag = true; //true为绿灯, false为红灯, 绿灯车行, 红灯人行
public void setFlag(boolean flag) {
this.flag = flag;
}
}
class Car implements Runnable {
private final Signal signal;
Car(Signal signal) {
this.signal = signal;
}
@Override
public void run() {
int i = 0;
while (i < 30) {
synchronized (signal) {
if (!signal.flag) {
try {
signal.wait(); //注意这里不是this.wait(), 因为获得的锁对象是signal而不是this
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("绿灯车行");
i++;
signal.setFlag(!signal.flag);
signal.notify();
}
}
}
}
class Human implements Runnable {
private final Signal signal;
Human(Signal signal) {
this.signal = signal;
}
@Override
public void run() {
int i = 0;
while (i < 30) {
synchronized (signal) {
if (signal.flag) {
try {
signal.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("红灯人行");
i++;
signal.setFlag(!signal.flag);
signal.notify();
}
}
}
}