java多线程高级-线程中断/阻塞(四)

java多线程高级-线程中断/阻塞(四)

线程中断/阻塞:interrupt/LockSupport

在Core Java中有这样一句话:”没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 “。

线程中断-interrupt

简单定义
每个线程都有一个interrupt status标志位,用于表明当前线程是否处于中断状态。
一般调用Thread.interrupt()会有两种处理方式

  • 遇到一个低优先级的block状态时,比如object.wait(),object.sleep(),object.join()。它会立马触发一个unblock解除阻塞,并throw一个InterruptedException。
  • 其他情况,Thread.interrupt()仅仅只是更新了status标志位。然后你的工作线程通过Thread.isInterrrupted()进行检查,可以做相应的处理,比如也throw InterruptedException或者是清理状态,取消task等。

Thread的interrupt处理的几个方法

  • public void interrupt() : 执行线程interrupt事件
  • public boolean isInterrupted() : 检查当前线程是否处于interrupt,正式检查,并不会做其它操作。
  • public static boolean interrupted() : check当前线程是否处于interrupt,并重置interrupt信息。interrupted取消interrupt中断操作,并返回当前是否处于interrupt状态。

interrupt设置线程状态设置status=1,标示线程停止了;
interrupted()做了getStatus()并且把status=0;获取线程的标示状态,然后把状态重制为0;
isInterrupted()只是做getStatus();

Thread.interrupt()并不是真正的停止当前线程,只是标示一下线程的状态为停止。需要在程序中和Thread.interrupted()配合使用,通过程序代码来做相应的停止执行。

代码说明:


class TestRunnable implements Runnable{
    public void run(){
        while(true)
        {
            System.out.println( "Thread is running..." );
            long time = System.currentTimeMillis();
            空循环1s
            while((System.currentTimeMillis()-time < 1000)) {

            }
        }
    }
}


public class ThreadInterruptDome {

    public static void main(String[] args){
        Thread th1=new Thread(new TestRunnable());
        th1.start();
        th1.interrupt();
    }
}

输出结果说明:
Thread is running…
Thread is running…
Thread is running…
Thread is running..

不停的往下输出

Thread.interrupt()执行后,如果程序并没有使用Thread.interrupted()做相应处理,Thread.interrupt()其实是没什么卵用的,线程继续执行。

代码片段2

package cn.thread.first.thread;



class TestRunnable implements Runnable{
    public void run(){
        while(true)
        {
            System.out.println( "Thread is running..." );
            if(Thread.interrupted()){//获取线程停止状态
                break;
            }
        }
        System.out.println("线程停止还是可以执行哦。");
    }
}


public class ThreadInterruptDome {

    public static void main(String[] args){
        Thread th1=new Thread(new TestRunnable());
        th1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        th1.interrupt();//设置状态为停止状态
    }
}

输出结果:
Thread is running…
Thread is running…
Thread is running…
Thread is running…
….
线程停止还是可以执行哦。

解说:代码里面做了Thread.interrupted()相应逻辑的判断了,然后break了,但是并不意味着当前线程就停止了,前面已经介绍过了interrupt只是标示一个状态而已,然后另外一头获取状态,仅此而已,获取状态后线程该咋的还是可以咋的,所以break只是跳出了while循环,并不是真正意义上的线程停止了,所以看到了“线程停止了还是执行了”这句。

解决方案:把break替换为return;或者interruptException抛异常。这样最后一句就不会打印了。

代码段3

package cn.thread.first.thread;

class MyThreadStop extends Thread {

    private int temp;

    public MyThreadStop(int temp) {
        this.temp = temp;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i=" + i);
            if (Thread.interrupted()) {
                System.out.println("线程已经停止了哦");
                break;
            }
        }
        System.out.println(Thread.currentThread().getName() + temp);
    }

}


public class ThreadStopDome {


    public static void main(String orgs[]) {
        MyThreadStop myThread = new MyThreadStop(1);
        myThread.start();
        myThread.interrupt();
        System.out.println("运行完毕");

    }

}

输出结果:
运行完毕
i=0
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.thread.first.thread.MyThreadStop.run(ThreadStopDome.java:15)
i=1
i=2
i=3
i=4
解说:1.在wait,notify,sleep的线程中使用interrupt会抛异常sleep interrupted。
2.上面也说明了,myThread.interrupt()把线程状态设置为停止,而调用一次Thread.interrupted()后,该状态被清除了。所以后续1,2,3,4顺利打印出来了,也没有抛异常。因为myThread.interrupt()线程停止的状态已经被清除了。

LockSupport

LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了主要的线程同步原语。
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport实际上是调用了Unsafe类里的函数。归结到Unsafe里,仅仅有两个函数:
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time);
参数:isAbsolute參数是指明时间是绝对的,还是相对的。
unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。
这个有点像信号量,可是这个“许可”是不能叠加的,“许可”是一次性的。
比如:线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,假设线程A再次调用park,则进入等待状态。
注意。unpark函数能够先于park调用。比方线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时。它发现已经有“许可”了。那么它会立即再继续执行。

看一段代码:

class ThreadParkService extends Thread {

    private Thread thread;

    public ThreadParkService(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " wakup before");
        LockSupport.unpark(thread);//解锁后线程2立马就可以执行了。
        try {
            System.out.println("解锁后休息三秒哦");
            Thread.sleep(3000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " wakup after");

    }
}

public class ThreadParkDome {

    public static void main(String args[]) {
        Thread thread = Thread.currentThread();

        ThreadParkService service = new ThreadParkService(thread);
        System.out.println(Thread.currentThread().getName()+" start1");
        service.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" center");
        LockSupport.park(thread);
        System.out.println("end");

    }
}

打印结果:
main start1
Thread-0 wakup before
解锁后休息三秒哦
main center
end
Thread-0 wakup after

