5.线程的八大核心基础知识之Thread和Object类中的重要方法详解

Posted zhihaospace

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5.线程的八大核心基础知识之Thread和Object类中的重要方法详解相关的知识,希望对你有一定的参考价值。

 一.概述

技术图片

 

 

技术图片

二.方法概览

技术图片

 

 

 三.wait,notify,notifyAll方法详解

1.作用和用法:阻塞阶段、唤醒阶段、遇到中断

  • wait作用是释放锁,当前线程进入等待,

  • notify和notifyAll作用是通知等待线程可以执行

  • wait,notify,notifyAll都必须放到同步代码块中

(1)wait和notify基本用法展示:

  • 首先thread1线程拿到object对象锁住object后执行进入到wait方法后释放了锁,进入了等待状态

  • 然后thread2线程拿到object对象锁住object后执行notify通知等待的线程可以运行了,然后继续执行run方法到结束

  • 最后thread1拿到锁继续执行run方法到结束

/**
 * 展示wait和notify的基本用法:1.研究代码执行顺序 2.证明wait释放锁
 */
public class Wait {

    public static Object object = new Object();

    static class Thread1 extends Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(Thread.currentThread().getName() + "开始执行了");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "获取到了锁");
            }
        }
    }

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

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();

    }
}

技术图片

 

 

 (2)notify和notifyAll用法展示

  • notifyAll所有被resourceA所阻塞的对象都被通知可以继续执行

  • notify则只通知一个被resourceA所阻塞的的对象,所以其他对象依旧不能继续执行

/**
 * notify和notifyAll区别以及start先执行不代表线程先启动
 */
public class WaitNotfiyAll implements Runnable {

    private static final Object resourceA = new Object();

    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName() + "获得锁");
            try {
                System.out.println(Thread.currentThread().getName() + "释放了锁");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName() + "运行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void useNotify() throws InterruptedException {
        WaitNotfiyAll waitNotfiyAll = new WaitNotfiyAll();
        Thread thread1 = new Thread(waitNotfiyAll);
        Thread thread2 = new Thread(waitNotfiyAll);
        Thread thread3 = new Thread(()->{
            synchronized (resourceA){
                resourceA.notify();
                System.out.println(Thread.currentThread().getName()+"执行了notify方法");
            }
        });

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

    public static void useNotifyAll() throws InterruptedException {
        WaitNotfiyAll waitNotfiyAll = new WaitNotfiyAll();
        Thread thread1 = new Thread(waitNotfiyAll);
        Thread thread2 = new Thread(waitNotfiyAll);
        Thread thread3 = new Thread(()->{
            synchronized (resourceA){
                resourceA.notifyAll();
                System.out.println(Thread.currentThread().getName()+"执行了notifyAll方法");
            }
        });

        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        thread3.start();
    }
    public static void main(String[] args) throws InterruptedException {
//        useNotify();
        useNotifyAll();

    }
}

技术图片

 

 

  •  下面是执行:useNotify方法的结果:只有一个执行,其他线程继续wait

技术图片

 

 

 (3)wait方法只释放当前调用者的锁

  • thread1线程先拿到对象A,B的锁之后释放了A锁,进而thread2拿到了A锁准备拿B锁,但thread1等待thread2用完A锁再拿到A锁运行完成,形成了死锁

 

public class WaitNotifyReleaseOwnMonitor {

    private static volatile Object resourceA = new Object();
    private static volatile Object resourceB = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(()->{

            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对象的锁");
                    System.out.println(Thread.currentThread().getName()+"释放resourceA对象的锁");
                    try {
                        resourceA.wait();
                        System.out.println(Thread.currentThread().getName()+"运行结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread2 = new Thread(()->{

            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对象的锁");

                }
            }
        });

        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

技术图片

 

 

 2.总结

(1)wait,notify,notifyAll的特点和性质

  • 首先必须获取到对象的锁

  • notify只能唤醒一个等待的线程,哪个线程被唤醒不是由我们决定

  • 三个方法都是Object类的

  • JDK分装了类似功能的Condition

  • 对于持有多个锁的情况注意释放顺序防止死锁发生

2.手写生产者和消费者设计模式

(1)为什么要使用生产者和消费者模式

技术图片

 

 

 

import java.util.Date;
import java.util.LinkedList;

/**
 * 用wait/notify实现生产者和消费者模式
 */
public class ProducerConsumerModel {

    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();

