Java并发编程—线程基础

    技术2022-07-11  93

    Java并发编程—线程基础

    线程的实现方式

    1)实现Runnable接口

    2)继承Thread类

    3)线程池,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,最终是通过Thread类创建线程的

    4)实现Callable接口,Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回

    5)定时器Timer

    。。。。。。

    实现线程的本质

    Runnable实现线程需要调用start()方法,它的本质也是调用run()方法

    @Override public void run() { if (target != null) { target.run(); } }

    Thread实现线程会重写run方法,run方法里是要执行的任务,最终还是通过start方法调用run方法。

    为什么实现Runnable比继承Thread类要好?
    解耦,runnable定义任务内容,Thread类负责线程启动和属性设置。提高性能,如果需要频繁创建线程,那么新建、销毁线程的性能要比执行线程的大得多。拓展性,java不支持双继承,限制了代码的可拓展性
    如何正确停止一个线程?

    ​ 对于 Java 而言,最正确的停止线程的方式是使用 interrupt,但是interrupt只是起到通知的作用,停不停止由线程自己决定。java本质是想线程之间相互协作,如果贸然停止这样会造成一些不安全的操作。

    while (!Thread.currentThread().islnterrupted() && more work to do) { do more work }

    ​ 源码中可以看到,JDK使用while循环判断线程中断标记位是否被设置,只有以上两个条件都成立才会进行下一步。

    Sleep期间是否能感受中断呢?

    ​ 其实是可以的,通过sleep、wait等方式让线程阻塞进入休眠状态是可以感受中断信号的,并且会抛出InterruptedException异常,同时清除中断信号。

    常见的两种线程中断处理方式
    方法签名抛异常,run() 强制 try/catch。不能将异常捕获后不做操作,你可以选择抛出去,或则根据业务来处理。再次中断。还可以在捕捉到异常后,手动添加中断信号。使用Thread.currentThread().interrupt()函数
    错误的停止方法

    1)stop ,stop会直接中断线程,不安全

    2)suspend/resume ,这两种方式不会释放锁,就开始进入休眠模式,可能造成死锁问题。

    这些方法已经被标记为@Deprecated。

    3)volatile,出于线程休眠的线程是不会感知到被volatile标记的变量的

    线程的6种状态

    1)New(新建),当我们用 new Thread() 新建一个线程时,如果线程没有开始运行 start() 方法,那么它就是新建状态

    2)Runnable(可以运行),调用start方法后进入Runnable状态,此时线程可能运行,也可能等待Cpu资源。

    3)Blocked(被阻塞), 进入synchronized 保护的代码时没有抢到 monitor 锁,会被阻塞。

    4)Waiting(等待),进入Waiting有三种可能性

    没有设置 Timeout 参数的 Object.wait() 方法。

    没有设置 Timeout 参数的 Thread.join() 方法。

    LockSupport.park() 方法

    Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。

    5)Timed Waiting(计时等待)与上面Waiting类似,在于有没有时间限制。以下有几种进入Timed Waiting的方式

    设置了时间参数的 Thread.sleep(long millis) 方法;

    设置了时间参数的 Object.wait(long timeout) 方法;

    设置了时间参数的 Thread.join(long millis) 方法;

    设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。

    6)Terminated(被终止),想要进入Terminated有两种可能。

    run() 方法执行完毕,线程正常退出。

    出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。

    如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。

    wait/notify/notifyAll

    1)wait 必须在 synchronized 保护的同步代码中使用,没有synchronized 保护的代码不能保证原子性。而且wait会释放monitor锁,所以线程需要进入synchronized 获得锁。有时会出现虚假唤醒,所以我们需要while循环判断

    while (condition does not hold) obj.wait();

    2)为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

    因为 Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。因为如果把 wait/notify/notifyAll 方法定义在 Thread 类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时 wait 方法定义在 Thread 类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是哪把锁呢?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

    3 ) wait/notify 和 sleep 方法的异同

    相同点
    它们都可以让线程阻塞。它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。
    不同点
    wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。
    生产者/消费者模式

    1)BlockingQueue 实现生产者消费者模式

    public static void main(String[] args) { BlockingQueue<Object> queue = new ArrayBlockingQueue<>(10); Runnable producer = () -> { while (true) { queue.put(new Object()); } }; new Thread(producer).start(); new Thread(producer).start(); Runnable consumer = () -> { while (true) { queue.take(); } }; new Thread(consumer).start(); new Thread(consumer).start(); } Condition 实现生产者消费者模式 public class MyBlockingQueueForCondition { private Queue queue; private int max = 16; private ReentrantLock lock = new ReentrantLock(); private Condition notEmpty = lock.newCondition(); private Condition notFull = lock.newCondition(); public MyBlockingQueueForCondition(int size) { this.max = size; queue = new LinkedList(); } public void put(Object o) throws InterruptedException { lock.lock(); try { while (queue.size() == max) { notFull.await(); } queue.add(o); notEmpty.signalAll(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (queue.size() == 0) { notEmpty.await(); } Object item = queue.remove(); notFull.signalAll(); return item; } finally { lock.unlock(); } } } wait/notify 实现生产者消费者模式 class MyBlockingQueue { private int maxSize; private LinkedList<Object> storage; public MyBlockingQueue(int size) { this.maxSize = size; storage = new LinkedList<>(); } public synchronized void put() throws InterruptedException { while (storage.size() == maxSize) { wait(); } storage.add(new Object()); notifyAll(); } public synchronized void take() throws InterruptedException { while (storage.size() == 0) { wait(); } System.out.println(storage.remove()); notifyAll(); } }
    Processed: 0.017, SQL: 9