跟着狂神大大学习线程啦!操作系统课上学过一些,所以这一部分应该很快就能学完的!
一、线程简介
1.1 程序、进程、线程
程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
进程:进程是程序的一次执行过程,是一个动态的概念。它是系统资源分配的单位。
线程:通常一个进程中包含若干个线程。线程是 CPU 调度和执行的单位。
注:一个 CPU 在同一个时间点,只能执行一个线程,但由于切换的快,会产生多个线程同时执行的错觉。
二、线程实现(重点)
三种线程创建方式
- Thread class:继承 Thread 类
- Runnable 接口:实现 Runnable 接口
- Callable 接口:实现 Callable 接口
2.1 Thread 类
使用 Thread 类创建线程步骤如下:
- 自定义线程类继承 Thread 类
- 重写 run () 方法,编写线程执行体
- 创建线程对象,调用 start () 方法启动线程
run () 方法与 start () 方法区别(图 from 狂神说):
调用 start () 方法的代码部分:
public class TestThread01 extends Thread{
@Override
public void run() {
//run方法体线程
for (int i = 0; i < 20; i++) {
System.out.println("我是run方法体线程" + i);
}
}
public static void main(String[] args) {
//main线程
//创建一个线程对象
TestThread01 testThread01 = new TestThread01();
//调用start()方法开启线程
testThread01.start();
for (int i = 0; i < 20; i++) {
System.out.println("我是main方法体线程" + i);
}
}
}
部分输出结果显示:
由此可以发现,使用 start () 方法,两个线程是并行交替执行的。
调用 run () 方法的代码部分 (不要用!只是展示一下和 start () 方法的区别!):
public class TestThread01 extends Thread{
@Override
public void run() {
//run方法体线程
for (int i = 0; i < 20; i++) {
System.out.println("我是run方法体线程" + i);
}
}
public static void main(String[] args) {
//main线程
//创建一个线程对象
TestThread01 testThread01 = new TestThread01();
//调用run()方法开启线程
testThread01.run();
for (int i = 0; i < 20; i++) {
System.out.println("我是main方法体线程" + i);
}
}
}
部分输出结果显示:
由此可见,调用 run () 方法后,CPU 先去执行 run () 线程,再去执行主线程。(因为 run () 现在在前调用的。若 run () 线程在主线程输出之后调用,则会先执行主线程的输出,再执行 run () 中的输出)
总结:线程开启不一定执行,由 CPU 调度执行。
2.2 实现 Runnable 接口
实现 Runnable 接口创建线程步骤如下:
- 定义 MyRunnable 类实现 Runnable 接口
- 重写 run () 方法,编写线程执行体
- 创建线程对象,调用 start () 方法启动线程
代码部分:
public class TestThread02 implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我是run方法线程体" + i);
}
}
//main方法线程体
public static void main(String[] args) {
//创建Runnable接口实现类
TestThread02 testThread02 = new TestThread02();
//创建线程对象,通过线程对象来开启线程 --> 代理
new Thread(testThread02).start();
for (int i = 0; i < 20; i++) {
System.out.println("我是main方法线程体" + i);
}
}
}
部分运行结果显示:
总结:
① 继承 Thread 类
- 子类继承 Thread 类具备多线程能力
- 启动线程:子类对象 . start ()
- 不建议使用:避免 OOP 单继承局限性
② 实现 Runnable 接口
- 实现接口 Runnable 具备多线程能力
- 启动线程:传入目标对象 + Thread 对象 . start ()
new Thread (目标对象) . start () - 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程调用
2.3 多个线程操作同一个对象(案例:模拟买票)
案例:模拟买票
public class TestThread03 implements Runnable{
//票数
private int tickets = 10;
@Override
public void run() {
while (tickets >= 0) {
System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "票");
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestThread03 testThread03 = new TestThread03();
new Thread(testThread03,"张三").start();
new Thread(testThread03,"李四").start();
new Thread(testThread03,"王五").start();
}
}
运行结果显示:
但是可能会存在问题:多个线程在操作同一个资源的情况下,线程不安全,数据会紊乱。后续线程同步时会解决该问题。
2.4 案例:龟兔赛跑
public class TestThread04 implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子") && (i%10) == 0)
try {
Thread.sleep(100);
} 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 == 100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
return false;
}
public static void main(String[] args) {
TestThread04 testThread04 = new TestThread04();
new Thread(testThread04,"乌龟").start();
new Thread(testThread04,"兔子").start();
}
}
部分运行结果显示:
2.5 实现 Callable 接口(了解)
① 实现 Callable 接口,需要返回值类型
② 重写 call 方法,需要抛出异常
③ 创建目标对象
④ 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1)
⑤ 提交执行:Future< Boolean > result1 = ser.submit(t1)
⑥ 获取结果:boolean r1 = result1.get()
⑦ 关闭服务:ser.shutdownNow()
2.6 静态代理
public class Main {
public static void main(String[] args) {
Company company = new Company(new Person());
company.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class Person implements Marry{
@Override
public void HappyMarry() {
System.out.println("张三要结婚了");
}
}
//代理角色
class Company implements Marry{
private Marry target;
public Company(Marry target){ //构造器
this.target = target;
}
@Override
public void HappyMarry() {
System.out.println("结婚前");
this.target.HappyMarry();
System.out.println("结婚后");
}
}
输出结果:
结婚前
张三要结婚了
结婚后
静态代理模式总结:
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
- 好处:
① 代理对象可以做很多真是对象做不了的事情
② 真实对象专注做自己的事情
2.7 Lambda 表达式
直接上例子:
new Thread ( ()->System.out.println("这就是Lambda表达式") ).start();
为什么要使用 Lambda 表达式?
- 避免匿名内部类过多
- 可以让代码看起来很简洁
- 去掉了一些没有意义的代码,只留下核心逻辑
- 实质属于函数式编程
关键:函数式接口
函数式接口定义:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。例如:
public interface Runnable{
public abstract void run();
}
- 对于函数式接口,我们可以用过 Lambda 表达式来创建该接口的对象。
Lambda 表达式进化过程。
一开始我们学习的时候:
public class Main {
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I Like Lambda");
}
}
演变成静态内部类(接口同上):
public class Main {
//静态内部类
static class Like implements ILike{
@Override
public void lambda() {
System.out.println("I Like Lambda");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
}
}
还有局部内部类(接口同上):
public class Main {
public static void main(String[] args) {
//局部内部类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I Like Lambda");
}
}
ILike like = new Like();
like.lambda();
}
}
进化成匿名内部类:
public class Main {
public static void main(String[] args) {
//匿名内部类,没有类的名称,必须借助接口或者父类
ILike like = new ILike(){
@Override
public void lambda() {
System.out.println("I Like Lambda");
}
};
like.lambda();
}
}
最终版!Lambda 表达式:
public class Main {
public static void main(String[] args) {
//Lambda表达式
ILike like = () -> {
System.out.println("I Like Lambda");
};
like.lambda();
}
}
来个带参数的例子!有进化过程!
① 初始版本
public class Main {
public static void main(String[] args) {
ILove love = new Love();
love.love(233);
}
}
interface ILove{
void love(int a);
}
class Love implements ILove{
@Override
public void love(int a) {
System.out.println("I Love You -->" + a);
}
}
② 静态内部类
public class Main {
//静态内部类
static class Love implements ILove{
@Override
public void love(int a) {
System.out.println("I Love You -->" + a);
}
}
public static void main(String[] args) {
ILove love = new Love();
love.love(233);
}
}
③ 局部内部类
public class Main {
public static void main(String[] args) {
//局部内部类
class Love implements ILove{
@Override
public void love(int a) {
System.out.println("I Love You -->" + a);
}
}
ILove love = new Love();
love.love(233);
}
}
④ 匿名内部类
public class Main {
public static void main(String[] args) {
//匿名内部类
ILove love = new ILove() {
@Override
public void love(int a) {
System.out.println("I Love You -->" + a);
}
};
love.love(233);
}
}
⑤ Lambda 表达式
public class Main {
public static void main(String[] args) {
//Lambda表达式
ILove love = (int a) -> {
System.out.println("I Love You -->" + a);
};
love.love(233);
}
}
⑥ Lambda 表达式再简化(去参数化类型、去花括号)
前提:代码只有一行
public class Main {
public static void main(String[] args) {
//Lambda表达式简化
ILove love = a -> System.out.println("I Love You -->" + a);
love.love(233);
}
}
三、线程状态
3.1 线程的五大状态
创建状态、就绪状态、运行状态、阻塞状态、死亡状态
贴图!(图 from 狂神说)
3.2 停止线程
不推荐使用 JDK 提供的 stop() 、destroy() 方法!
- 推荐让线程自己停下来 --> 利用次数,不建议死循环
- 建议使用一个标志位作为终止变量。当 flag = false ,则终止线程运行。
public class TestStop implements Runnable{
//定义一个私有标志位,安全
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run......Thread" + (i++));
}
}
//设置一个公开的方法转换标志位,从而停止线程
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
//创建线程对象
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 100; i++) {
System.out.println("main" + i);
if (i == 50){
//调用stop1方法转换标志位,停止线程
testStop.stop();
System.out.println("线程该停止了");
}
}
}
}
部分输出结果显示:
3.3 线程休眠
sleep 指定当前线程阻塞毫秒数
- sleep 存在异常 InterruptedException
- sleep 时间达到后线程进入就绪状态
- sleep 可以模拟网络延时、倒计时等
- 每一个对象都有一个锁,sleep 不会释放锁
模拟网络延时:能够放大问题的发生性。
例子同 2.3 模拟买票。
模拟倒计时:
public class Main{
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException {
int num = 10; //倒计时10秒
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0)
break;
}
}
}
打印当前系统时间:
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main{
public static void main(String[] args) {
Date startTime = new Date(System.currentTimeMillis()); //获取系统当前时间
while (true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis()); //更新当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.4 线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞。
- 将线程从运行状态转为就绪状态
- 让 CPU 重新调度,礼让不一定成功!看 CPU 心情!
大白话:礼让就是让线程从运行态变为就绪态,就绪态到运行态是需要 CPU 调度的,所以 A 礼让结束后,下一次 CPU 可能还是会调度 A 。
代码部分:
public class Main {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield(); //线程礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
输出结果(礼让成功):
b线程开始执行
a线程开始执行
b线程停止执行
a线程停止执行
输出结果(礼让不成功):
b线程开始执行
b线程停止执行
a线程开始执行
a线程停止执行
3.5 线程强制执行
Join 合并线程:待此线程执行完成后,再执行其他线程。此线程执行时,其他线程处于阻塞态。相当于插队。
代码部分:
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程vip来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//开启线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
//主线程
for (int i = 0; i < 20; i++) {
if (i == 10){
thread.start();
thread.join(); //代理插队
}
System.out.println("main线程" + i);
}
}
}
部分运行结果显示:
3.6 线程状态观测
Thread.State
NEW:新建进程
RUNNING:运行中
BLOCKED:阻塞
WAITING:等待另一个线程执行特定动作
TIMED_WAITING:等待另一个线程到达指定等待时间
TERMINATED:死亡
代码部分:
public class Main {
public static void main(String[] args) throws InterruptedException {
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); //NEW
//启动线程
thread.start();
state = thread.getState();
System.out.println(state); //RUNNABLE
while (state != Thread.State.TERMINATED) //只要线程不终止
{
Thread.sleep(100);
state = thread.getState(); //更新线程状态
System.out.println(state);
}
}
}
3.7 线程优先级
线程优先级用数字表示,范围1~10.
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY = 10
- Thread.NORM_PRIORITY = 5
优先级高的不一定先执行,但是权重大,更容易优先执行。
改变优先级:setPriority(int XXX)
获取优先级:getPriority()
代码部分(先设置优先级再启动):
public class Main {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
t1.setPriority(Thread.MAX_PRIORITY); //MAX_PRIORITY = 10
t1.start();
t2.setPriority(3);
t2.start();
t3.setPriority(Thread.MIN_PRIORITY); //MIN_PRIORITY = 1
t3.start();
t4.setPriority(8);
t4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
输出结果1:
main–>5
Thread-0–>10
Thread-3–>8
Thread-1–>3
Thread-2–>1
输出结果2:
main–>5
Thread-0–>10
Thread-2–>1
Thread-1–>3
Thread-3–>8
由此可见,并不是优先级高的线程就一定先执行!只是优先执行的概率高了!还是要看 CPU 的调度!
3.8 守护线程(daemon)
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 用户线程:main 线程等
守护线程:后台记录操作日志、监控内存、垃圾回收(gc)等等
设置守护线程:Thread.setDaemon(boolean)
默认为 false,表示是用户线程。正常的线程都是用户线程。
代码部分:
public class Main {
public static void main(String[] args) {
God god = new God();
You you = new You();
//守护线程启动
Thread thread = new Thread(god);
thread.setDaemon(true); //设置为守护线程
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("你开心的活着...");
}
System.out.println("======Goodbye World======");
}
}
部分输出结果显示:
注:用户线程停止,虚拟机停止。但虚拟机停止需要一段时间,所以守护线程会在用户线程结束后再运行一段时间。
四、线程同步(重点)
4.1 同步、并发、队列和锁
并发:同一个对象被多个线程同时操作
线程同步:处理多线程问题时,多个线程访问同一个对象(并发),并且某些线程还想修改这个对象,这个时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的进行进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
线程同步形成条件:队列 + 锁 --> 安全
由于统一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。但会存在一下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 在多线程竞争下,枷锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
关于 Runnable 与 Thread 中哪里写什么的问题:
Runnable就是能做的事情、要做的事情。
Thread 就是能做事情的人。所以 Thread 有 name。
4.2 线程不安全的例子
ArrayList 就是不安全的
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String >();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
//模拟延时
Thread.sleep(1000);
System.out.println(list.size());
}
}
输出结果:
9999
注:每个人每次执行结果都是不一样的,反正就是要存10000个数据,真正存进去的可能没有10000个。
4.3 同步方法及同步块
同步方法(就是加锁):
public synchronized void method(int args) {}
缺陷:若将一个大的方法声明为 synchronized 将会影响效率
同步块:
synchronized (Obj) {}
Obj 称为同步监视器:
- Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class(反射中的知识点)
要锁需要共享的量,也就是同步资源!!!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String >();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (list) { //要共享的是list这个资源
list.add(Thread.currentThread().getName());
}
}).start();
}
//模拟延时
Thread.sleep(1000);
System.out.println(list.size());
}
}
输出结果:
10000
4.4 死锁
多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都无法向前推进。某一个同步代码块同时拥有 “两个以上对象的锁” 时,就可能会发生“死锁”的问题。
死锁产生的必要条件:
① 互斥条件。指在一段时间内某资源仅为一个进程所占有。
② 不剥夺条件。指进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,而只能由该进程自己释放。
③ 请求并保持条件。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程所占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
④ 循环等待条件。指存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。
操作系统学过,所以直接贴代码!
public class Main {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"A");
Makeup g2 = new Makeup(1,"B");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
//竞争资源,用static保证只有一份,final保证不允许被更改
static final Lipstick lipstick = new Lipstick();
static final Mirror mirror = new Mirror();
int choice; //选择
String name; //使用化妆品的人
public Makeup (int choice, String name){
this.choice = choice;
this.name = name;
}
@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.name + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){ //已经获得口红的同时还想要镜子
System.out.println(this.name + "想获得镜子的锁");
}
}
}
else{
synchronized (mirror){ //获得镜子的锁
System.out.println(this.name + "获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){ //已经获得镜子的同时还想要口红
System.out.println(this.name + "想获得口红的锁");
}
}
}
}
}
输出结果:
可以看到,线程之间产生了死锁,无法向前推进,程序终止不了。
五、线程通信问题
5.1 生产者消费者问题
在生产者消费者问题中,仅有 synchronized 是不够的。
- synchronized 可阻止并发更新同一个共享资源,实现同步
- synchronized 不能用来实现不同线程之间的消息传递(通信)
方法:
wait():表示线程会等待,直到其他线程通知
wait(long timeout):指定等待的毫秒数
notify():唤醒一个处于等待状态的线程
notifyAll():唤醒同一个对象上所有调用 wait() 方法的线程,优先级别高的优先调度
wait() 与 sleep() 的区别:sleep() 抱着锁睡觉,wait() 会放开锁。
5.2 管程法(缓冲区)
生产者将生产号的数据放入缓冲区,消费者从缓冲区拿出数据
public class Main {
public static void main(String[] args) {
Container container = new Container();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
Container container;
public Producer(Container container){
this.container = container;
}
//生产方法
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
container.push(new Chicken(i));
System.out.println("生产第" + i + "只鸡");
}
}
}
//消费者
class Consumer extends Thread{
Container container;
public Consumer(Container container){
this.container = container;
}
//消费方法
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("消费了第" + container.pop().id + "只鸡");
}
}
}
//产品
class Chicken{
int id; //产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class Container{
//需要一个容器
Chicken[] chickens = new Chicken[10]; //容器大小为10
int count = 0; //容器中现有产品数量
//生产者放入
public synchronized void push(Chicken chicken){
if (count == chickens.length){ //如果容器满了,等待消费者消费
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else { //如果容器没满,就可以放入产品
chickens[count++] = chicken;
//通知消费者消费
this.notifyAll();
}
}
//消费者取出
public synchronized Chicken pop(){
if (count == 0){ //如果容器为空,等待生产者生产
//通知生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器里有东西,可以消费
count--;
Chicken chicken = chickens[count];
//通知生产者生产
this.notifyAll();
return chicken;
}
}
部分输出结果显示:
5.3 信号灯法
public class Main {
public static void main(String[] args) {
Content content = new Content();
new Writer(content).start();
new Reader(content).start();
}
}
//写
class Writer extends Thread{
Content content;
public Writer(Content content){
this.content = content;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2 == 0){
this.content.write("一首诗歌");
}
else {
this.content.write("一篇散文");
}
}
}
}
//读
class Reader extends Thread{
Content content;
public Reader(Content content){
this.content = content;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
content.read();
}
}
}
//内容
class Content{
//写者写,读者等待 T
//读者读,写者等待 F
String test; //文本内容
boolean flag = true;
//写方法
public synchronized void write(String test){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("写者写了:" + test);
this.notifyAll(); //通知读者可以读了
this.test = test;
this.flag = !flag;
}
//读方法
public synchronized void read(){
if (flag){ //未写入,等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("读者读了:" + test);
this.notifyAll(); //通知写者可以写了
this.flag = !flag;
}
}
部分代码显示:
5.4 线程池
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
后记:学过操作系统的话还是比较轻松的。Java基础部分差不多了,下一个是前端。冲冲冲!