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();
            }
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容