        Producer producer = new Producer(eventStorage);
        Consumer consumer = new Consumer(eventStorage);

        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

class Producer implements Runnable {

    private EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

class Consumer implements Runnable {

    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

class EventStorage {
    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        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 take() {
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("获取到" + storage.poll() + ",当前剩余" + storage.size());
        notify();
    }
}

技术图片

6.常见面试问题

(1)使用两个线程轮流打印0~100的奇偶数

/**
 * 使用两个线程交替打印0~100的奇偶数
 */
public class WaitNotifyPrintOddEvenSyn {

    private static int count = 0;
    private static final Object lock = new Object();

    static class TurningRunner implements Runnable {

        @Override
        public void run() {
            while (count <= 100) {
                synchronized (lock) {

                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    //将其他等待线程唤醒
                    lock.notify();

                    if (count <= 100) {
                        try {
                            //本线程休眠
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        new Thread(new TurningRunner(), "偶数").start();
        Thread.sleep(10);
        new Thread(new TurningRunner(), "奇数").start();

    }
}

(2)手写生产者和消费者模式(上述已做过)

(3)为什么wait()需要在同步代码块内使用,而sleep()不需要

  • 由于wait()需要和其他线程交互的可能会死锁所以放在同步代码块中,而sleep()只与本线程有关与其他线程没有交互所以不需要同步代码块

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

  • 由于锁是基于对象的,每一个对象都有锁 并且Thread中可以控制多个对象的锁增加了控制的灵活性,而sleep是基于当前线程的控制线程的休眠与对象无关

(5)wait方法是属于Object对象的,那调用Thread.wait会怎么样呢?

  • 线程也是对象所以也具有wait方法

  • JVM源码中展示了在当前线程退出时会执行当前线程的notify方法,这会打乱我们的操作流程,Thread类不是作为我们的锁对象。一般我们不要使用Thread.wait,而是建立其他对象使用wait方法

(6)notify和notifyAll区别(上述已表述)

(7)notifyAll之后所有的线程再次抢夺锁,如果某线程抢夺失败会怎么办?

  • 会等待锁的持有者释放锁,再去抢夺锁

7.Java相关概念

  • JavaSE,JavaEE,JavaME:标准版,企业版和移动版,已经不常见了现在只要说到Java都是SE

  • JRE,JDK,JVM关系:

    • JRE:Java运行环境

    • JDK:Java开发工具包,包含了JRE

    • JVM:Java虚拟机,是JRE的一部分

  • Java版本升级都包含了哪些东西升级

    • Java中类的升级和JVM的升级

  • Java8,Java1.8和JDK8是什么关系,是一个东西吗?

    • 是一个东西

四.sleep方法详解

1.作用

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

2.sleep方法不释放锁

  • 与wait方法不同,wait会释放锁资源,sleep不会释放synchronized和lock锁

(1)sleep方法不释放synchronized锁

public class SleepDontReleaseMonitor implements Runnable {
    @Override
    public void run() {
        syn();
    }

    private synchronized void syn(){
        System.out.println(Thread.currentThread().getName() + "获得锁");

        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "睡眠3s");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放锁并退出");

    }

    public static void main(String[] args) {
        SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();

        new Thread(sleepDontReleaseMonitor).start();
        new Thread(sleepDontReleaseMonitor).start();

    }
}

技术图片

 

 

(2)sleep方法不释放lock锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SleepDontReleaseLock implements Runnable {

    private static final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        //上锁
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获得锁");

        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+"睡眠3s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+"释放锁");
            //解锁
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();

        new Thread(sleepDontReleaseLock).start();
        new Thread(sleepDontReleaseLock).start();
    }
}

技术图片

 3.sleep方法响应中断

(1)作用:

  • 抛出InterruptedException

  • 清除中断状态

(2)第二种写法(推荐)

  • 不使用Thread.sleep(),而是使用TimeUnit类

  • TimeUnit类对于传入负数不会抛异常而是忽略,Thread.sleep()对于负数会抛出异常

  • TimeUnit类还可以控制传入时分秒等

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SleepInterrupted implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println(new Date());
            try {
                TimeUnit.HOURS.sleep(0);
                TimeUnit.MINUTES.sleep(0);
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("主线程向子线程传递中断信息,子线程中断");
                e.printStackTrace();
                break;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SleepInterrupted sleepInterrupted = new SleepInterrupted();
        Thread thread = new Thread(sleepInterrupted);
        thread.start();
        Thread.sleep(5500);
        thread.interrupt();
    }
}

技术图片

 

 

