Java并发编程2——Java线程

  • Java线程
    • 线程的创建(Thread,Runnable,FutureTask)
    • 线程的常用方法
      • interrupt() / isInterrupted() / interrupted() 三者区别
      • setDaemon() 守护线程
    • 线程的生命周期和状态
      • 操作系统层面:五种状态
      • Java API层面:六种状态
    • 锁对象的方法:wait()和notify()
      • 执行原理
      • WAITING与BLOCKED状态的区别
      • wait()与sleep()的区别
      • notify()与notifyAll()的区别
      • 虚假唤醒
    • LockSupport类的方法:park()和unpark(thread)
      • 执行原理

2.1 线程创建的三种方式

  • 1. 继承Thead类( java.lang.Thead ),重写run()方法
    • 缺点:java不支持多继承,无法继承其他类;代码与任务没有分离
    • 优点:在run()方法内获取当前线程直接用this,无需Thread.currentThread();
Thread t1 = new Thread(){    
    public void run(){
        //线程方法
    }
};
t1.start();
  • 2. 实现Runnable接口( java.lang.Runnable ),重写run()方法,把runnable作为参数传递给thread()
    • 原理:本质都是调用run()方法,如果传入了runnable对象,则会赋值给target,优先使用runnable中的run()方法
    • 缺点:没有返回值
Runnable run = new Runnable(){
    public void run(){
        //代码
    }
};
Thread t = new Thread(run);
t.start();
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}    

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null, true);
}

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
  • 3. FutureTask(java.util.concurrent.FutureTask):本质上FutureTask类实现了Runnable接口和Future接口。它与Runnable的区别在于它可以得到线程的返回值,适用于线程间通信
    • 小结:更推荐Runnable,组合(接口)优于继承。可以使代码更加灵活。如果需要有返回值,需要FutureTask
    • 原理:有两个构造方法,分别穿入Callable和Runnable对象,其中传Runnable的最后通过适配器的方式装换成了Callable,本质都是调用Callable方法
FutureTask<Integer> task = new FutureTask(new Callable<>()){
    public Integer call(){
        //代码
        return 100;
    }
};
Thread t = new Thread(task);
t.start();
Integer result = t.get();

2.2 Thread的常用方法

名称 介绍

thread.start()

thread.run()

  • run()仅仅是调用方法,start()开启并运行新线程
  • start()只能调用一次,多次调用会illegalThreadStateException
thread.sleep(n)
  • eg. 在该线程中调用Thread.sleep(1000);
  • 让线程从Runnable(或者Running)进入Time Waiting状态;
  • 如果该线程持有了共享锁,不会释放锁
  • 其他线程可以通过interrupt()方法打断在睡眠的线程,这时sleep方法会抛出InterruptedException
  • 睡眠结束了进入Runnable状态等待被调用,未必会立即被调用,等待CPU时间片;
  • 建议使用juc包下的TimeUnit类的sleep方法代替Thread下的sleep方法,可读性更好。
thread.yield()
  • 线程礼让,静态方法
  • eg. 在该线程中调用Thread.yield();
    • 让当前线程从Running进入Runnable,重新分配(但有可能重新调度回自己)
thread.getState()
  • 获取线程的状态
    • NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
thread.setPriority()
  • 设计优先级1-10,默认5
  • 然还还是主要根据操作系统分配

thread.join()

thread.join(long n)

  • 等待调用join的线程运行结束/限制最大等待时间,该线程会先进入阻塞状态,底层原理与wait()相同。
  • eg.在主线程中t1.join(),会等待t1运行结束再执行下面代码
  • 底层:保护性暂停模式,底层调用wait(),当线程执行结束,再notifyAll()

thread.interrupt()

thread.isInterrupted()

thread.interrupted() 

  • thread.interrupt():打断其他线程。
    • eg1. 主线程中打断t1线程,t1.interrupt()
  • thread.isInterrupted():判断线程是否被打断
    • eg2. 主线程中判断t1是否被打断,t1.isInterrupted();
    • eg3. t1中判断自己是否被打断,Thread.currentThread().isInterrupted();
  • thread.interrupted() 获取中打断标记重置打断标记为false
thread.setDaemon(true)
  • 设置守护线程
    • eg. 在主线程中t1.setDaemon(true)会让t1成为守护线程,主线程如果运行结束,强制t1结束。
  • 注:t1.setDaemon(true)需要用在t1.start()之前
stop()/ suspend()/ resume()
  • 停止线程运行/ 挂起线程/ 恢复线程运行; 都不推荐使用,如果该线程在执行同步代码块被停止,对象锁无法释放,引起线程同步问题
  • 三种方法都已过时

Object.wait()

Object.wait(n)

Object.notify()

