多线程学习(二)——Thread和Object类中的重要方法详解

方法概览

方法名简介
Threadsleep相关sleep的相关重载方法
join等待其他线程执行完毕
yield相关放弃已获得的CPU资源
currentThread获取当前执行线程的引用
start, run相关启动线程相关
interrupt相关中断线程
stop, suspend, resume相关已废弃
Objectwait / notify / notifyAll让线程暂时休息和唤醒

wait / notify / notifyAll 方法

作用

wait 让线程休息,进入阻塞阶段,执行完wait方法会释放monitor锁。

四种被唤醒的情况:
1、另一个线程调用这个对象的 notify() 方法且刚好被唤醒的是本线程;
2、另一个线程调用这个对象的 notifyAll() 方法;
3、过了 wait(long timeout) 规定的超时时间,如果传入的是 0 ,则表示永久等待;
4、线程自身调用了 interrupt()

(遇到中断时,会抛出 InterruptedException 并释放monitor锁)

特点
  • 需要在synchronized 关键字修饰的代码块或方法中执行
  • 执行wait、notify、notifyAll都必须先拥有monitor
  • notify只能唤醒其中一个线程
  • 多个锁的情况下,只会释放调用的当前的锁
  • 都属于Object类
  • 类似功能Condition
代码演示

1、场景:线程1睡了,线程2去唤醒
执行顺序:
在 Thread1 进入同步代码块,执行了 wait() 方法后,线程释放了锁;
此时 Thread2 获得锁执行了 notify() 方法唤醒了 Thread1,执行完 Thread2 同步代码块中的所有语句后,Thread1继续执行 wait() 后的代码

public class WaitNotify {
    public static Object object = new Object();

    static class Thread1 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + " 开始执行");
                try {
                    // 在同步代码块中执行了 wait() 方法后,释放了锁
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 获取到了monitor锁");
            }
        }
    }

    // 唤醒 Thread1
    static class Thread2 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println(Thread.currentThread().getName() + " 调用了notify");
            }
        }
    }

    // 让Thread1先进入wait状态,再被Thread2唤醒
    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        Thread.sleep(200);
        thread2.start();
    }
}

在这里插入图片描述
2、notify与notifyAll的区别
场景:线程1和线程2被阻塞,线程3唤醒它们

notifyAll :
调用notifyAll唤醒全部线程

(start的调用顺序不一定能保证执行顺序,因此线程3需要等待一会儿再start)

public class NotifyAndNotifyAll implements Runnable{
    private static final Object resource = new Object();

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new NotifyAndNotifyAll();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(() -> {
            synchronized (resource) {
                resource.notifyAll();
                System.out.println(Thread.currentThread().getName() + " 看我叫醒这两个猪");
            }
        });

        thread1.start();
        thread2.start();
        Thread.sleep(200);
        thread3.start();
    }

    @Override
    public void run() {
        synchronized (resource) {
            System.out.println(Thread.currentThread().getName() + " 得到了锁");
            try {
                System.out.println(Thread.currentThread().getName() + " 困了,休息会儿");
                resource.wait();
                System.out.println(Thread.currentThread().getName() + " 醒啦,一起来happy");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

在这里插入图片描述
notify:
notify只能唤醒其中一个线程
在这里插入图片描述
在这里插入图片描述
3、证明wait只会释放当前的锁
场景:线程1持有两把锁并释放其中一把,线程2能拿到几把锁

public class ReleaseOwnMonitor {
    private static volatile Object resourceA = new Object();
    private static volatile Object resourceB = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resourceA) {
                System.out.println(Thread.currentThread().getName() + " 得到了resourceA锁");
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread().getName() + " 得到了resourceB锁");
                    try {
                        System.out.println(Thread.currentThread().getName() + " 释放了resourceA锁");
                        resourceA.wait();
                        System.out.println(Thread.currentThread().getName() + " 后续happy");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (resourceA) {
                System.out.println(Thread.currentThread().getName() + " 表示收到了老铁释放的resourceA了");
                System.out.println(Thread.currentThread().getName() + " 心想:那么resourceB还在它手上吗,拿到了我再吱声");
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread().getName() + " 我拿到resourceB啦");
                }
            }
        }).start();
    }
}

根据控制台输出,我们可以看到线程2只拿到了线程1释放的当前的锁A,B没有被释放导致线程2一直在等待锁的过程中
在这里插入图片描述

sleep 方法

作用

只想让线程在预期的时间执行,其他时间不占用CPU资源

特点

