跳至主要內容

10、多线程

T4makojava基础语法javaos大约 17 分钟

1、程序、线程、进程等概念

  • 程序:静态代码,软件执行的蓝本
  • 进程:程序的一次动态执行过程,包括进程的产生、发展、消亡
  • 线程:比进程更小的执行单位

一个进程可以有产生多个线程,每个进程有一段「专用」的内存区域,线程间可以共享 「相同」的内存单元

每个 Java 程序都有一个默认的「主线程」 main()
当 JVM 加载代码,发现 main() 后,会启动一个主线程,在 main 方法中可以创建其他线程。
如果 main 方法没有创建其他线程,那么 main 方法执行结束后,JVM 就结束了 java 程序
如果创建了其他线程,JVM 就要在主线程和其他线程之间轮流切换,即使 main 方法结束,JVM 也不会结束 java 程序,JVM 一直到程序中的所有线程都结束才结束 java 程序

在 Java 中,至少有三个线程

  • main() 主线程
  • gc() 垃圾回收线程
  • 异常处理线程

并行与并发:

  • 并行:在一个时间点,多个 CPU 同时执行多个任务
  • 并发:在一段时间内,一个 CPU 执行多个任务

多线程程序优点:

  • 提高应用程序的响应,增强用户体验。
  • 提高计算机资源利用率

进程可细化为多个线程

  • 每个线程,拥有自己独立的栈,程序计数器(PC)
  • 多个线程,共享一个进程中的结构:方法区,堆

线程生命周期

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

Tread 提供 getStatue() 方法返回枚举类型,Thread.Statue 的值:

  • 新建状态(NEW)
    • 当一个 Thread 类或其子类的对象被创建时,新生的线程处于 「NEW」 状态。此时它已经占有了相应的内存空间和其他资源,但并没有被 JVM 管理,即尚未启动(没有调用 start() 方法)
  • 可运行状态(RUNNABLE)
    • 线程调用 start() 方法后进入「RUNNABLE」状态,称为可运行状态,JVM 管理该线程,当 JVM 将 CPU 使用权切换给该状态的线程时,如果线程是 Thread 子类创建的,则调用 run() 方法
  • 中断状态(BLOCKED、TIMED_WAITING、WAITING)
    • 当中断的线程重新进入「RUNNABLE」状态后,且 JVM 分配 CPU 使用权给该线程,run() 方法将从中断出继续执行
      • BLOCKED:JVM 将 CPU 资源从当前「RUNNABLE」切换给其他线程时,本线程进入「BOLCKED」状态,「BOLCKED」状态必须等待 JVM 解除其「BOLCKED」再次进入「RUNNABLE」
      • TIMED_WAITING:当线程执行 sleep(int millsecond) 方法后,该线程立刻让出 CPU 使用权,经过至多 millsecond 毫秒后再次进入「RUNNABLE」状态
      • WAITING:当先线程执行 wait() 方法后,进入「WAITING」状态「WAITING」状态不会主动进入「RUNNABLE」状态,必须由其他线程调用 notify() 方法通知它,使其进入「RUNNABLE」状态
  • 死亡状态(TERMINATED)
    • 一个线程执行完了 run() 方法,该线程进入 TERMINATED 状态

相关信息

只有处于 NEW 状态的线程可以调用 start() 方法,处于其他状态的线程都不可调用 start() 方法,否则触发 IllegalThreadStateException 方法

image-20220716151536614

image-20220716152120159

线程的调度与优先级

处于就绪状态的线程首先就绪队列排队等待 CPU 资源

线程在获得 CPU 使用权后,即使没有完成自己的全部操作,java 调度器也会中断当前线程的执行,把 CPU 的使用权切换给下一个排队等待的线程,当前线程等待 CPU 资源的下一次轮回,然后从中断出继续执行

Java 调度器使高优先级的线程始终运行,一旦有空闲时间片,则使有相同优先级的线程顺序使用时间片。

如 A 和 B 线程优先级高于 C 和 D,则先执行 A 和 B,等 A 和 B 进入死亡转台,执行 C 和 D(高优先级的线程要抢占低优先级线程 CPU 的执行权。 但从概率上讲,高优先级的线程高概率情况下被执行,并不意味着强制的先后顺序)

Java 虚拟机的线程调度器负责管理线程,调度器把线程的优先级分为 10 个级别,分别用 Thread 类中的类常量表示,每个 Java 线程优先级为常数 1 - 10,即 Thread.MIN_PRIORITYThread.MAX_PRIORITY 之间。
线程优先级别默认为 5,即 Thread.NORM_PRIORITY

线程优先级可以通过 setPriority(int grade) 方法调整,grade 参数在 1 - 10 之间

2、线程的创建

Thread 子类

