java线程入门基础(二)

2020-05-22  本文已影响0人  Venkingk1

java线程入门基础(二)

一、认识Java里的线程

1.1 Java里的程序天生就是多线程的

一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。

启动main方法后,启动的线程:

二、线程的启动与中止

2.1启动

启动线程的方式有2种:

  1. 继承Thread, X extends Thread
  2. 实现Runabl接口, X implements Runnable
2.1.1 Java多线程之Callable、Future和FutureTask
1. Callable接口

Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call(),这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。

callable接口和runable接口类似,但是提供了比runnable更加强大的功能,主要表现为一下3点:

Callable的使用

private static class UseCall implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("I am implements Callable");
        Thread.sleep(1000);
        return "CallResult";
    }
}

public static void main(String[] args) 
            throws InterruptedException, ExecutionException {
            
        UseCall useCall = new UseCall();
        FutureTask<String> futureTask = new FutureTask<>(useCall);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}
2. Future接口

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
[图片上传失败...(image-8bd7ad-1590133319087)]

3. FutureTask类

FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

创建FutureTask的实例的两种方法:
[图片上传失败...(image-f04c67-1590133319088)]

2.1终止(结束)线程

线程自然终止:要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

2.1.1 手动终止线程
过时的,不安全的,不推荐的

暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

安全终止线程的方式

安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。

线程通过检查自身的中断标志位是否被置为true来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。

总结:在其他线程调用线程A的interrupt()发出中断信号,会中断标示位由false改为true;在A线程的run()方法中调用isInterrupt()或静态方法Thread.interrupted()来进行判断当前线程是否被中断;

注意:
    1.调用静态方法Thread.interrupted()会同时将中断标识位改写为false。
    2.如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。
    3.处于死锁状态的线程无法被中断。

2.2 深入理run()和start()

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。

start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用。

而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,可以被单独调用。

2.3 其他的线程方法

2.3.1 yield()方法

使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行。

2.3.2 join()方法

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

2.3.2 wait()

1.wait():调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁

2.wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回

3.wait (long,int):对于超时时间更细粒度的控制,可以达到纳秒

2.3.2 notify()/notifyAll()

notify():通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。

notifyAll():通知所有等待在该对象上的线程

notify和notifAll的区别
notifyAll: 会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会;
notify: 只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。

三、线程的生命周期

线程生命周期.jpg
上一篇 下一篇

猜你喜欢

热点阅读