JUC-1.基本概念/Lock与synchronized/线程间通信/集合的线程安全/线程池/LockSupport与线程中断

目录

一、基本概念

二、Lock与synchronized

2.1 synchronized

2.2 Lock

2.2.1 ReentrantLock

2.2.2 ReadWriteLock

三、线程间通信

四、集合的线程安全

4.1 ArryList

4.2 HashSet/HashMap

五、线程池

5.1 概念

5.2 线程池的种类与创建

5.2.1 newCachedThreadPool(常用)

5.2.2 newFixedThreadPool(常用)

5.2.3 newSingleThreadExecutor(常用)

5.2.4 newScheduleThreadPool(了解)

5.3 原理与注意事项

六、LockSupport与线程中断

6.1 啥是线程中断?为什么引入?

6.2 线程中断相关API

 6.3 LockSupport

6.3.1 3种让线程等待和唤醒的方法

6.3.2 使用


一、基本概念

        JUC 就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5 开始出现的。

        进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程— —资源分配的最小单位

        线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个 单元执行流。线程——程序执行的最小单位

        并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。 例子:春运抢票 ...         

        并行:多项工作一起执行,互不干扰,之后再汇总。 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中 

        用户线程:平时用到的普通线程,自定义线程

        守护线程:运行在后台,是一种特殊的线程,比如垃圾回收

        当主线程结束后,若用户线程还在运行,则JVM 存活;如果没有用户线程,都是守护线程,则JVM 结束

        线程的状态

NEW,(新建)、RUNNABLE,(准备就绪)、BLOCKED,(阻塞)、WAITING,(不见不散)、TIMED_WAITING,(过时不候)、TERMINATED;(终结)

        wait/sleep 的区别

(1)sleep 是 Thread 静态方法,wait 是 Object 方法,任何对象实例都能调用

(2)sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。

(3)它们都可以被 interrupted 方法中断。 

       管程(monitor)

        是保证了同一时刻只有一个进程管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行

        JVM 中同步是基于进入退出管程(monitor)对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor)会随着 java 对象一同创建销毁

        执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程方法执行时候会持有管程,其他线程无法再获取同一个管程

二、Lock与synchronized

2.1 synchronized

        synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:

        1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象

        2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象

        3. 修饰一个静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象

        4. 修饰一个,其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象

        对于synchronized 修饰的内容,线程释放锁只会有两种情况:

         1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

         2)线程执行发生异常,此时 JVM 会让线程自动释放锁

class Ticket{
    private int num=50;
    public synchronized void sale(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+":卖出:"+(num--)+",剩下:"+num);
        }
    }
}
/**
 * 卖票的例子,A、B、C三者同时买50张票
 */
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"B").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"C").start();

    }
}

2.2 Lock

        Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。 

        Locksynchronized 的区别:

        1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;

        2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;

        3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能响应中断

        4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

        5. Lock 可以提高多个线程进行读操作的效率。 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。

        采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用 Lock 必须在 try{} catch{}块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生。 

        Lock中的重要类与方法

        Lock():是用来获取锁。如果锁已被其他 线程获取,则进行等待。 

        Contition 类:返回 Condition 对象,Condition 类也可以实现等待/通知模式。(类似于使用 synchronized 时 wait()/notify()的用法。但用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知)

                • await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重 新获得锁并继续执行。

                • signal()用于唤醒一个等待的线程。

class LTicket {
    private int num = 50;
    private final ReentrantLock lock = new ReentrantLock();

