多线程基础常用方法介绍

Posted 烟锁迷城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程基础常用方法介绍相关的知识,希望对你有一定的参考价值。

1、Thread.join

join()函数的作用是保证线程执行结果的可见性,即保证线程A的结果对线程B可见。
在示例代码中,线程thread的结果对主线程可见,是因为thread让主线程阻塞,直到thread线程执行完毕才唤醒主线程,这样就让thread的结果对主线程可见。

private static int a = 1;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            a = 100;
        });
        thread.start();
        thread.join();
        System.out.println("a:"+a);
    }

join函数源码可以看到,join有一个对象级别的锁保证单个线程的执行顺序,然后又调用了wait方法,这里的wait方法是让当前线程进入等待状态,直到线程不再存活。
也就是说,主函数线程启动子线程后,子线程调用join方法,主函数线程进入等待状态,子线程执行完毕,主线程继续执行,这样主线程才能获取到子线程内执行完成的数据结果,这样多线程就变为串行执行。

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

在join源码中没有与wait对应的notify方法,因为代码在JVM中,子线程结束后会调用notifyall,唤醒等待的主线程。

2、Thread.sleep

sleep()函数使线程暂停执行一段时间,直到等待的时间结束才恢复执行,或者在这段时间内被中断,它的执行步骤如下:

  1. 挂起线程并修改其运行状态
  2. 用sleep()提供的参数来设置一个定时器
  3. 当时间结束,定时器会触发,内核受到中断后修改线程的运行状态。
public class ThreadDemo extends Thread{

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread show");
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
        System.out.println("main show");
    }
}

在操作系统中,CPU竞争有很多种策略,Unix系统使用时间片算法,如果时间片到了,当前线程就会被挂起,交给其他线程执行。windows属于抢占式算法,如果一个线程抢占到CPU,就会一直使用直到执行结束。
因此对于sleep()函数的等待时间而言,就算是时间到了,也不会准确的唤醒该线程,因为它可能正在等待其他线程放开资源,或是和其他线程一起抢占资源,它的优先级并不会提高,也就是说,sleep(1000)不会真的等待1秒之后唤醒,而是表示1秒之后该线程加入资源抢占,就像sleep(0)的作用是让线程重新参与资源抢占,本身是具有意义的。

3、wait与notify

两个线程想要互相感知对方变化,就需要使用wait线程等待,notify线程唤醒。
最典型的案例,就是生产者与消费者模式

public class Consumer implements Runnable{

    private Queue<String> msg;
    private int maxSize;

    public Consumer(Queue<String> msg, int maxSize) {
        this.msg = msg;
        this.maxSize = maxSize;
    }
    
    @Override
    public void run() {
        while (true){
            synchronized (msg){
                while (msg.isEmpty()){
                    System.out.println("消费者停止消费");
                    try {
                        msg.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("消费产品:"+msg.remove());;
                msg.notify();
            }
        }
    }
}
public class Producter implements Runnable{

    private Queue<String> msg;
    private int maxSize;

    public Producter(Queue<String> msg, int maxSize) {
        this.msg = msg;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        int i = 0;
        while (true){
            synchronized (msg){
                i++;
                while (msg.size() == maxSize){
                    System.out.println("生产者停止生产");
                    try {
                        msg.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("生产产品:"+"产品"+i);
                msg.add("产品"+i);
                msg.notify();
            }
        }
    }
}

从刚刚的案例来看,wait/notify本质上是一种条件竞争,也就是说,wait与notify是互斥存在的,所以需要增加synchronized来实现互斥。
wait和notify是用于实现多线程之间的通信,而通信就需要一个通信载体,synchronized就是一个载体,它们需要在同一个锁的范围内。

4、Thread.interrupted和Thread.interrupt

4.1、线程终止

使用函数stop()可以将线程强制终止。

public class StopDemo extends Thread{

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new StopDemo();
        System.out.println("main show");
        thread.stop();
    }
}

使用stop()终止线程是有风险的。在java程序本身没有线程,它需要借助jvm调用操作系统的线程,所以java程序强制终止线程时,实际上终止的是操作系统的线程,这会带来未知的风险,因此终止线程还是建议使用中断标志。

4.2、中断标志

使用线程的中断标志,可以安全的中断线程。但实际上中断标志是逻辑中断,并不会真的终止线程,所以当线程处于类似死循环的状态时,中断标志无法中断线程。

public class Demo implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("processor stop");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Demo());
        System.out.println("main show");
        thread.start();
        thread.interrupt();
    }
}

中断标志Thread.currentThread().isInterrupted()的默认值是false,当执行thread.isInterrupted()方法后,中断标志变为true,这样就可以从线程外对线程逻辑进行控制。

4.3、线程复位

对于已经执行interrupt()方法的线程,可以手动复位,使用静态方法Thread.interrupted();让标志位恢复为false。
假如在上一个例子中的主程序增加一行代码,sleep(1000),会发生什么呢?

public class Demo implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("processor stop");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Demo());
        System.out.println("main show");
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

执行结果为:
在这里插入图片描述
从执行结果中可以发现,线程没有被中断,而是在抛出异常后继续执行,这意味着,线程的中断标志被恢复了,线程继续运行。
发生这个结果的原因是线程内部sleep(2000)这个语句开始生效,因为之前的中断标志在线程还未进入TIME_WAITING状态就已经被结束了,在设置延时1秒中断之后,线程成功进入超时等待状态,此刻线程中断标志变更,正在等待的线程被唤醒,抛出异常,但是没有任何响应,所以线程被复位,中断标志恢复成false,线程继续执行。
假如在异常处理中,再次执行线程中断呢?

public class Demo implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("processor stop");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Demo());
        System.out.println("main show");
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

执行结果为:
在这里插入图片描述
此时可以看到,线程被成功中断。
这里推测,在interrupt()方法中,会做两件事情

  1. 将一个共享变量设置为true
  2. 将进入等待或超市等待状态下的线程唤醒

可以发现在所有的等待或是超时等待函数中,都要求必须被捕捉异常,这就是因为线程进入等待状态后用中断标志进行线程中断时,需要进行对应的执行操作,线程可以选择是继续执行,还是就此中断,这就将线程的执行权力交给当前线程而不是外部操作,这样的执行方式更加友好。

以上是关于多线程基础常用方法介绍的主要内容,如果未能解决你的问题,请参考以下文章

初识多线程之基础知识与常用方法

多线程更新UI的常用方法

Java多线程中joinyieldsleep方法详解

JAVA SE基础篇54.多线程介绍和创建

多线程常用基础

多线程代码讲解