 TimeUnit类中的sleep方法会判断传递参数,内部依旧是调用Thread.sleep方法

技术图片

4.总结

技术图片

 5.sleep方法的常见面试问题

(1)wait/notify与sleep方法的异同点

  • 相同:

    • 都会阻塞且都能响应中断

  • 不同:

    • wait/notify需要在同步方法中,sleep不需要

    • wait/notify会释放锁,sleep不会

    • wait在不设置时间后会一直等待通知,sleep会根据传入的时间休眠之后继续运行

    • wait/notify属于Object类,sleep属于Thread类

五.join方法详解

1.join的作用和用法

(1)作用:因为新的线程加入我们,所以我们要等新的线程执行完再出发(main等待thread1执行完毕)

(2)普通用法

  • 子线程加入主线程,主线程等待子线程执行完毕之后,主线程再执行

public class Join {

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        });

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        });

        thread1.start();
        thread2.start();
        System.out.println("子线程开始执行");
        thread1.join();
        thread2.join();
        System.out.println("主线程执行完毕");
    }
}

技术图片

 

(3)join期间被中断效果

  • 子线程调用join方法加入主线程,主线程再等待子线程运行完毕时被打断则主线程捕获异常中断,而子线程会继续运行。这是不合理的,所以我们也要将中断传入子线程,将子线程中断

public class JoinInterrupt {

    public static void main(String[] args) throws InterruptedException {

        Thread mainThread = Thread.currentThread();

        Thread thread = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "正在运行");
            try {
                //子线程对主线程执行中断
                mainThread.interrupt();
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "执行完毕");

            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "中断");
                System.out.println(Thread.currentThread().getName() + "运行完毕");

            }
        }, "thread子线程");

        thread.start();
        System.out.println("子线程加入主线程,主线程等待子线程运行完毕");
        try {
            //主线程在等待子线程过程中遇到中断异常
            thread.join();
        } catch (InterruptedException e) {
            //主线程遇到中断对子线程 也执行中断
            System.out.println(Thread.currentThread().getName() + "线程被中断了");
            thread.interrupt();
        }

        System.out.println(Thread.currentThread().getName() + "线程运行完毕");

    }
}

技术图片

 

 (4)查看子线程join期间主线程的状态:Waiting状态

public class JoinThreadState {

    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();

        Thread thread = new Thread(() -> {

            try {
                Thread.sleep(3000);
                System.out.println(mainThread.getName() + "线程运行状态:" + mainThread.getState());
                System.out.println(Thread.currentThread().getName() + "运行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread子线程");

        thread.start();
        System.out.println("等待子线程运行完毕");
        thread.join();
        System.out.println("主线程运行结束");
    }
}

技术图片

 

2.join注意点

(1)CountDownLatch或CyclicBarrier工具类有与join相同的功能

3.join源码分析

(1)join方法中在同步代码块中执行了wait方法一直等待,此处是让主线程一直等待,知道子线程运行完释放所有资源,就会唤醒主线程

技术图片

 

  • 从JDK源码中看到线程在run方法运行结束退出时会唤醒所有等待该线程的线程

技术图片

 

 (2)实现与join方法相同功能的代码

public class JoinPrinciple {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"thread子线程");

        thread.start();
        System.out.println("等待子线程运行完毕");
//        thread.join();
        synchronized (thread){
            thread.wait();
        }
        System.out.println("所有线程都运行完毕");

    }
}

六.yield方法详解

  • 作用:释放调用者的CPU时间片,但不处在Blocked或Waiting状态,还是Runnable状态一直都可以竞争CPU资源

  • 定位:JVM不保证遵循

  • yield和sleep区别:是否随时可能再次被调度,yield可随时调度,sleep不可以

  • 开发中yield方法很少用到

 

以上是关于5.线程的八大核心基础知识之Thread和Object类中的重要方法详解的主要内容,如果未能解决你的问题,请参考以下文章

2.线程的八大核心基础知识之启动线程的正确和错误方式

3.线程的八大核心基础知识之如何正确停止线程

7.线程的八大核心基础知识之未捕获异常如何处理

线程八大基础核心二(启动线程)

线程八大基础核心六(线程属性)

线程八大基础核心四(线程生命周期)