    public void sale() {
        lock.lock();
        try {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖出:" + (num--) + ",剩下:" + num);
            }
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 卖票的例子,A、B、C三者同时买50张票
 */

public class LSaleTicket {
    public static void main(String[] args) {
    LTicket ticket=new LTicket();
    new Thread(()->{
        for(int i=0;i<40;i++){
            ticket.sale();
        }
    },"A").start();
        new Thread(()->{
            for(int i=0;i<40;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for(int i=0;i<40;i++){
                ticket.sale();
            }
        },"C").start();
    }
}

2.2.1 ReentrantLock

        ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。 ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法

2.2.2 ReadWriteLock

        ReadWriteLock 也是一个接口,在它里面只定义了两个方法-返回写锁/读锁

        也就是说将文件的读写操作分开,分成 2 个锁来分配给线程,从而使得多个线程可以同时进行读操作

        ReentrantReadWriteLock 实现了 ReadWriteLock 接口,最主要的有两个方法:

        readLock()        和         writeLock()        用来获取读锁写锁

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
... ...
rwl.readLock().lock();
... ...
rwl.readLock().unlock();

        注意

        • 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁

         • 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。 

三、线程间通信

        线程间通信的模型有两种:共享内存消息传递,以下方式都是基于这两种模型来实现的。

        使用synchroniza关键字:

class Resource{
    private int num=0;
    public synchronized void inc() throws InterruptedException {
        while (num!=0) this.wait();//判断,防止了虚假唤醒
        num++;//干活
        System.out.println(Thread.currentThread().getName()+"->"+num);
        notifyAll();//通知
    }
    public synchronized void dec() throws InterruptedException {
        while (num!=1) this.wait();//判断,防止了虚假唤醒
        num--;//干活
        System.out.println(Thread.currentThread().getName()+"->"+num);
        notifyAll();//通知
    }

}

/**
 * A、B两个线程分别进行+1、-1操作,二者相互通信,交替执行
 */
public class ThreadCommunication {
    public static void main(String[] args) {
        Resource resource=new Resource();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    resource.inc();//+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    resource.dec();//-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

         使用Lock

class LResource{
    private int num=0;
    private final ReentrantLock lock = new ReentrantLock();
    private Condition condition=lock.newCondition();

    public  void inc() throws InterruptedException {
        lock.lock();
        try {
            while (num!=0) condition.await();//判断,防止了虚假唤醒
            num++;//干活
            System.out.println(Thread.currentThread().getName()+"->"+num);
            condition.signalAll();//通知
        } finally {
            lock.unlock();
        }
    }
    public  void dec() throws InterruptedException {
        lock.lock();
        try {
            while (num!=1) condition.await();//判断,防止了虚假唤醒
            num--;//干活
            System.out.println(Thread.currentThread().getName()+"->"+num);
            condition.signalAll();//通知
        } finally {
            lock.unlock();
        }
    }

}
/**
 * A、B两个线程分别进行+1、-1操作,二者相互通信,交替执行
 */
public class LThreadCommunication {
    public static void main(String[] args) {
        LResource lResource = new LResource();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    lResource.inc();//+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    lResource.dec();//-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

        注意

        使用Lock时代码的判断条件部分必须用while语句,因为如果用if,会产生虚假唤醒的后果:假如一个线程不满足判断条件,处于等待状态,那它再次被唤醒时跳过这个判断条件,直接向下进行

while (num!=0) condition.await();//判断,防止了虚假唤醒 不建议用if

        线程间还可进行定制化通信

class LLResource{
    private int flag=1;
    private Lock lock=new ReentrantLock();
    private Condition c1=lock.newCondition();
    private Condition c2=lock.newCondition();
    private Condition c3=lock.newCondition();

    public void print5(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag!=1){
                c1.await();
            }
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"->"+i+"轮数"+loop);
            }
            flag=2;
            c2.signal();
        } finally {
            lock.unlock();
        }
    }
    public void print10(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag!=2){
                c2.await();
            }
            for(int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"->"+i+"轮数"+loop);
            }
            flag=3;
            c3.signal();
        } finally {
            lock.unlock();
        }
    }
    public void print15(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag!=3){
                c3.await();
            }
            for(int i=0;i<15;i++){
                System.out.println(Thread.currentThread().getName()+"->"+i+"轮数"+loop);
            }
            flag=1;
            c1.signal();
        } finally {
            lock.unlock();
        }
    }
}

/**
 *  A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮
 */