与wait不同,执行wait会释放锁,而执行sleep的时候不释放锁(synchronized和lock),等sleep设置的时间到了以后(正常结束)才会释放锁。

代码演示

1、sleep 方法不释放锁

public class SleepDontReleaseMonitor implements Runnable{
    public static void main(String[] args) {
        SleepDontReleaseMonitor target = new SleepDontReleaseMonitor();
        new Thread(target).start();
        new Thread(target).start();

    }
    
    @Override
    public void run() {
        sync();
    }
    private synchronized void sync() {
        System.out.println(Thread.currentThread().getName() + " 获取到了monitor");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 退出了同步代码块");
    }
}
Thread-0 获取到了monitor
Thread-0 退出了同步代码块
Thread-1 获取到了monitor
Thread-1 退出了同步代码块
public class SleepDontReleaseLock implements Runnable{
    private static final Lock LOCK =  new ReentrantLock();

    public static void main(String[] args) {
        SleepDontReleaseLock target = new SleepDontReleaseLock();
        new Thread(target).start();
        new Thread(target).start();
    }
    @Override
    public void run() {
        LOCK.lock();
        System.out.println(Thread.currentThread().getName() + " 获取到了锁");
        try {
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + " 醒了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }
}

Thread-1 获取到了锁
Thread-1 醒了
Thread-0 获取到了锁
Thread-0 醒了

2、sleep 方法响应中断
线程中断会抛出 InterruptedException,随即会清楚中断状态

public class SleepInterrupted implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepInterrupted());
        thread.start();
        Thread.sleep(8000);
        thread.interrupt();
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                System.out.println("遇到了中断");
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

TimeUnit

上面的代码中,我们使用了 TimeUnit.SECONDS.sleep(long) 进行休眠;
它帮我们进行了时间的单位换算
在这里插入图片描述
而它的 sleep 方法本质也是 Thread.sleep
只不过当它的超时时间 < 0时,不作操作,而 Thread.sleep 的设置的时间如果 < 0,则会 throw new IllegalArgumentException("timeout value is negative");
在这里插入图片描述

join 方法

作用

新的线程加入,需要等待它执行完毕再出发,例如main线程等待子线程thread1执行完毕。

代码演示

1、join 方法普通用法

如果没有调用 join 方法,两条输出语句如厕和上完出来是紧接着输出的;
而调用了 join 方法,main线程就会等待它们执行完毕再执行

public class Join {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 我花了 2 秒上完了厕所");
        });

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 我花了 3 秒上完了厕所");
        });
        thread1.start();
        thread2.start();
        System.out.println(Thread.currentThread().getName() + " 小伙伴要如厕,我在门口开始等候它们");
        thread1.join();
        thread2.join();
        System.out.println(Thread.currentThread().getName() + " 它们终于上完出来啦");
    }
}
main 小伙伴要如厕,我在门口开始等候它们
Thread-0 我花了 2 秒上完了厕所
Thread-1 我花了 3 秒上完了厕所
main 它们终于上完出来啦

2、遇到中断

当主线程中断时,子线程也需要中断

public class JoinInterrupted {
    public static void main(String[] args) {
        Thread mianThread = Thread.currentThread();
        Thread thread1 = new Thread(() -> {
            try {
                mianThread.interrupt();
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + " 我执行完毕了");
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 我也中断了");

            }
        });
        thread1.start();
        System.out.println(Thread.currentThread().getName() + " 等待子线程搞完");
        try {
            // 主线程加入 Thread1
            thread1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " 被中断了");
            // 将主线程的中断传递给子线程,不然会不一致
            thread1.interrupt();
        }
        System.out.println(Thread.currentThread().getName() + " 子线程执行完毕了");
    }
}
main 等待子线程搞完
main 被中断了
main 子线程执行完毕了
Thread-0 我也中断了

3、join期间,线程是什么状态 : Waiting

public class JoinState {
    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " 我想知道此时主线程的状态 : " + mainThread.getState());
                System.out.println(Thread.currentThread().getName() + " 运行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        System.out.println(Thread.currentThread().getName() + " 等待子线程运行完毕");
        thread.join();
        System.out.println(Thread.currentThread().getName() + " 子线程运行完毕");
    }
}
main 等待子线程运行完毕
Thread-0 我想知道此时主线程的状态 : WAITING
Thread-0 运行结束
main 子线程运行完毕
源码

join 内部调用了 wait() 方法,wait(0) 表示一直处于休眠直至被唤醒。
然而我们并没有看到唤醒的方法,这是因为Thread类run方法执行完毕后,会自动唤醒。
底层c++代码中在线程退出后,会执行 lock.notify_all(thread);唤醒

