JAVA线程详解

什么是线程:

当操作系统运行一个程序时会为其创建一个进程,一个进程里可以创建多个线程,这些线程都拥有各自的程序计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换

多线程的好处:
  1. 提高cpu的利用率
  2. 提高程序响应速度

线程生命周期

image

线程的状态

线程实际状态只有下面六个状态、上图中的 Ready 和 Running 是为了方便理解加上的。

New

初始状态:线程刚被创建还未启动

Runnable

运行中:cpu在多个线程中切换执行

  • Ready:未争抢到cpu的执行权
  • Running:获得cpu的执行权
Blocked

阻塞中:线程未争抢到锁,被加入阻塞队列,当锁被其他线程释放时再去争抢锁

Waiting

等待中:

  • wait/park:线程释放了锁,被加入等待队列
  • sleep:线程不释放锁,只是放弃cpu的执行权,调度到 Ready 状态
TimeWaiting

超时等待:线程进入等待状态,指定时间后自动唤醒

  • wait/park:也可使用 notify、notifyAll、unpark 可唤醒
  • sleep:只有等自动唤醒
Terminated

终止:线程执行完成

线程的终止

  • run 方法中无循环:方法执行完成后自动终止

  • run 方法中存在循环:终止循环即可

      可以使用下面标志去终止循环:
      1.使用 interrupt 标志
      2.使用共享变量 volatile 标志
    
  • run 方法中进入了 Waiting/TimeWaiting 状态,可使用 interrupt() 方法中断等待状态,线程捕获 InterruptException 异常

  • 调用 stop 方法,该方法不安全已废弃

线程的中断

线程的 interrupt 的含义并不是将线程终止或者中断暂停的意思,interrupt 真正的含义是线程的一个中断标识,只是一个 Boolean 字段,true 代表线程被中断,线程需要根据这个字段做出相应的操作。

Thread 类中关于中断的方法
//判断当前线程是否被中断
//该方法会清除线程的中断状态,即调用过后会将中断状态改为默认值 false,复位线程
public static boolean interrupted()

//判断当前线程是否被中断
public boolean isInterrupted()

//中断线程,将中断状态设置为true
public void interrupt()
线程对中断状态的处理

Waiting状态:

线程处于等待状态,比如调用了 sleep、wait、join 时,此时调用线程的 interrupt 方法线程会抛出一个 InterruptException ,并且中断状态会由true重置为false,线程被复位到Runable状态。

线程在调用 sleep、wait、join 方法后,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常。

   public static void main(String[] args) throws InterruptedException {

        //阻塞状态下调用 interrupt
        RunnerC runnerC = new RunnerC();
        Thread threadC = new Thread(runnerC);
        threadC.start();
        TimeUnit.SECONDS.sleep(1);
        threadC.interrupt();
    }
    
     static class RunnerC implements Runnable{

        private int i;

        @Override
        public void run() {

            try {
                Thread.sleep(10000);

                while(true){
                    i++;
                }

            } catch (InterruptedException e) {
                System.out.println("InterruptedException throw");
            }
            //发生异常后 interrupt 状态被重置为 false
            System.out.println("interrupt状态:"+Thread.currentThread().isInterrupted());
            System.out.println("RunnerC-Count i = "+i);
        }

    }

Runable塞状态:

线程在正常运行状态下,interrupt 被调用只会将中断状态设置为 true,线程不会做任何操作。这时开发者可以根据中断状态做出相应的处理,比如中断run方法中的循环使得线程自动终止

  public static void main(String[] args) throws InterruptedException {

        RunnerA runnerA = new RunnerA();
        Thread threadA = new Thread(runnerA);
        threadA.start();
        TimeUnit.SECONDS.sleep(1);
        //使用 interrupt 信号终止线程
        threadA.interrupt();
    }

    static class RunnerA implements Runnable{

        private int i;

        @Override
        public void run() {

           //根据中断状态来终止循环,从而终止线程
           while(!Thread.currentThread().isInterrupted() ){
                i++;
            }
            System.out.println("RunnerA-Count i = "+i);
        }
    }