public class ThreadDemo {
    public static void main(String[] args) {
        LLResource resource= new LLResource();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    resource.print5(i);//+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    resource.print10(i);//+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    resource.print15(i);//+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }

}

四、集合的线程安全

4.1 ArryList

        为什么不安全?查看其add方法的源码可知是不安全的,如果有多个线程同时add,并打印,则会报错。

public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
}

        如何解决?

        1.采用Vector 。它的add方法是线程安全的(但太老了,基于synchronized,效率低)

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

        2.使用Collections 提供的方法 synchronizedList

List list = Collections.synchronizedList(new ArrayList<>());

        3.使用CopyOnWriteArrayList 

        特点:

        1. 独占锁效率低:采用读写分离思想解决

        2. 写线程获取到锁,其他写线程阻塞

        3.复制思想(写时复制技术

        当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器引用指向新的容器。 这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写入内存,其他的线程就会读到了脏数据(原来的数据)

        因此,该类型集合最适合于具有以下特征的应用程序:

        List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。

List list = new CopyOnWriteArrayList();

4.2 HashSet/HashMap

        二者也是线程不安全的。有了上面的理解,直接给出二者对应的线程安全的集合:

Set<String> set = new CopyOnWriteArraySet<>();


Map<String,String> map = new HashTable<>();//synchronized(性能低)
Map<String,String> map = Collections.synchronizedMap(new HashMap<>); //装饰器模式,synchronized(性能低)
Map<String,String> map = new ConcurrentHashMap<>();//适合高并发

        为啥ConcurrentHashMap并发性能好呢?

        首先了解SegmentSegment本身就相当于一个HashMap对象。

        ConcurrentHashMap与其他几种类型的区别在于它包含好多个Segment,可以说,ConcurrentHashMap是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表

        不同Segment的写入是可以并发执行的。

        同一Segment的是也可以并发执行

        Segment的写入是需要上锁的,因此对同一Segment的并发写入会被阻塞

        再回顾其他类型的集合,操作都给整个集合加锁,降低了并发效率。

        由此可见,ConcurrentHashMap当中每个Segment各自持有一把锁。在保证线程安全的同时降低了锁的粒度,让并发操作效率更高。

五、线程池

5.1 概念

        线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

        优点

        • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。

        • 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。

        • 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

        线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

        Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 ExecutorExecutorsExecutorServiceThreadPoolExecutor 这几个类

        线程池常用参数:

• corePoolSize 线程池的核心线程数
• maximumPoolSize 能容纳的最大线程数
• keepAliveTime 空闲线程存活时间
• unit 存活的时间单位
• workQueue 存放提交但未执行任务的队列
• threadFactory 创建线程的工厂类
• handler 等待队列满后的拒绝策略

        当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。

        CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大

        AbortPolicy: 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。

        DiscardPolicy: 直接丢弃,其他啥都没有

        DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入

5.2 线程池的种类与创建

5.2.1 newCachedThreadPool(常用)

        作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.

        特点:

         • 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)

         • 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)

        • 当线程池中,没有可用线程,会重新创建一个线程

ExecutorService pool=Executors.newCachedThreadPool();

        场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较 短,任务多的场景

5.2.2 newFixedThreadPool(常用)

        作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这 些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线 程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中 等待。

        特征

        • 线程池中的线程处于一定的量,可以很好的控制线程的并发量

        • 线程可以重复被使用,在显示关闭之前,都将一直存在

        • 超出一定量的线程被提交时候需在队列中等待

ExecutorService pool=Executors.newFixedThreadPool(5)// 固定5个线程

        场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景

5.2.3 newSingleThreadExecutor(常用)

        作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该 线程。

        特征: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此执行

ExecutorService pool= Executors.newSingleThreadExecutor();//一池一线程

        场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个 线程的场景

5.2.4 newScheduleThreadPool(了解)

        作用: 线程池支持定时以及周期性执行任务,创建一个 corePoolSize 为传入参数,最大线程数为整形的最大数的线程池**

        特征:

        (1)线程池中具有指定数量的线程,即便是空线程也将保留

        (2)可定时或者 延迟执行线程活动