Java 中,用 Thread 类或子类创建线程对象
不同的线程可以操作共享的数据

  1. 创建子类
  2. 重写 Threadrun(),此线程执行的操作声明在 run()
  3. 创建 子类对象
  4. 通过此对象调用 start(),启动当前线程并调用当前线程的 run方法

使用 Thread 子类创建线程的优点是可以在子类中增加新的成员变量和方法
但 Java 不支持多继承,Thread 类的子类不能再继承其他的类

//创建子类
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

相关信息

子类对象调用 start() 方法,不能直接调用 run() 方法

Runnable 接口

  1. 创建一个实现了 Runnable 接口 的类
  2. 实现类实现 Runnable 中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到 Thread 类的构造器 Thread(Runnable target) 中,创建 Thread 对象
  5. 通过 Thread 类的对象调用 start()

通过 Thread 类的对象调用 start():

  1. 启动线程
  2. 调用当前线程的 run() --> 调用了 Runnable 类型的 target 对象的 run 方法
class MThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);
        Thread t2 = new Thread(mThread); // t1 与 t2 目标对象相同
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

相关信息

比较创建线程的两种方式
开发中,优先选择实现 Runnable 接口的方式,原因如下:

  • 实现的方式没有类的单继承性的局限性
  • 实现的方式更适合处理多个线程有共享数据的情况

联系:public class Thread implements Runnable
相同点:两种方式都需要重写 run(),将线程要执行的逻辑声明在 run() 中

相关信息

在线程中启动其他线程

在主线程中调用 start 方法后,一旦该线程获取到 CPU 资源,就可以脱离创建它的主线程开始自己的生命周期
实际上也可以在任何一个线程中启动另外一个线程(在 run 方法中调用不同 Thread 的对象的 start 方法)

3、Thread 有关方法

相关信息

关于 run() 方法中的局部变量:
当线程被创建多个时,run() 方法中的局部变量也被分配不同的内存空间。不同线程的 run() 方法中的局部变量互不干扰

thread 的常用方法:

方法名称描述
void start()启动当前线程,调用 run() 方法
run()需要重写,方法体为线程所执行的操作
static Thread currentThread()静态方法,返回执行当前代码的线程
String getName()获取当前线程名
void setName(String name)设置当前线程名
static void yield()线程让步,暂停当前正在执行线程, 把执行机会让给优先级相同或更高的线程,若等待队列中没有,则忽略此方法
join()在线程 A 中调用线程 B 的 join() 方法,线程 A 进入阻塞状态直到线程 B 完全执行完才结束阻塞状态,进入就绪状态(该方法与优先级无关)
static void sleep(long millitime)让当前线程放弃 CPU 资源,让其他线程获得执行机会,millitime 毫秒后重新计入就绪队列,如果休眠时被打断,抛出 InterruptedException
boolean isAlive()判断当前进程是否存活(NEW 和 TERMINATED 状态返回 fasle,其他返回 true)
stop()已过时,强制结束当前进程
interrupt()用于唤醒正在休眠(sleep)的线程,使休眠线程发生 InterruptedException,是线程进入 RUNNABLE 状态,等待 CPU 资源
void setDaemon(boolean on)线程默认是非守护线程(用户线程),通过 thread.setDaemon(true) 设置为守护线程,当程序中的所有用户线程都结束运行,即使守护线程的 run() 方法中还有要执行的语句也立刻结束运行

相关信息

一个正在运行的线程在没有进入死亡状态时,不要再给线程分配实体,线程只能引用最后分配的实体,先前的实体就会成为垃圾,且不会被垃圾收集器回收,因此会继续运行,如:

Thread thread = new Thread(target);
target.start;
thread = new Thread(target) //  thread 指向新实体,之前的实体不被回收,先前线程继续执行
public class ThreadMethodTest {
    public static void main(String[] args) {
        HelloThread thread = new HelloThread("thread-1"); // 创建 Thread 实例并命名
        thread.start();
        Thread.currentThread().setName("main thread"); //给主线程命名
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            if(i == 20){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(thread.isAlive());
    }
}
class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 20 == 0){
                this.yield();
            }
            try {
                sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    public HelloThread(String name){
        super(name);
    }
}

4、线程的同步(线程的安全问题)

线程安全问题:不同线程在同一时间操作同一变量时或出现混乱

在 java 中,通过 同步机制,解决线程安全问题

解决线程安全问题的方法:

  • 同步代码块
  • 同步方法

相关信息

在使用 wait()notify()notifyAll() 方法时,通过同步监视器调用

同步代码块

同步代码块编写方式:

synchronized(同步监视器){
    //需要被同步的代码
}
  • 需要被同步的代码:操作共享数据的代码(不含包含过多或过少代码)
  • 同步监视器(锁):任何一个类的对象,都可以充当锁
  • 多个线程必须要公用同一把锁

