首页 > 代码库 > JavaSE 15 多线程
JavaSE 15 多线程
1、线程的相关概念
程序
为了让计算机完成某个特定的功能,而编写的一系列的有序指定的集合。即:一段静态的代码。
进程
正在运行的程序。即:动态的过程,有自己的产生、存在和消亡。
线程
一个进程可以划分为若干个小的执行单位,每一个执行单位,就是线程。
一个进程可以包含多个线程。
若一个程序可以在同一时间执行多个线程,就是支持多线程的。
多任务
同一时刻,好像同时执行多个任务(进程)。比如Windows操作系统就是多任务操作系统。多任务可以理解为多进程。
2、多线程
<h2>概念</h2>
同一时刻,好像同时执行多个线程。
每个Java程序都有一个隐含的主线程:main方法。
多线程的执行
多个线程轮流抢占CPU时间片,因为速度非常快,所以造成了多个线程同时执行的错觉。
使用的好处
⑴ 有效的占用了CPU的资源,从某种意义上来讲提高了效率。
⑵ 提高了用户体验性。
应用场景
⑴ 程序需要同时执行两个或多个任务。
⑵ 程序需要实现一些等待的任务,比如:用户输入、文件读写操作、网络操作、搜索等。
⑶ 程序需要后台一直完成某项操作。
3、Thread类
Java语言使用Thread类及其子类的对象来表示线程。
4、Thread类的构造方法
Thread()
public Thread() {}
构造一个Thread对象。
<h2>Thread(String name)</h2>
public Thread(String name) {}
构造一个Thread对象,同时指定线程的名字。
Thread的子类可以通过调用super(String name); 来设置线程的名字。
<h2>Thread(Runnable target, String name)</h2>
public Thread(Runnable target, String name) {}
构造一个Thread对象,需要传入一个Runnable的实现类,同时指定该线程的名字。
5、线程的创建和启动
继承Thread
⑴ 定义一个类,让其继承Thread类,再重写run方法。
⑵ 创建继承Thread的类的对象,并调用对象的start方法。
class MyThread extends Thread {
@Override
public void run() {
// 任务体
}
}
MyThread mt = new MyThread();
mt.start();
实现Runnable接口
⑴ 定义一个类,让其实现Runnable接口,再实现run方法。
⑵ 创建一个Thread对象,将实现Runnable接口的实现类的对象传入Thread的构造方法。
⑶ 调用Thread对象的start方法。
class MyRunnable implements Runnable {
@Override
public void run() {
// 任务体
}
}
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
对比
相同点
⑴ 任务体都写在run方法中
⑵ 都调用了的Thread类(本类或子类)的start方法来启动线程
<h3>不同点</h3>
相对于继承Thread类,实现Runnable接口:
⑴ 避免了单继承的局限性,更加灵活
⑵ 比较适合解决多个线程共享数据资源的情况
<h4>卖票【多个线程共享数据资源】</h4>
public class Test {
public static void main(String[] args) {
WindowRunnable wr = new WindowRunnable();
Thread t1 = new Thread(wr, “窗口1”);
Thread t2 = new Thread(wr, “窗口2”);
Thread t3 = new Thread(wr, “窗口3”);
t1.start();
t2.start();
t3.start();
}
}
class WindowRunnable implements Runnable {
private int tickets = 100; // 总票数
@Override
public void run() {
for (;;) {
if (0 < tickets) {
System.out.println(Thread.currentThread().getName() + “卖出了一张票,剩余票数:” + (–tickets));
} else {
System.out.println(Thread.currentThread().getName() + “票卖完了!”);
break;
}
}
}
}
6、Thread类的方法列举
start
public synchronized void start() {}
启动线程,并执行对应的run方法。
run
public void run() {}
子线程要执行的代码,要放入重写(实现)的此方法中。
currentThread
public static native Thread currentThread();
获取当前正在执行的线程对象。静态方法,通过Thread来调用。
setName
public final void setName(String name) {}
设置线程的名称。
getName
public final String getName() {}
获取线程的名称。
<h3>设置和获取线程名称【Thread的子类】</h3>
public class Test {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
mt1.start(); // Thread-0 【默认线程名字从0开始】
MyThread mt2 = new MyThread(“线程2”);
mt2.start(); // 线程2
MyThread mt3 = new MyThread();
mt3.setName(“线程3”); // 调用设置线程名称的方法
mt3.start(); // 线程3
}
}
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) { // 添加有参构造
super(name); // 调用父类【Thread】的构造方法
}
@Override
public void run() {
// 因为该线程类继承了Thread,所以可以直接调用【省略了this 完整:this.getName()】
System.out.println(getName());
}
}
<h3>设置和获取线程名称【Runnable的实现类】</h3>
public class Test {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, “线程1”); // 设置线程名称
Thread t2 = new Thread(mr, “线程2”); // 设置线程名称
t1.start(); // 线程1
t2.start(); // 线程2
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
// 通过调用Thread的静态方法获取当前线程对象,再调用方法获取线程名称
System.out.println(Thread.currentThread().getName());
}
}
setPriority
public final void setPriority(int newPriority) {}
设置线程的优先级。需要传入一个int类型的优先级。优先级范围1-10。注意:不能超过最高优先级【10】或低于最高优先级【1】,否则报错。看源码:
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
Thread类中有三个常量:
public final static int MIN_PRIORITY = 1; // 最低优先级
public final static int NORM_PRIORITY = 5; // 默认优先级
public final static int MAX_PRIORITY = 10; // 最高优先级
注意:<b>优先级高的线程不一定先执行完,它只是具有CPU抢占的优先权。</b>
getPriority
public final int getPriority() {}
返回线程的优先级。
sleep
public static native void sleep(long millis) throws InterruptedException;
让当前线程休眠(暂停执行)指定的毫秒数。静态方法,通过Thread来调用。
<h3>设置和获取线程的优先级并测试执行的优先级</h3>
public class Test {
public static void main(String[] args) {
MyThread mt1 = new MyThread(“最高优先级线程”);
mt1.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级【10】
mt1.start();
MyThread mt2 = new MyThread(“默认优先级线程”);
mt1.setPriority(Thread.NORM_PRIORITY); // 设置默认优先级【5】
mt2.start();
MyThread mt3 = new MyThread(“最低优先级线程”);
mt3.setPriority(Thread.MIN_PRIORITY); // 设置最低优先级【1】
mt3.start();
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(300); // 休眠线程300毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "被执行了");
}
}
}
可以看出最终的执行效果具有随机性,所以可以验证:
① 优先级高的线程不一定先执行完,它只是具有CPU抢占的优先权。
② CPU抢占的优先权是随机的。
isAlive
public final native boolean isAlive();
测试线程是否处于活动状态。
isInterrupted
public boolean isInterrupted() {}
测试线程是否已经中断。
interrupt
public void interrupt() {}
中断线程,但实际上并没有真正停止线程。方法的本质:给线程添加一个中断的标记。
注意
如果中断的是正在休眠的线程,则会执行try住Thread.sleep(long millis); 方法中的catch块中的语句【可能是错误:java.lang.InterruptedException: sleep interrupted 或 自定义代码】。
示例:
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
mt.interrupt(); // 添加中断标记
}
}
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000); // 线程休眠1000毫秒
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println(“我就是被执行的文字!”);
}
}
}
理解
给线程添加一个中断的标记,可以配合isInterrupted方法来使用。
示例:
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
try {
Thread.sleep(50); // 主线程休眠(暂停执行)50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
mt.interrupt(); // 添加中断标记
}
}
class MyThread extends Thread {
@Override
public void run() {
for (;;) {
System.out.println(“线程任务打印文字”);
if (this.isInterrupted()) { // 判断是否已经添加中断标记
break; // 跳出循环
}
}
System.out.println(“============任务结束了============”);
}
}
yield
public static native void yield();
暂停当前正在执行的线程对象,并执行其他线程。静态方法,通过Thread来调用。
注意:让出CPU的占用权,但是让出的时间具有不确定性。不一定让出CPU占用权的线程最后执行完。
<h3>查看其中一个线程暂停后的执行结果</h3>
public class Test {
public static void main(String[] args) {
MyThread1 mt1 = new MyThread1();
mt1.setName(“线程1”);
MyThread2 mt2 = new MyThread2();
mt2.setName(“线程2”);
MyThread3 mt3 = new MyThread3();
mt3.setName(“线程3”);
mt1.start();
mt2.start();
mt3.start();
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (10 == i) {
Thread.yield(); // 暂停当前线程
System.out.println(“==========暂停” + getName() + “==========”);
}
System.out.println(getName() + "被执行");
}
System.out.println("==========" + getName() + "执行完毕!==========");
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName() + “被执行”);
}
}
}
class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName() + “被执行”);
}
}
}
join
public final void join() throws InterruptedException {}
让线程抢占CPU的时间片。一旦抢到,该线程肯定会被执行完。
应用于当一个线程需要调用另一个线程的执行结果,而另一个线程并未执行完毕,这时就可以调用此方法,以便获取执行的结果。
<h3>主线程获取另外一子线程的执行结果</h3>
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 10; i++) {
System.out.println(mt.sum); // 获取求和的结果
}
try {
System.out.println("让mt线程获取CPU时间片, 请耐心等待结果的输出。。。");
mt.join(); // 调用join方法,让mt线程获取CPU时间片【若不让mt线程抢占CPU时间片,则主线程打印结果会是0】
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1-100的和为:" + mt.sum); // 输出求和的结果
}
}
class MyThread extends Thread {
int sum; // 求和结果
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
sum += i;
}
}
}
setDaemon
public final void setDaemon(boolean on) {}
将该线程标记为守护线程或用户线程。true为守护线程,false为用户线程。
线程的分类
用户线程
守护线程
<h4>区别</h4>
线程的执行是否要结束的方式
⑴ 用户线程有2种方式:
① 正常结束(任务执行完毕)
② 被中断
⑵ 守护线程只有1中方式:
用户线程执行结束
注意:如果用户线程已经执行结束,这时不管守护线程有没有结束,其都要结束。
<h4>JDK中典型的守护线程</h4>
垃圾回收机制
<h3>守护线程随着主线程的结束而结束</h3>
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setDaemon(true); // 设置为守护线程
mt.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("主线程执行");
}
System.err.println("==========主线程执行完毕==========");
}
}
class MyThread extends Thread {
@Override
public void run() {
for (;;) {
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----守护线程执行任务-----");
}
}
}
7、线程的停止
实现方法
⑴ 调用线程类的stop方法。但该方法已经过时(@Deprecated),不建议使用。
⑵ 调用线程类的interrupt方法。但是不能真正的停止。
⑶ 采用通知的方式。该方式可确保线程以安全的方式结束运行,建议使用。
<h3>通知方式的实现步骤</h3>
⑴ 在线程类中添加一个boolean类型的循环标记,默认为true。
⑵ 在线程类中的run方法中,添加一个循环,循环条件为刚才添加的boolean类型的循环标记。
⑶ 在线程类中添加一个更新循环标记的方法。
⑷ 在需要停止线程时,将循环标记改为false即可。
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
try {
Thread.sleep(50); // 主线程休眠(暂停执行)50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
mt.setLoop(false); // 更新循环标记
}
}
class MyThread extends Thread {
private boolean loop = true; // 循环标记
@Override
public void run() {
while (loop) { // 循环条件为循环标记
System.out.println(“线程任务打印文字”);
}
System.out.println(“============任务结束了============”);
}
public void setLoop(boolean loop) { // 更新循环标记的方法
this.loop = loop;
}
}
8、线程的生命周期
新建 —— 就绪 —— 运行 —— 阻塞 —— 死亡
新建
创建线程对象
就绪
调用了start方法
注意:调用此方法的线程将会进入线程队列等待CPU时间片,它只是具备了运行的条件。
运行
抢到了CPU的执行权
线程的run方法定义了线程的操作和功能。
阻塞
调用了sleep方法,或wait方法
在某种特殊的情况下,被人为挂起或输入输出操作时,让出CPU,并临时中止。
死亡
线程任务正常执行完毕,或异常停止
9、线程的同步
问题的产生
多个线程,共同操作同一个数据资源时,有可能其中一个线程还没有执行完毕,而另外一个线程参与进来。这样就会导致数据出现了脏读。这就是线程同步问题。
解决办法
对多条操作共享数据的语句,只能让一个线程都执行完,其他的线程再参与进来。在其中一个线程执行过程中,其他线程不可以参与执行。
具体就是:将需要共同操作的代码,上个“锁”。
互斥锁
Java中引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每一个对象都对应一个可以称为“互斥锁”的标记。这个标记用来保证在任一时刻,只能有一个线程对象访问该对象。
<h3>synchronized关键字</h3>
用synchronized关键字来与对象的互斥锁联系。<b>当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问。</b>
<h3>同步代码块</h3>
synchronized(对象) { // 对象就是“锁”
// 需要被同步的代码
}
<h3>同步方法</h3>
修饰符 synchronized 返回值类型 方法名(形参列表) {
// 需要被同步的代码
}
整个方法为同步方法。
注意:
⑴ 需要同步的代码一般里面没有循环。
⑵
① 普通的同步方法默认的锁对象为:this
② 静态的同步方法默认的锁对象为:当前类.class
实现步骤
⑴ 找到需要同步的代码【多个线程共同操作的代码】。
⑵ 添加同步代码块或同步方法。
⑶ 判断锁对象是否为同一个
释放锁的操作
⑴ 当前线程的同步代码块、同步方法执行完毕。
⑵ 当前线程在同步代码块、同步方法中遇到break或return终止了该代码块、方法的继续执行。
⑶ 当前线程在同步代码块、同步方法中出现了Error或Exception,导致异常结束。
⑷ 当前线程在同步代码块、同步方法中执行了线程对象的wait 方法。会使当前线程暂停,并释放互斥锁。
不会释放锁的操作
当某线程执行同步代码块、同步方法时,程序调用了Thread.sleep()【休眠线程】 或 Thread.yield()【暂停线程,让出CPU的抢占权】,暂停了当前线程的执行。这并不会释放互斥锁。
同步的前提
⑴ 有多个线程。
⑵ 多个线程有共享的数据。
⑶ 多个线程都有操作共享数据的代码。
同步的局限性
在一定程度上降低了程序的执行效率。
使用示例
不使用同步来卖票【出现了线程同步问题】
public class Test {
public static void main(String[] args) {
WindowRunnable wr = new WindowRunnable();
Thread t1 = new Thread(wr, “窗口1”);
Thread t2 = new Thread(wr, “窗口2”);
Thread t3 = new Thread(wr, “窗口3”);
t1.start();
t2.start();
t3.start();
}
}
class WindowRunnable implements Runnable {
private int tickets = 100; // 总票数
@Override
public void run() {
for (;;) {
if (0 < tickets) {
try {
Thread.sleep(10); // 让当前线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + (--tickets));
} else {
System.out.println(Thread.currentThread().getName() + "票卖完了!");
break;
}
}
}
}
可以看出最终的结果存在负数,所以出现了线程同步问题。
使用同步来卖票【解决线程同步问题】
public class Test {
public static void main(String[] args) {
WindowRunnable wr = new WindowRunnable();
Thread t1 = new Thread(wr, “窗口1”);
Thread t2 = new Thread(wr, “窗口2”);
Thread t3 = new Thread(wr, “窗口3”);
t1.start();
t2.start();
t3.start();
}
}
class WindowRunnable implements Runnable {
private int tickets = 100; // 总票数
private boolean loop = true; // 循环标记
private synchronized void sellTickets() { // 同步方法
if (0 < tickets) {
try {
Thread.sleep(10); // 让当前线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + (--tickets));
} else {
System.out.println(Thread.currentThread().getName() + "票卖完了!");
loop = false;
}
}
@Override
public void run() {
while (loop) {
sellTickets();
/* 同步代码块
synchronized(this) {
if (0 < tickets) {
try {
Thread.sleep(10); // 让当前线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + (--tickets));
} else {
System.out.println(Thread.currentThread().getName() + "票卖完了!");
break;
}
}
*/
}
}
}
解决单例模式(懒汉式)的线程同步问题
<h3>旧的代码【线程同步问题】</h3>
public class Test {
public static void main(String[] args) {
PrintThread pt1 = new PrintThread();
PrintThread pt2 = new PrintThread();
pt1.start();
pt2.start();
}
}
class PrintThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + Singleton.getInstance());
}
}
}
class Singleton {
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
可以看到:之前的代码,如果在多个线程中运行,会发现创建出的对象不是唯一的。
<h3>修改后的单例模式【解决线程同步问题】</h3>
public class Test {
public static void main(String[] args) {
PrintThread pt1 = new PrintThread();
PrintThread pt2 = new PrintThread();
pt1.start();
pt2.start();
}
}
class PrintThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + Singleton.getInstance());
}
}
}
class Singleton {
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance() {
if (null == instance) { // 提高效率
synchronized(Singleton.class) { // 同步代码块
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
10、线程的通信
概念
多个线程之间可以进行通话。
例如:开启了两个线程来打印100以内的数字,结果可能会是:
A 1
A 2
B 3
B 4
B 5
…
而我们希望是:
A 1
B 2
A 3
B 4
...
这种交替打印的效果
这时就需要使用线程的通信
常见方法
wait
public final native void wait(long timeout) throws InterruptedException;
使某个同步代码块或同步方法中的当前线程进入等待状态,直到另外一线程对该线程发出notify或notifyAll为止。该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。
注意:
⑴ 该方法在Object类中,即每个对象类都有。
⑵ 调用此方法后,当前线程会释放对象监控权(互斥锁),然后进入等待。
⑶ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。
⑷ 当线程被notify后,重新获取监控权(互斥锁),这时会从断点处【被wait的地方】继续执行。
<h3>notify</h3>
public final native void notify();
唤醒某个同步代码块或同步方法中的被wait的一个线程。<b>该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。</b>
注意:
⑴ 该方法在Object类中,即每个对象类都有。
⑵ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。
<h3>notifyAll</h3>
public final native void notifyAll();
唤醒某个同步代码块或同步方法中的被wait的所有线程。唤醒的顺序是随机的。<b>该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。</b>
注意:
⑴ 该方法在Object类中,即每个对象类都有。
⑵ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。
使用示例
<h3>两个线程交错打印1-100内的数</h3>
public class Test {
public static void main(String[] args) {
PrintNum pn = new PrintNum();
Thread t1 = new Thread(pn, “打印者一”);
Thread t2 = new Thread(pn, “打印者二”);
t1.start();
t2.start();
}
}
class PrintNum implements Runnable {
private int num = 0;
@Override
public void run() {
for (;;) {
synchronized (this) {
this.notify(); // 在此唤醒上次打印过的线程
if (num < 100) {
try {
Thread.sleep(26);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---" + (++num));
try {
this.wait(); // 每次打印一个数字,就让当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
<h3>生产者和消费者</h3>
public class Test {
public static void main(String[] args) {
Storehouse storehouse = new Storehouse();
Producer producer = new Producer(storehouse);
Consumer consumer = new Consumer(storehouse);
producer.start();
consumer.start();
}
}
/**
* 仓库
*/
class Storehouse {
private int total = 0; // 货物总数
/**
* 存储方法
*/
public synchronized void store() {
// 唤醒消费者 【可能消费者的线程的抢到的CPU时间片较多,其一旦消费了所有的产品,就会wait,所以需要用生产者来唤醒】
this.notify();
if (total < 20) {
System.out.println("生产者生产了1个产品,当前库存:" + (++total));
} else {
try {
this.wait(); // 达到库存上线,停止生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 取出方法
*/
public synchronized void getOut() {
// 唤醒生产者 【可能生产者的线程的抢到的CPU时间片较多,其一旦生产够了库存,就会wait,所以需要用消费者来唤醒】
this.notify();
if (total > 0) {
System.out.println("消费者消费了1个产品,当前库存:" + (--total));
} else {
try {
this.wait(); // 没有库存,无法消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 生产者
*/
class Producer extends Thread {
private Storehouse storehouse;
public Producer(Storehouse storehouse) {
this.storehouse = storehouse;
}
@Override
public void run() {
for (;;) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
storehouse.store();
}
}
}
/**
* 消费者
*/
class Consumer extends Thread {
private Storehouse storehouse;
public Consumer(Storehouse storehouse) {
this.storehouse = storehouse;
}
@Override
public void run() {
for (;;) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
storehouse.getOut();
}
}
}
线程同步的前提
⑴ 必须在同步的基础上(必须在同步代码块或同步方法中实现通信)。
⑵ 要求调用wait、notify、notifyAll的对象必须为锁对象。
线程的死锁问题
概念
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
示例【小华和小明同时过独木桥】
public class Test {
public static void main(String[] args) {
Person p1 = new Person(true); // 小明
Person p2 = new Person(false); // 小华
p1.start();
p2.start();
}
}
/**
* 小明
*/
class XiaoMing {
public void holdOn() {
System.out.println(“小明说:你先让我过去,我再让你过去”);
}
public void pass() {
System.out.println(“小明过了独木桥”);
}
}
/**
* 小华
*/
class XiaoHua {
public void holdOn() {
System.out.println(“小华说:你先让我过去,我再让你过去”);
}
public void pass() {
System.out.println(“小华过了独木桥”);
}
}
class Person extends Thread {
private static final XiaoMing xm = new XiaoMing();
private static final XiaoHua xh = new XiaoHua();
private boolean which; // true代表小明;false代表小华
public Person(boolean which) { // 传入boolean值,创建小明或小华
this.which = which;
}
@Override
public void run() {
if (which) { // 小明
synchronized (xm) {
xm.holdOn();
synchronized(xh) {
xm.pass();
}
}
} else { // 小华
synchronized (xh) {
xh.holdOn();
synchronized(xm) {
xh.pass();
}
}
}
}
}
JavaSE 15 多线程