void JavaThread::exit(booldestory_vm, ExitTypeexit_type);
static void ensure_join(JavaThread*thread) {
	Handle threadObj(thread, thread -> threadObj());
	ObjectLocker lock(threadObj, thread);
	thread -> clear_pending_exception();
	java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
	java_lang_Thread::set_thread(threadObj(), NULL);
	lock.notify_all(thread);
	thread -> clear_pending_exception();
}

join 源代码

    public final void join() throws InterruptedException {
        join(0);
    }
    
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

CountDownLatch / CyclicBarrier 是 join 的等价类 (感兴趣的小伙伴可以参考另外资料了解)

yeild 方法

作用

释放自己的CPU时间片,但不会释放锁,也不会陷入阻塞,线程状态依然是Runnable状态

与sleep的区别,yield只是暂时把CPU调度权让给别的线程,立刻能处于竞争状态被再次调度;sleep的话会被认为已经阻塞了

问题:

1、为什么线程通信的方法 wait(), notify() 和 notifyAll()被定义在Object类中?而sleep()定义在Thread类?

因为 wait(), notify() 和 notifyAll()是锁级别操作,而锁是属于对象的,每一个对象的对象头中都包含几位用于保存当前锁的状态的预留,因此锁是绑定于某一个对象,而不是线程。

2、wait/notify与sleep的异同点?

相同:都会使线程进入阻塞状态,可以响应中断
不同:wait/notify必须在同步方法中执行,防止死锁和永久等待,而sleep不需要;wait释放锁,sleep不释放锁;
sleep必须指定参数,wait可不传参;所属的类不同,前者属于Object类,后者属于Thread类

3、使用 wait-notify 实现生产者消费者设计模式

生产者往队列中存放数据,如果队列满了会阻塞;消费者从队列获取数据,如果队列空了也会阻塞

如果队列中有了数据,生产者会通知消费者去获取数据;同样如果队列中数据不满,消费者会通知生产者生产数据
在这里插入图片描述

public class ConsumerProducer {
    public static void main(String[] args) {
        DataStorage storage = new DataStorage();
        Producer producer = new Producer(storage);
        Consumer consumer = new Consumer(storage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }

}
class DataStorage {
    private int maxSize;
    private LinkedList<Date> storage;

    // 初始化存储队列
    public DataStorage() {
        this.maxSize = 10;
        this.storage = new LinkedList<>();
    }

    // 生产数据
    public synchronized void put() {
        // 如果队列中已满,则进入等待状态
        while (storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("生产者生产数据了,仓库中有 " + storage.size() + " 条数据");
        // 通知消费者
        notify();
    }

    // 消费数据
    public synchronized void get() {
        // 同理,如果队列中没有数据,则进入等待数据的状态
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 获取并删除队列中数据
        System.out.println("消费者取到 " + storage.poll() + " ; 仓库还剩 " + storage.size() + " 条数据");
        // 消费后必然会有空闲容量,通知生产者
        notify();
    }
}

在这里插入图片描述

4、两个线程交替打印 0~100 的奇偶数

仅通过加锁synchronized,分别判断奇、偶性进行输出。
该方法虽然能得到正确的输出,但是效率很低。两个线程同时在竞争锁,如果同一个线程一直抢到锁,另外个线程就会一直等待无法接着输出,这会经历多余的 while 循环(只不过不符合输出要求不会打印罢了)。

这时,我们就想到,通过 wait - notify 让它们轮流能拿到锁进行输出:

public class PrintOddEvenAlternately {
    private static Object lock = new Object();
    private static int num;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new AlternateRunner(), "OddThread").start();
        Thread.sleep(100);
        new Thread(new AlternateRunner(), "EvenThread").start();
    }

    private static class AlternateRunner implements Runnable {
        @Override
        public void run() {
            while (num <= 100) {
                synchronized (lock) {
                    // int类型变量初始化值为 0,第一次会是偶数线程打印 0
                    System.out.println(Thread.currentThread().getName() + " : " + num++);
                    // 偶(奇)线程在打印一次后,唤醒对方执行打印
                    lock.notify();
                    // 这里必须再进行一次判断再进入wait
                    // 否则如果直接进入休眠,而正好100输出后通过num++变成了101,另外个线程就无法进入while循环进行唤醒
                    // 那么线程将一直处于等待状态,程序无法停止运行
                    if (num <= 100) {
                        try {
                            // 进入等待状态,释放锁给对方
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值