相关信息

在实现 Runnable 接口创建多线程的方式中,可以考虑使用 this 充当同步监视器

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window1 implements Runnable{
    private int ticket = 100; // 多个线程操作的变量
    Object object = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (this) {// 此时的 this: 唯一的 Window1 对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":票号" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
    }
}
}
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 window1 = new Window2();
        Window2 window2 = new Window2();
        Window2 window3 = new Window2();
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}

class Window2 extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            //synchronized (obj)
            synchronized (Window2.class) {//window2.class只会加载一次
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":票号" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

同步方法

如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步的

权限修饰符 synchronized 返回值类型 方法名(参数列表){

}

相关信息

关于同步方法:

  • 同步方法仍然涉及到同步监视器
  • 非静态的同步方法,同步监视器是:this
  • 静态的同步方法,同步监视器是:当前类本身

同步的方式,解决了线程的安全问题
操作同步代码时只能有一个线程参与,其他线程等待
相当于是一个单线程的过程

public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w1 = new Window3();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window3 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            show();
        }
    }
    private synchronized void show(){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":票号" + ticket);
            ticket--;
        }
    }
}
public class WindowTest4 {
    public static void main(String[] args) {
        Window4 window1 = new Window4();
        Window4 window2 = new Window4();
        Window4 window3 = new Window4();
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}
class  Window4 extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
           show();
        }
    }
    private static synchronized void show(){// 同步监视器:window4.class
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":票号" + ticket);
            ticket--;
        }
    }
}

线程死锁

image-20220716213252399

  1. 死锁:不同的线程分别占用对方需要的同步资源不放弃
  2. 说明:出现死锁后,不会出现异常与提示,只是所有的线程都处于阻塞状态,无法继续
  3. 要避免出现死锁

5、线程安全的懒汉式单例模式