Object.notifyAll()

  • 只有持有了对象锁时才能调用这些方法:让该线程进入WAITING状态,进入Object锁对象中的WaitSet
  • thread.interrupt() / thread.isInterrupted() / thread.interrupted() 三者区别
    • 对于sleep, wait, join中的线程,如t1:
      • 调用t1.interrupt()会让其重新回到RUNNABLE状态
      • 同时t1会有InterruptedException异常,需要捕获
      • 打断后,调用t1.isInterrupted()方法会返回false(标记清除)
    • 对于正在运行态RUNNABLE的线程,如t1:
      • 调用t1.interrupt()方法,并不会对t1有影响,t1仍然会继续执行
      • 不会有异常
      • 但是,t1的打断标记会变true,即t1.isinterrupted() = true
      • 因此,对于正在运行的线程,需要配合打断标记一起使用
      • Thread t1 = new Thread(()->{
            while(true){
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted == true){
                    break;
                }
            }
        });
    • 对于处于park的线程
      • t1的打断标记会变true,即t1.isinterrupted() = true
      • 不会报异常
      • 调用t1.interrupt()会让其重新进入RUNNABLE状态
  • setDaemon() 守护线程
    • 常见的守护线程:gc线程
    • 注:当最后一个非守护线程(用户线程)结束时,JVM会正常退出;守护线程是否正常结束不影响JVM的退出下
    • main线程运行结束后,JVM 会自动启动个叫作 DestroyJavaVM 的线程,该线程会等待所有用户线程结束后终止JVM 
    • 总结:如果希望主线程结束后JVM进程立马结束,可以设置其他线程为守护线程

2.3 线程的生命周期和状态

  • 从操作系统层面,有5种
  • 从java API层面:根据Thread.State,分为6种
    • NEW 初始状态,线程被创建,但还没调用start()方法
      RUNNABLE 包括 可运行状态(RUNNABLE)和 正在运行状态(RUNNING)
      BLOCKED 阻塞状态,被锁阻塞
      WAITING 等待状态
      TIME_WAITING 超时等待,超过等待时间后自行返回
      TERMINATED 终止状态,表示该线程已执行完毕
    • 1
      • thread1.start()
      2
      • thread1线程获得了锁对象之后
        • 调用object.wait() :thread1从RUNNABLE变成WAITING
        • 调用object.notify(),object.notifyAll(),thread2.interrupt()
          • 锁竞争成功:thread1线程从 WAITING变成RUNNABLE
          • 锁竞争失败:thread1线程从 WAITING变成BLOCKED
      3
      • thread2.join():thread1从RUNNABLE变成WAITING,直到thread2结束,thread1再变回RUNNABLE
      4
      • LockSupport.park():让当前线程从RUNNABLE变成WAITING
      • LockSupport.unpart(目标线程):让目标线程从WAITING变回RUNNABLE
      5
      • thread1线程获得了锁对象之后:调用object.wait(时间) 
      6
      • thread2.join(时间)
      7
      • thread1.sleep(时间)
      8
      • LockSupport.parkNanos(时间)  或者  LockSupport.parkUntil(时间)
      9
      • thread1线程获取锁对象失败会进入BLOCKED。
      • 如果锁被释放,会通知该对象上所有BLOCKED的线程重新竞争
      10
      • 代码执行完毕

2.4 锁对象的方法:wait()和notify()

  • 执行原理
    • wait是属于Object的方法,如果不带参数执行wait(),默认调用wait(0),最终给native本地方法执行
    • public final native void wait(long var1) throws InterruptedException;
    • 当某一对象获得锁并执行时,如果想退出,执行wait方法,该线程会进入锁对象中的WaitSet等待再次被唤醒;状态由RUNABLE变为WAITING状态,详细见第三章:https://blog.csdn.net/qq_41157876/article/details/114930398
  • WAITING与BLOCKED状态的区别
    • BLOCKED线程会在Owner释放锁之后唤醒参与竞争,而WAITING需要被唤醒
    • WAITING线程会在Owner线程调用notify或者notifyAll时唤醒,进入EntryList重新竞争
  • wait()与sleep()的区别
    • sleep是thread的静态方法,wait是Object的方法
    • sleep不需和synchronized配合使用,wait需要和synchronized一起使用
    • sleep睡眠时不会释放锁,wait会释放锁
  • notify()与notifyAll()的区别
    • notify()随机唤醒WaitSet中任意一个线程进入EntryList
    • notifyAll()唤醒全部WaitSet的线程
  • 虚假唤醒
    • 概念:处于waiting状态的线程,没有经过notify(),notifyAll()等方法通知,或者没有被打断,仍然意外得被唤醒。称为虚假唤醒
    • 常用解决方法
    • synchronized(obj){
          //如果条件不满足,可能是被虚假唤醒,让出锁对象继续WAITING
          while(条件不满足){
              obj.wait();
          }
      }

2.5 LockSupport类的方法:park()和unpark(thread)

猜你喜欢

转载自blog.csdn.net/qq_41157876/article/details/114902271