首页 > 代码库 > 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();
}
}
}
}
}

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    JavaSE 15 多线程