public class BankTest {
}
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static  Bank getInstance() {
        //方式一:效率稍差
        /*synchronized (Bank.class) {
            if(instance == null){
                instance = new Bank();
            }
            return instance;
        }*/
        //方式二:效率稍高
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

6、lock 锁

image-20220717110119921

解决线程安全问题方式三:Lock 锁 -- JDK5.0 新增

相关信息

synchronized 与 lock 的异同:

  1. 相同点:二者都可以解决线程安全问题
  2. 不同点:
    • synchronized 机制在执行相应的同步代码后,自动的释放同步监视器
    • lock 需要手动的启动同步(lock()),同时也需要手动的结束同步(unlock())

相关信息

解决线程安全问题的方式(优先顺序)

  1. Lock
  2. 同步代码块
  3. 同步方法
public class MyThread extends Thread{

    int ticket = 100;

    static Lock lock = new ReentrantLock(); // 每个线程对象共享一个锁

    @Override
    public void run() {
        while (true) {
            lock.lock(); // 加锁
            {
                try{
                    if (ticket > 0) {
                        Thread.sleep(10);
                        System.out.println(Thread.currentThread().getName() + ":票号" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock(); // 最后一定要释放锁,否则程序会继续运行
                }

            }
        }
    }
}

7、线程的通信(wait、notify、notifyAll)

对于同步方法(synchronized)有时涉及一些特殊情况,如当你在一个售票窗口排队购票时,售票员没有零钱找你,你就必须等待,允许后面的人购票,获得零钱

  • wait():中断同步方法/代码块执行,当前线程进入阻塞状态,释放同步监视器(锁),允许其他线程使用该同步方法/代码块
  • notify():唤醒一个 wait 的线程,如果有多个线程被 wait,就唤醒优先级高的那个
  • notifyAll:一旦执行此方法,就会唤醒所有被 wait 的线程

一般来说,先中断的线程先被唤醒

相关信息

  1. wait()notify()notifyAll() 三个方法必须使用在同步代码块或同步方法中
  2. wait()notify()notifyAll() 三个方法的调用者必须是同步代码块或同步方法中的监视器
    否则会出现IllegalMonitorStateException 异常
  3. wait()notify()notifyAll() 都是 Object 类中的 final 方法,不允许重写
class Number implements Runnable{
    private int num = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                this.notify();
                if (num <= 100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                    try {
                        //使得调用 wait() 方法的线程进入阻塞状态
                        this.wait();// wait() 会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }

        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number n = new Number();
        Thread t1 = new Thread(n);
        Thread t2 = new Thread(n);
        t1.setName("A");
        t2.setName("B");
        t1.start();
        t2.start();
    }
}

相关信息

sleep()wait() 的异同:

  1. 相同点:都可以让当前线程进入阻塞状态
  2. 不同点:
    • 两个方法声明的位置不同:Thread 类中声明 sleep(),Object 类中声明 wait()
    • 调用要求:sleep() 可以在任何需求的场景下调用。wait() 必须使用在同步代码块或同步方法中
    • 如果两个方法都使用在同步代码块或同步方法中,sleep() 不会释放锁,wait() 会释放锁

8、多线程的经典问题

image-20220717150304782

class Clerk{
    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {
        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个");
            notify();
        }else{
            //等待
            try {
                wait();// wait() 会释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费产品
    public synchronized void consumeProduct() {
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个");
            productCount--;
            notify();
        }else {
            //等待
            try {
                wait();// wait() 会释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Producer extends Thread{//生产者
    private Clerk clerk;
    public  Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"生产产品");
        while(true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}
class Customer extends Thread{//消费者
    private Clerk clerk;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"消费产品");
        while(true){
            try {
                Thread.sleep(15);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }
}
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Customer c1 = new Customer(clerk);
        c1.setName("消费者1");

        p1.start();
        c1.start();

    }
}

9、JDK5.0 新增线程创建方式

1、实现 Callable 接口

image-20220717153422970

image-20220717154346597

  1. 创建一个实现 Callable 的实现类
  2. 实现 call 方法,将此线程需要执行的操作声明在 call() 中
  3. 创建 Callable 接口实现类的对象
  4. 将此 Callable 接口实现类的对象作为传递到 FutureTask 的构造器中,创建 FutureTask 的对象
  5. 将 FutureTask 的对象作为参数传递到Thread类的构造器中,创建 Thread 对象,并调用 start()
  6. 获取 Callable 中 call 方法中的返回值
class NumberThread implements Callable{

    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i%2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        NumberThread numberThread = new NumberThread();

        FutureTask futureTask = new FutureTask(numberThread);
        new Thread(futureTask).start();

        try {
            //get() 方法返回值即为 FutureTask 构造器参数 callable 实现类重写的 call() 的返回值
            Object sum = futureTask.get();
            System.out.println("sum:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

相关信息

Callable 比 Runnable 接口创建多线程方式的优点:

  1. call() 方法可以有返回值
  2. call() 方法可以抛出异常,被外面的操作捕获,获取异常信息
  3. Callable 支持泛型

2、线程池

image-20220717164234833

image-20220717164713924

线程池的好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止...
class NumThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //1、提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service; 
        //设置线程池的属性
        //System.out.println(service.getClass());
        service1.setCorePoolSize(15);
        //service1.setKeepAliveTime();
        
        //2、执行指定的线程操作。需要提供实现Runnable接口或Call接口实现类的对象
        service.execute(new NumThread());//适合使用于Runnable
        //service.submit(Callable callable);//适合使用于Callable
        //3、关闭连接池
        service.shutdown();
    }
}

10、多线程练习

100 张票,两个窗口领取,每次领取 30 ms


public class SellTickets {
    public static void main(String[] args) {
        Thread t1 = new MyThread("窗口1");
        Thread t2 = new MyThread("窗口2");
        t1.start();
        t2.start();
    }
}

class MyThread extends Thread{

    public MyThread(String name) {
        super(name);
    }

    static final Object obj = new Object();

    static int tickets = 100;

    @Override
    public void run() {
        while (tickets > 0){
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized(obj){
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + ":sold " + tickets--);
                }
            }
        }
    }
}

100 张票,两个窗口领取,当剩下 10 张时,停止领取

// 1000 张票,两个窗口领取,当剩下 10 张时,停止领取
public class SellTickets {
    public static void main(String[] args) {
        Thread t1 = new MyThread("窗口1");
        Thread t2 = new MyThread("窗口2");
        t1.start();
        t2.start();
    }
}

class MyThread extends Thread{

    public MyThread(String name) {
        super(name);
    }

    static final Object obj = new Object();

    static int tickets = 100;

    @Override
    public void run() {
        while (tickets > 10){
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized(obj){
                if (tickets > 10){
                    System.out.println(Thread.currentThread().getName() + ":sold " + tickets--);
                }else {
                    System.out.println("关门了");
                }
            }
        }
    }
}

使用两个线程交替打印 1-100 中所有奇数

public class SellTickets {
    public static void main(String[] args) {
        Thread t1 = new MyThread("线程 1");
        Thread t2 = new MyThread("线程 2");
        t1.start();
        t2.start();
    }
}

class MyThread extends Thread{

    public MyThread(String name) {
        super(name);
    }

    static final Object obj = new Object();

    static int nums = 1;

    @Override
    public void run() {
        while (nums < 100){
            synchronized(obj){
                if (nums % 2 == 1) {
                    System.out.println(Thread.currentThread().getName() + ":" + nums);
                    nums += 2;
                }
                obj.notifyAll(); // 唤醒 wait 的线程
                try {
                    if (nums < 100){
                        obj.wait(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }
}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.5