解说:
主线程main先打印了自己的东西,主线程到了park时被阻塞了。
线程1异步执行打印了 wakup before
main center打印在“解锁后休息3s哦”说明线程1中的unpark先与park执行。
线程1执行了unpark了,这个时候主线程中的park立马被唤醒,开始执行了end。
然后线程1等待了3s后执行了wakup after。

unpark,可以在park之前执行。它们一对一哦。不管什么时候park只要有unpark过一次就好了。

park被阻塞后,unpark执行后park立即被唤醒。与wait与notify不同,notify唤醒wait后,还必须等待notify代码块中的代码执行完毕后wait才能执行。

代码段2:

class ThreadParkService extends Thread {

    private Thread thread;

    public ThreadParkService(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " wakup before");
        LockSupport.unpark(thread);//解锁后线程2立马就可以执行了。
        try {
            System.out.println("解锁后休息三秒哦");
            Thread.sleep(3000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " wakup after");

    }
}

class ThreadParkService2 extends Thread {

    private Thread thread;

    public ThreadParkService2(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + "-线程2开始park before");
        LockSupport.park(this);//阻塞当前执行的线程,不能阻塞其他线程,看源码哦。里面阻塞时并不需要任何传参。
        //LockSupport.park(thread);//这里并不会阻塞thread线程,阻塞的还是当前线程。thread只是做一个标记方便被锁的对象。
        System.out.println(Thread.currentThread().getName() + "-线程2park after");

    }
}


public class ThreadParkDome {


    public static void main(String args[]) {
        Thread thread = Thread.currentThread();

        ThreadParkService2 service2 = new ThreadParkService2(thread);
        System.out.println(Thread.currentThread().getName() + " start2");
        service2.start();

        System.out.println(" 漂亮的分割线---");

        ThreadParkService service = new ThreadParkService(service2);
        System.out.println(Thread.currentThread().getName() + " start1");
        service.start();
    }
}

执行结果:
main start2
漂亮的分割线—
Thread-0-线程2开始park before
main start1
Thread-1 wakup before
解锁后休息三秒哦
Thread-0-线程2park after
Thread-1 wakup after

解说:其实看结果就是线程2阻塞了,线程1唤醒了线程2.就这么简单。
重点来了:
1.park()与park(Object obj)有什么区别?park()是可以不需要参数的哦,就是说加锁时,不需要指定对象。那么它是怎么加锁的呢?点进去看源码

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);//记录当前线程等待的对象(阻塞对象);
        UNSAFE.park(false, 0L);//阻塞当前线程;
        setBlocker(t, null);//当前线程等待对象置为null。
}

可以看出来,阻塞的时候UNSAFE.park(false, 0L);根本不需要外面的入参数。
那么setBlocker是用来干吗的呢?它是用来做监控用的,当前被阻塞的对象是那个,可以通过LockSupport中的get之类的方法来获取当前被阻塞的对象。
UNSAFE.park(false, 0L);锁住的是当前正在执行的线程哦。

2.代码中线程2有一个参数,我把主线程传递过去了, 想用线程2来阻塞主线程。
main中:
Thread thread = Thread.currentThread();
ThreadParkService2 service2 = new ThreadParkService2(thread);

线程2中:
LockSupport.park(thread);
这样是不行的,前面说过park阻塞的是当前正在执行的线程,和外面传递进来的值无关。所以这里被阻塞的还是线程2.
3.unpark需要带上参数,这样才知道解锁那个线程。

LockSupport.park 锁住的是Thread.currentThread()当前线程。

Thread.currentThread()与this

类继承了Thread类后,可以使用this.getName();获取当前线程的名称,但是Thread.currentThread().getName()也获取线程名称。那么它们有什么区别呢?
继承了Thread类后使用this.getName()获取的当前对象的线程名称,而Thread.currentThread().getName()获取的是当前正在运行的线程的名称
上面代码片段2可以自己执行后测试一下。

park与wait的区别

  1. unpark可以先与park执行,notify不能先与wait执行,不然就不能唤醒了。
  2. unpark需要指定唤醒的线程,而notify是随机唤醒一个wait。
  3. unpark解锁后park立即可以执行,notify解锁后wait需要等待notify所在的synchronized代码块执行完毕后wait才能继续执行。
  4. wait必须在Synchronizer里面才能有效,unpark则不用。
  5. wait是对象锁,park则是线程锁。

HotSpot park实现

synchronized,wait,notify是通过每个对象中的monitor监控器来实现的。
在HotSpot虚拟机的世界里面,每个java线程都有一个Parker的实例,而LockSupper就是通过每个线程里面的Parker实例来实现的。lockSupport底层调用的是UNSAFE.park(),UNSAFE是直接操作操作系统底层的东西,所以没啥好说的。查看这边文章看看了解了解就行。http://www.jianshu.com/p/ceb8870ef2c5


  • synchronized->每个对象中的monitor(monitorenter,monitorexit)->一系列算法锁优化(偏向锁,自旋锁,轻量级锁)->操作系统函数->cpu指令mutex加锁。
  • LockSupper->每个线程中的Parker实例->操作系统函数->cpu指令mutex加锁.

linux与windows中调用cpu的指令有所不同,但都是通过cpu的mutex指令来线程互斥的。
synchronized与lockSupport最终都是一把锁。synchronized锁住的是一件房子,也可以锁住一间房间。而lockSupport锁住的是一个柜子,或者一个抽屉柜。它们所的粒度不同而已,但终究是通过操作系统来实现的。

参看

http://www.cnblogs.com/lcchuguo/p/4855664.html
http://www.cnblogs.com/skywang12345/p/3505784.html(深度好文)
http://www.jianshu.com/p/ceb8870ef2c5

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值