也可以使用 volatile 变量进行通信来终止线程

   public static void main(String[] args) throws InterruptedException {

        RunnerB runnerB = new RunnerB();
        Thread threadB = new Thread(runnerB);
        threadB.start();

        TimeUnit.SECONDS.sleep(1);
        //使用共享变量 volatile 终止线程
        runnerB.cancle();
    }
    
    static class RunnerB implements Runnable{

        private int i;
        private volatile boolean on = true;

        @Override
        public void run() {
            //根据 volatile 变量来终止循环,从而终止线程
            while(on){
                i++;
            }
            System.out.println("RunnerB-Count i = "+i);
        }

        public void cancle(){
            on = false;
        }
    }

不可中断的操作:

不可中断的操作,包括进入synchronized段以及Lock.lock(),inputSteam.read()等,调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。

对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。

对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。

线程间通信

volatile 和 synchronize

通信原理是利用了线程间共享内存,volatile 和 synchronize 都可以保证线程间共享变量的可见性,所以可以通过修改共享变量实现线程间的通信

Wait、notify 实现等待通知机制

wait方法的使用必须在synchronize的范围内,因为调用wait 方法后会释放对象的锁,所以要先获取到锁
wait() 调用该方法线程进入 WAITING 状态,只有等待另外线程的通知或被中断才返回
wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒。

notify 通知一个在对象上等待的线程,使其从wait() 方法返回,而返回的前提是该线程获取到对象上的锁
notifAll 通知所有等待在该对象的线程
public class ThreadTest {

    static Object lock = new Object();
    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

      new Thread(new Wait()).start();

      TimeUnit.SECONDS.sleep(1);

      new Thread(new Notify()).start();
    }

    public static class Wait implements Runnable{

        @Override
        public void run() {
            //获取对象锁
            synchronized (lock){
                //判断是否满足条件,不满足则等待
                if (flag){
                    try {
                        System.out.println(Thread.currentThread()
                                +" flag is true. wait @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));

                        //调用 wait 方法,该线程会释放对象锁
                        lock.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //条件满足,执行任务
                System.out.println("执行任务----");
            }
        }
    }

    public static class Notify implements Runnable{

        @Override
        public void run() {
            //获取对象锁
            synchronized (lock){
                System.out.println(Thread.currentThread()
                        +" hold lock notify @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
                //修改条件
                flag = false;
                //通知
                lock.notifyAll();

                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

上述例子演示了 wait和notify 的使用,其实原理就是利用synchronize来进行两个线程之间的通信。需要注意的是这里的通信信号就是 flag 变量,因为flag的修改是在synchronize中进行的,所以保证了flag 的可见性。

还有就是调用 wait 方法后线程是处于 WAITING 状态的不是阻塞状态,该线程是不会去争抢cpu资源的,等到唤醒线程释放锁后才会从 wait 方法返回,由等待状态变为阻塞状态

利用 wait/notify 可以实现经典的生产者消费者模式

join
join():当前线程等该加入该线程后面,等待该线程终止。
join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。

join 是利用 wait方法实现的:

// join JDK 源码
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;
            }
        }
    }

Lock 中的等待通知机制

synchronized 使用对象的 wait,notify 方法实现等待通知机制,因为 synchronized 的实现原理就是对象锁。

Lock 使用的不是对象锁,所以无法使用 object的wait,notify方法
Lock 使用 Condition 来实现等待通知机制

static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static boolean flag = true;

public static class Wait implements Runnable{

        @Override
        public void run() {
            //获取对象锁
            lock.lock();

            try {
                //判断是否满足条件,不满足则等待
                if (flag){
                    System.out.println(Thread.currentThread()
                            +" flag is true. wait @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));

                    //调用 wait 方法
                    condition.await();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

            //条件满足,执行任务
            System.out.println("执行任务----");
        }
    }
    
    public static class Notify implements Runnable{

        @Override
        public void run() {
            //获取对象锁
            lock.lock();

            try {
                System.out.println(Thread.currentThread()
                        +" hold lock notify @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
                //修改条件
                flag = false;
                //通知
                condition.signalAll();

                TimeUnit.SECONDS.sleep(5);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }

你可能感兴趣的:(JAVA线程详解)