ScheduledExecutorService pool = Executors.newScheduledThreadPool(6);

        场景: 适用于需要多个后台线程执行周期任务的场景

5.3 原理与注意事项

        1. 在创建了线程池后,线程池中的线程数为0

        2. 当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:

                2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;                 2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;                 2.3 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

                2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

        3. 当一个线程完成任务时,它会从队列中取下一个任务来执行

        4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:

                4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉

                4.2 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

        注意事项

        通常建议自定义线程池,原因如下(OOM-内存溢出):

         

/**
 * 自定义线程池
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            for(int i=1;i<=10;i++){
                pool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务中");
                });
            }
        }
        finally {
            pool.shutdown();
        }
    }
}

pool-1-thread-1办理业务中
pool-1-thread-2办理业务中
pool-1-thread-3办理业务中
pool-1-thread-2办理业务中
pool-1-thread-1办理业务中
pool-1-thread-1办理业务中
pool-1-thread-2办理业务中
pool-1-thread-5办理业务中
pool-1-thread-4办理业务中
pool-1-thread-3办理业务中

六、LockSupport与线程中断

6.1 啥是线程中断?为什么引入?

        首先 一个线程不应该由其他线程强制中断停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

        其次 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断

        中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现

6.2 线程中断相关API

        具体来说,当对一个线程,调用 interrupt() 时:

        ① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。 被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。

        ② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法, 那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

        Thread类的静态方法interrupted() 返回当前线程的中断状态(boolean类型)且将当前线程的中断状态设为false。此方法调用之后会清除当前线程的中断标志位的状态(false),返回当前值

         一般情况下推荐interrupt()与isInterrupted()组合使用。

        中断标识用于在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑。

//实现方式有三种:
private static volatile boolean isStop = false;
private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true);

Thread.currentThread().isInterrupted()//线程是否被中断? (推荐)


Thread.currentThread().interrupt()//设置该线程的中断标志位为true
public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            while(true)
            {
                if(Thread.currentThread().isInterrupted())
                {
                    System.out.println("-----t1 线程被中断了,break,程序结束");
                    break;
                }
                System.out.println("-----hello");
            }
        }, "t1");
        t1.start();

        System.out.println("**************"+t1.isInterrupted());
        //暂停5毫秒
        try { TimeUnit.MILLISECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
        t1.interrupt();
        System.out.println("**************"+t1.isInterrupted());
    }

**************false
-----hello
-----hello
... ...
-----hello
-----hello
**************true
-----t1 线程被中断了,break,程序结束

 6.3 LockSupport

        LockSupport是用来创建锁和其他同步类基本线程阻塞原语.

6.3.1 3种让线程等待和唤醒的方法

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  2. 使用JUC包中Conditionawait()方法让线程等待,使用signal()方法唤醒线程
  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

        Object和Condition使用的限制条件:

        线程先要获得并持有锁,必须在锁块(synchronized或lock)中,

必须要先等待后唤醒,线程才能够被唤醒

        LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞唤醒线程的功能, 每个线程都有一个许可(permit), permit只有两个值1和零,默认是零。 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。 

6.3.2 使用

阻塞

        park() /park(Object blocker)         //阻塞当前线程/阻塞传入的具体线程

唤醒

        unpark(Thread thread)         //唤醒处于阻塞状态的指定线程

public static void main(String[] args)
    {
        //正常使用+不需要锁块!!!
    Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName()+" "+"1111111111111");
    LockSupport.park();
    System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒");
},"t1");
t1.start();

//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

LockSupport.unpark(t1);//唤醒
System.out.println(Thread.currentThread().getName()+"   -----LockSupport.unparrk() invoked over");

    }
//先唤醒后等待,LockSupport照样支持
public static void main(String[] args)
    {
            Thread t1 = new Thread(() -> {
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis());
            LockSupport.park();//后阻塞
            System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---被叫醒");
        },"t1");
        t1.start();

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

        LockSupport.unpark(t1);//先唤醒
        System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---unpark over");
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值