JavaSE 15 多线程

1、线程的相关概念

程序

为了让计算机完成某个特定的功能,而编写的一系列的有序指定的集合。即:一段静态的代码。

进程

正在运行的程序。即:动态的过程,有自己的产生、存在和消亡。

线程

一个进程可以划分为若干个小的执行单位,每一个执行单位,就是线程。
一个进程可以包含多个线程。
若一个程序可以在同一时间执行多个线程,就是支持多线程的。

多任务

同一时刻,好像同时执行多个任务(进程)。比如Windows操作系统就是多任务操作系统。多任务可以理解为多进程。

2、多线程

概念

同一时刻,好像同时执行多个线程。
每个Java程序都有一个隐含的主线程:main方法。

多线程的执行

多个线程轮流抢占CPU时间片,因为速度非常快,所以造成了多个线程同时执行的错觉。

使用的好处

⑴ 有效的占用了CPU的资源,从某种意义上来讲提高了效率。
⑵ 提高了用户体验性。

应用场景

⑴ 程序需要同时执行两个或多个任务。
⑵ 程序需要实现一些等待的任务,比如:用户输入、文件读写操作、网络操作、搜索等。
⑶ 程序需要后台一直完成某项操作。

3、Thread类

Java语言使用Thread类及其子类的对象来表示线程。

4、Thread类的构造方法

Thread()

public Thread() {}
构造一个Thread对象。

Thread(String name)


public Thread(String name) {}
构造一个Thread对象,同时指定线程的名字。
Thread的子类可以通过调用super(String name); 来设置线程的名字。

Thread(Runnable target, String name)


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方法来启动线程

不同点

相对于继承Thread类,实现Runnable接口:
⑴ 避免了单继承的局限性,更加灵活
⑵ 比较适合解决多个线程共享数据资源的情况

卖票【多个线程共享数据资源】
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() {}
获取线程的名称。

设置和获取线程名称【Thread的子类】

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());
  }
}

设置和获取线程名称【Runnable的实现类】

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; // 最高优先级

注意:优先级高的线程不一定先执行完,它只是具有CPU抢占的优先权。

getPriority

public final int getPriority() {}
返回线程的优先级。

sleep

public static native void sleep(long millis) throws InterruptedException;
让当前线程休眠(暂停执行)指定的毫秒数。静态方法,通过Thread来调用

设置和获取线程的优先级并测试执行的优先级

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占用权的线程最后执行完。

查看其中一个线程暂停后的执行结果

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的时间片。一旦抢到,该线程肯定会被执行完。
应用于当一个线程需要调用另一个线程的执行结果,而另一个线程并未执行完毕,这时就可以调用此方法,以便获取执行的结果。

主线程获取另外一子线程的执行结果

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为用户线程。

线程的分类


用户线程
守护线程

区别

线程的执行是否要结束的方式

⑴ 用户线程有2种方式:
    ① 正常结束(任务执行完毕)
    ② 被中断

⑵ 守护线程只有1中方式:
    用户线程执行结束
    注意:如果用户线程已经执行结束,这时不管守护线程有没有结束,其都要结束。

JDK中典型的守护线程

    垃圾回收机制

守护线程随着主线程的结束而结束

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方法。但是不能真正的停止。
⑶ 采用通知的方式。该方式可确保线程以安全的方式结束运行,建议使用。

通知方式的实现步骤

⑴ 在线程类中添加一个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中引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每一个对象都对应一个可以称为“互斥锁”的标记。这个标记用来保证在任一时刻,只能有一个线程对象访问该对象。

synchronized关键字

用synchronized关键字来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问。

同步代码块

    synchronized(对象) { // 对象就是“锁”
        // 需要被同步的代码
    }

同步方法


    修饰符 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;
        }
      }
       */
    }
  }

}

解决单例模式(懒汉式)的线程同步问题

旧的代码【线程同步问题】

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;
  }
}

可以看到:之前的代码,如果在多个线程中运行,会发现创建出的对象不是唯一的(打印出了不同的地址号)。

修改后的单例模式【解决线程同步问题】

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的地方】继续执行。

notify

public final native void notify();
唤醒某个同步代码块或同步方法中的被wait的一个线程。该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。

注意:
    ⑴ 该方法在Object类中,即每个对象类都有。
    ⑵ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。
    ⑶ 只能唤醒同一个锁对象上其他等待的线程,而且只能随机唤醒一个。

notifyAll

public final native void notifyAll();
唤醒某个同步代码块或同步方法中的被wait的所有线程。唤醒的顺序是随机的。该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。

注意:
    ⑴ 该方法在Object类中,即每个对象类都有。
    ⑵ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。

使用示例

两个线程交错打印1-100内的数

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;
            }
        }
     }
  }
}

生产者和消费者

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();
          }
      }
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值