线程

Thread中的start和run方法的区别

image-20210119225813255
  • 调用start()方法会创建一个新的子线程并启动
  • run()方法只是Thread的一个普通方法的调用

Thread和Runnable的关系

Runnable是一个接口,Thread是一个类实现了Runnable接口。

  • 因类的单一继承原则,推荐多使用Runnable接口

如何给run()方法传参

  • 构造函数传参
  • 成员变量传参
  • 回调函数传参

如何实现处理线程的返回值

  • 主线程等待法

    • 主线程循环等待,直到子线程返回
    • 缺点:代码臃肿,不知道等待多久,容易出问题
    • image-20210119230825024
  • 使用Thread类的join()阻塞当前线程以等待子线程处理完毕

    • 缺点:粒度不够细
    • image-20210119230841531
  • 通过Callable接口实现:通过FutureTask或者线程池获取

    • 
      

    public class MyCallable implements Callable {
    @Override
    public String call() throws Exception {
    String value=”test”;
    System.out.println(“Read to work”);
    Thread.currentThread().sleep(5000);
    System.out.println(“task done”);
    return value;
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      
    - FutureTask获取返回值的方式:

    - ``` java

    public class FutureTaskDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    FutureTask<String> task=new FutureTask<String>(new MyCallable());
    new Thread(task).start();
    if(!task.isDone()){
    System.out.println("task has not finished,please wait!");
    }
    System.out.println("task return:"+task.get());
    }
    }

    • 通过线程池获取返回值的方式:

      •   public static void main(String[] args) {
              //定义一个线程池
              ExecutorService executorService = Executors.newCachedThreadPool();
              //往线程池添加任务
              Future<String> future = executorService.submit(new MyCallable());
          
              if(!future.isDone()){
                  System.out.println("task has not finished, please wait!");
              }
          
              try{
                  System.out.println(future.get());
              }catch (InterruptedException e) {
                  e.printStackTrace();
              }catch (Exception e) {
                  e.printStackTrace();
              }finally {
                  executorService.shutdown();
              }
          }
          
        

线程的状态

  1. 新建(New):创建后尚未启动的线程的状态

  2. 运行(Runnable):包含Running和Ready

  3. 无限期等待(Waiting):不会被分配CPU执行时间,需要显式被换醒

    • 以下会出现无限期等待:

      1. 没有设置Timeout参数的Oject.wait()方法

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

      3. LockSupport.park()方法

  4. 限期等待(Timed Waiting):在一定时间后会由系统自动唤醒

    • 以下会出现限期等待
      1. Thread.sleep()方法。
      2. 设置了Timeout参数的Object.wait()方法
      3. 设置了Timeout参数的Thread.join()方法
      4. LockSupport.parkNanos()方法
      5. LockSupport.parkUntil()方法
  5. 阻塞(Blocked):等待获取排它锁

  6. 结束(Terminated):已终止线程的状态,线程已经结束执行

sleep和wait的区别

基本差别:

  • sleep是Thread类的方法,wait是Object类中定义的方法
  • sleep方法可以在任何地方使用
  • wait方法只能在synchronized方法或synchronized块中使用

最主要的本质区别

  • Thread.sleep只会让出CPU,不会导致锁行为的改变
  • Object.wait不仅让出CPU,还会释放已经占有的同步资源锁

notify和notifyall的区别

唤醒wait中的线程

两个概念:

  • 锁池EntryList
    • 假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中去等待锁的释放。
  • 等待池WaitSet
    • 假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

区别:

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

yield()方法

概念:

调用Thread.yield()方法后,会给线程调度器一个暗示,暗示当前线程愿意让出CPU的使用,但是调度器可能会忽略这个暗示。

interrupt函数

如何中断线程

已经被抛弃的方法:

  • 通过调用stop()停止线程
  • 通过调用suspend()和resume()方法

目前使用的方法

  • 调用interrupt(),通知线程应该中断了
    • 如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
    • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
  • 需要被调用的线程配合中断
    • 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。

线程状态及转换图

preview