Thread的几种常用方法

线程的创建

1)继承Thread类创建线程

通过继承Thread类来创建并启动多线程的一般步骤如下

1】d定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

2】创建Thread子类的实例,也就是创建了线程对象

3】启动线程,即调用线程的start()方法
 

2)实现Runnable接口创建线程

通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】第三部依然是通过调用线程对象的start()方法来启动线程
 

3)使用Callable和Future创建线程

1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
 

4)使用线程池例如用Executor框架

1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象。

    Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

    Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

    ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

线程中断

线程中断就是处于运行状态的线程被强制打断。线程中断总体来说有三种方法:正常退出、stop暴力停止、interrupt异常停止。其中使用stop()方法是不安全的,可能导致数据不同步或者资源无法回收,目前stop()方法已经被标注为作废方法。一般使用interrupt停止线程,这里有几个与之相关的方法:

public void interrupt() {} // 中断线程
 
public boolean isInterrupted(){} // 查看线程中断状态

打印的线程状态有所变化,但是线程好像又没有中断,这是为什么呢?实际上,interrupt并不能真正的停止线程,只是更换了线程状态标志。在Core Java中有这样一句话:"没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 "。这句话启发了我们如何正确的中断一个线程,中断一个线程的本质就是中断run()方法的执行,也就是说run()不再执行后,这个线程就结束了。所以,通过中断状态标志位来控制run()方法中逻辑代码的运行,就可以很好的保证线程的中断。

class MyThread7 extends Thread {
 
	@Override
	public void run() {
		try {
			Thread.sleep(20000); // 子线程睡眠
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
	}
}
 
public class Interrupt {
 
	public static void main(String[] args) throws InterruptedException {
 
		MyThread7 thread = new MyThread7();
 
		thread.start();
		thread.interrupt(); // 由主线程中断了一个子线程
	}
}


补充:interrupted()方法,作用是测试当前线程是否已经中断,线程的中断状态也是由该方法清除。主线程被打断,使用interrupted()方法查看中断状态,发现标志位改为了true;再次使用interrupted()查看中断状态,发现标志位改为false。在第一次使用interrupted()方法时,它返回标志位,同时清除标志位(置为false),导致下一次的返回值变为false。所以interrupted()方法有清除标志位的功能。
 

线程等待

线程通知与等待wait()/notify()/notifyAll()
Object类是所有类的父类,鉴于继承机制,Java把所有类都需要的方法放在了Object中,现在要用到的通知与等待系列函数也在其中。

wait()函数
当一个线程调用一个**共享变量的wait()**方法时,该调用线程会被阻塞挂起。

wait()方法是属于共享变量的!!!

线程想要申请一个共享变量,则需要等待共享变量空闲或者满足某种程序员需要的条件才可以申请到。如果共享变量是非空闲的状态或不满足条件,那么需要给共享变量加监视锁(监视到什么时候空闲或满足某种条件)并释放自己在当前变量上的其他锁以便别的线程使用(在其他变量上的锁是不会被释放的),等待其他占用线程释放该变量并唤醒该锁。

挂起线程什么时候被取下来开始返回进入运行状态呢?满足如下条件之一即可:

其他线程调用了该共享对象的notify()/notifyAll()方法唤醒。
其他线程调用了该线程的interrupt()方法中断,该线程抛出InterruptedException异常返回(需要进行异常处理)。
等待超时
:如果调用wait()之前没有获取该对象的监视器锁,就会抛出IllegalMonitorStateException异常。
获取监视器锁
synchronized同步代码块
使用该共享变量作为参数:

共享变量方法,方法使用synchronized修饰
synchronized void add(int a, int b) {
    //do something
}
虚假唤醒
在不满足上述三个唤醒条件的情况下,也可能会从挂起变为运行状态,这就是所谓的虚假唤醒。为了避免这种情况发生,应该不停测试线程被唤醒的条件是否满足,不满足就继续等待(while)。

synchronized (obj) {
    while(条件不满足) {
        obj.wait();
    }
}
举例:消费者&生产者
唤醒函数
notify()函数
一个线程在调用共享对象的notify()方法后,会唤醒一个在共享变量上wait等待的线程,如果有多个线程在等待,具体唤醒哪一个则是随机的。

被唤醒线程不会马上返回,必须在获取了共享对象的监视器锁才能返回(唤醒他的线程释放了他的监视器锁之后,但被唤线程不一定能获得此锁,得竞争)。

同时,只有当前进程获取到监视器锁才能调用notify()方法,否则会抛异常。

notifyAll()函数
唤醒共享变量上的所有wait挂起的线程。

等待线程执行终止join
有些场景需要等待某些事情完成后才能继续往下执行:多个线程加载资源等…

wait()等待通知方法是Object类中的方法

join()方法是Thread类直接提供的,无参,返回值void。

亲测:join()方法是阻塞,使得主线程等待join()线程执行完毕再往下执行。

CountDownLatch

睡眠sleep
sleep方法是Thread类中的一个静态方法。

如果线程调用sleep方法,则线程会暂时让出指定时间的执行权,在这期间不参与CPU调度,但是所持监视器资源(锁等)不会让出。

睡眠时间到后,函数正常返回,线程处于就绪状态,参与CPU调度,获得CPU资源后可以继续运行。

如果睡眠期间,其他线程调用interrupt()中断该线程,则该线程在调用sleep处抛出异常InterrupetException并返回。

让出CPU执行权yield()
yield()是Thread类中的一个静态方法。

线程休眠

sleep 线程休眠

sleep是让当前线程进入休眠状态,让出CPU资源,但是不会释放锁。
使用场景:延时执行
例如,Android中的版本更新逻辑,一般我们是在进入主页面几秒后去请求接口获取版本更新信息,就可以用sleep实现。

我们先来看下源码:

Thread的几种常用方法_第1张图片

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "线程开始执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "任务执行完毕");
        }, "work");

        thread.start();

        System.out.println("main线程执行");
        
    }

 这里有一个小坑,需要注意下:
我们看到了源码是一个static修饰的方法,那也就是说我们也可以通过对象去调用,下面我们通过对象调用看看是什么效果。

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "线程开始执行");
            System.out.println(threadName + "任务执行完毕");
        }, "work");


        thread.start();
        try {
            thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main线程执行");

    }

也就是说,sleep方法的调用只跟当前线程有关,在哪个线程调sleep,哪个线程就会休眠。

下面我们多加点打印看下是不是这样:

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "线程开始执行");
            try {
                System.out.println(threadName + "开始休眠");
                Thread.sleep(3000);
                System.out.println(threadName + "休眠完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "任务执行完毕");
        }, "work");


        thread.start();

        String threadName = Thread.currentThread().getName();
        try {
            System.out.println(threadName + "开始休眠");
            thread.sleep(2000);
            System.out.println(threadName + "休眠完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(threadName + "线程执行完毕");

    }

你可能感兴趣的:(java,开发语言)