栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC并发编程与源码分析(2)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JUC并发编程与源码分析(2)

JUC并发编程与源码分析
  • 一、Java的锁
    • 1.1 乐观锁和悲观锁
      • 1.1.1 悲观锁
      • 1.1.2 乐观锁
    • 1.2 通过8种情况演示锁运行案例,看看我们到底锁的是什么
      • 1.2.1 JVM中对应的锁在哪里?
      • 1.2.2 synchronized有三种应用方式
        • 1.2.2.1 JDK源码(notify方法)说明举例
        • 1.2.2.2 8种锁的案例实际体现在3个地方
      • 1.2.3 从字节码角度分析synchronized实现
        • synchronized同步代码块
          • 一定是一个enter两个exit吗?
        • synchronized普通同步方法
        • synchronized静态同步方法
      • 1.2.4 反编译synchronized锁的是什么
        • 1.2.4.1 什么是管程monitor
          • 为什么任何一个对象都可以成为锁?
    • 1.3 公平锁和非公平锁
      • 从ReentrantLock卖票编码演示公平和非公平现象
      • 何为公平锁/非公平锁?
        • 源码
        • 面试题
        • 预埋伏AQS
    • 1.4 可重入锁(又名递归锁)
      • "可重入锁"解释
      • 可重入锁种类
        • 隐式锁(即synchronized关键字使用的锁)默认是可重入锁
          • Synchronized的重入的实现机理
        • 显式锁(即Lock)也有ReentrantLock这样的可重入锁。
    • 1.5 死锁及排查
      • 1.5.1 产生死锁原因
      • 1.5.2 死锁case
      • 1.5.3 如何排查死锁
  • 二、LockSupport与线程中断
    • 2.1 线程中断机制
      • 2.1.1 如何停止、中断一个线程?
      • 2.1.2 什么是中断?
      • 2.1.3 中断相关API方法
        • 2.1.3.1 源码分析
          • 实例方法interrupt()
          • 实例方法isInterrupted()
          • 静态方法interrupted()
        • 2.1.3.2 静态方法interrupted()与实例方法isInterrupted()对比
      • 2.1.4 如何使用中断标志停止线程?
        • 2.1.4.1 方法
          • 通过一个volatile变量实现
          • 通过AtomicBoolean实现
          • 通过Thread类自带的中断api方法实现
        • 2.1.4.2 当前线程的中断标识为true,是不是就立刻停止?
          • 后手案例
          • 小总结
      • 总结
    • 2.2 LockSupport是什么?
    • 2.3 线程等待唤醒机制
      • 2.3.1 3种让线程等待和唤醒的方法
      • 2.3.2 Object类中的wait和notify方法
      • 2.3.3 Condition接口中的await后signal方法实现线程的等待和唤醒
      • 2.3.4 Object和Condition使用的限制条件
      • 2.3.5 LockSupport类中的park等待和unpark唤醒
        • 2.3.5.1 是什么?
        • 2.3.5.2 主要方法
          • API
          • 阻塞park() /park(Object blocker)
          • 唤醒unpark(Thread thread)
        • 2.3.5.3 代码
          • 正常+无锁块要求
          • 之前错误的先唤醒后等待,LockSupport照样支持

一、Java的锁 1.1 乐观锁和悲观锁 1.1.1 悲观锁

认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
synchronized关键字和Lock的实现类都是悲观锁

  • 适合写操作多的场景,先加锁可以保证写操作时数据正确。
  • 显式的锁定之后再操作同步资源
1.1.2 乐观锁

乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。

如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作

乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的

  • 适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
  • 乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢
  • 乐观锁一般有两种实现方式:
    • 采用版本号机制
    • CAS(Compare-and-Swap,即比较并替换)算法实现
1.2 通过8种情况演示锁运行案例,看看我们到底锁的是什么
class Phone {//资源类
    public static synchronized void sendEmail() {
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-------sendEmail");
    }

    public synchronized void sendSMS()
    {
        System.out.println("-------sendSMS");
    }

    public void hello()
    {
        System.out.println("-------hello");
    }
}


public class Lock8Demo {
    public static void main(String[] args) {//一切程序的入口,主线程
        Phone phone = new Phone();//资源类1
        Phone phone2 = new Phone();//资源类2

        new Thread(() -> {
            phone.sendEmail();
        },"a").start();

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            //phone.sendSMS();
            //phone.hello();
            phone2.sendSMS();
        },"b").start();

    }
}


1.2.1 JVM中对应的锁在哪里?

1.2.2 synchronized有三种应用方式 1.2.2.1 JDK源码(notify方法)说明举例

1.2.2.2 8种锁的案例实际体现在3个地方
  • 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
  • 作用于代码块,对括号里配置的对象加锁。
  • 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
1.2.3 从字节码角度分析synchronized实现
  • javap -c *.class文件反编译
  • javap -v *.class文件反编译(输出附加信息(包括行号,本地变量表,反汇编等详细信息))
synchronized同步代码块
public class LockByteCodeDemo {
    final Object object = new Object();



    public void m1() {
        synchronized (object){
            System.out.println("----------hello sync");
            //throw new RuntimeException("----ex");
        }
    }

    

    public static synchronized void m2() {

    }
}

反编译

一定是一个enter两个exit吗?

不一定

synchronized普通同步方法
  • 调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。
    如果设置了,执行线程会将先持有monitor然后再执行方法,
    最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor


synchronized静态同步方法
  • ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法


1.2.4 反编译synchronized锁的是什么 1.2.4.1 什么是管程monitor

管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

为什么任何一个对象都可以成为锁?
  • 因为所有对象都继承于Object
  • 在HotSpot虚拟机中,monitor采用ObjectMonitor实现
  • 每个对象天生都带着一个对象监视器

C++源码

  • ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
  • objectMonitor.hpp

1.3 公平锁和非公平锁 从ReentrantLock卖票编码演示公平和非公平现象
class Ticket {
    private int number = 50;

    //private Lock lock = new ReentrantLock(true); //默认用的是非公平锁,分配的平均一点,=--》公平一点
    private Lock lock = new ReentrantLock(false); //默认用的是非公平锁,分配的平均一点,=--》公平一点
    public void sale() {
        lock.lock();
        try {
            if(number > 0) {
                System.out.println(Thread.currentThread().getName()+"t 卖出第: "+(number--)+"t 还剩下: "+number);
            }
        }finally {
            lock.unlock();
        }
    }



    
}

public class SaleTicketDemo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(() -> { for (int i = 1; i <=55; i++) {
            ticket.sale();
        }
        },"a").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) {
            ticket.sale();
        }
        },"b").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) {
            ticket.sale();
        }
        },"c").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) {
            ticket.sale();
        }
        },"d").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) {
            ticket.sale();
        }
        },"e").start();
    }
}

非公平锁:


公平锁:

何为公平锁/非公平锁?

⽣活中,排队讲求先来后到视为公平。程序中的公平性也是符合请求锁的绝对时间的,其实就是 FIFO,否则视为不公平

源码
  • 按序排队公平锁,就是判断同步队列是否还有先驱节点的存在(我前面还有人吗?),如果没有先驱节点才能获取锁;
  • 先占先得非公平锁,是不管这个事的,只要能抢获到同步状态就可以

面试题
  1. 为什么会有公平锁/非公平锁的设计为什么默认非公平?
  • 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间

  • 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销

  1. 使用公平锁会有什么问题?
  • 公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,这就是传说中的 “锁饥饿”
  1. 什么时候用公平?什么时候用非公平?
  • 如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。
预埋伏AQS


1.4 可重入锁(又名递归锁)
  • 是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

"可重入锁"解释

可: 可以
重: 再次
入: 进入
锁: 同步锁。
进入什么:

  • 进入同步域(即同步代码块/方法或显式锁锁定的代码)

一句话:

  • 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。
  • 自己可以获取自己的内部锁
可重入锁种类 隐式锁(即synchronized关键字使用的锁)默认是可重入锁

同步代码块:

public class ReEntryLockDemo{
    public static void main(String[] args) {
        final Object objectLockA = new Object();

        new Thread(() -> {
            synchronized (objectLockA) {
                System.out.println("-----外层调用");
                synchronized (objectLockA) {
                    System.out.println("-----中层调用");
                    synchronized (objectLockA) {
                        System.out.println("-----内层调用");
                    }
                }
            }
        },"a").start();
    }
}
 

同步方法:

public class ReEntryLockDemo{
    public synchronized void m1() {
        System.out.println("-----m1");
        m2();
    }
    public synchronized void m2() {
        System.out.println("-----m2");
        m3();
    }
    public synchronized void m3() {
        System.out.println("-----m3");
    }

    public static void main(String[] args) {
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();

        reEntryLockDemo.m1();
    }
}
Synchronized的重入的实现机理

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

显式锁(即Lock)也有ReentrantLock这样的可重入锁。
public class ReEntryLockDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"t"+"-----外层");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"t"+"-----内层");
                }finally {
                    //lock.unlock();
                }
            }finally {
                lock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("------22222");
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }
}

1.5 死锁及排查

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

1.5.1 产生死锁原因
  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当
1.5.2 死锁case
public class DeadLockDemo {
    static Object lockA = new Object();
    static Object lockB = new Object();


    public static void main(String[] args) {

        Thread a = new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "t" + " 自己持有A锁,期待获得B锁");

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

                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "t 获得B锁成功");
                }
            }
        }, "a");
        a.start();

        new Thread(() -> {
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName()+"t"+" 自己持有B锁,期待获得A锁");

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

                synchronized (lockA)
                {
                    System.out.println(Thread.currentThread().getName()+"t 获得A锁成功");
                }
            }
        },"b").start();


    }
}
1.5.3 如何排查死锁

纯命令:

  • jps -l
  • jstack 进程编号


图形化:

  • jconsole


二、LockSupport与线程中断 2.1 线程中断机制 2.1.1 如何停止、中断一个线程?

2.1.2 什么是中断?

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

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

中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,
此时究竟该做什么需要你自己写代码实现。

每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

2.1.3 中断相关API方法


2.1.3.1 源码分析 实例方法interrupt()




实例方法isInterrupted()

静态方法interrupted()
public class InterruptDemo
{

    public static void main(String[] args) throws InterruptedException
    {
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
        System.out.println("111111");
        Thread.currentThread().interrupt();
        System.out.println("222222");
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
    }
}

2.1.3.2 静态方法interrupted()与实例方法isInterrupted()对比




方法的注释也清晰的表达了“中断状态将会根据传入的ClearInterrupted参数值确定是否重置”。

所以,
静态方法interrupted将 会清除中断状态(传入的参数ClearInterrupted为true),

实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)。

2.1.4 如何使用中断标志停止线程?
  • 在需要中断的线程中不断监听中断状态
    一旦发生中断,就执行相应的中断处理业务逻辑。
2.1.4.1 方法 通过一个volatile变量实现
public class InterruptDemo
{
private static volatile boolean isStop = false;

public static void main(String[] args)
{
    new Thread(() -> {
        while(true)
        {
            if(isStop)
            {
                System.out.println(Thread.currentThread().getName()+"线程------isStop = true,自己退出了");
                break;
            }
            System.out.println("-------hello interrupt");
        }
    },"t1").start();

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

}
 
通过AtomicBoolean实现
public class StopThreadDemo
{
    private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true);

    public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            while(atomicBoolean.get())
            {
                try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("-----hello");
            }
        }, "t1");
        t1.start();

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

        atomicBoolean.set(false);
    }
}
 
通过Thread类自带的中断api方法实现
public class InterruptDemo
{
    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());
    }
}
 
2.1.4.2 当前线程的中断标识为true,是不是就立刻停止?

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

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

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

后手案例

结论:

小总结
  • 中断只是一种协同机制,修改中断标识位仅此而已,不是立刻stop打断
总结

线程中断相关的方法:

interrupt()方法是一个实例方法
它通知目标线程中断,也就是设置目标线程的中断标志位为true,中断标志位表示当前线程已经被中断了。

sInterrupted()方法也是一个实例方法i
它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志

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

2.2 LockSupport是什么?



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

下面这句话,后面详细说
LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程

2.3 线程等待唤醒机制 2.3.1 3种让线程等待和唤醒的方法
  • 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  • 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
  • 方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
2.3.2 Object类中的wait和notify方法
  • wait和notify方法必须要在同步块或者方法里面,且成对出现使用
  • 先wait后notify才OK
2.3.3 Condition接口中的await后signal方法实现线程的等待和唤醒
  • Condtion中的线程等待和唤醒方法之前,需要先获取锁
  • 一定要先await后signal,不要反了
2.3.4 Object和Condition使用的限制条件
  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒
2.3.5 LockSupport类中的park等待和unpark唤醒 2.3.5.1 是什么?
  • 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作


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

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

2.3.5.2 主要方法 API

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

调用LockSupport.park()时

permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,
然后会将permit再次设置为零并返回。

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


调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

2.3.5.3 代码 正常+无锁块要求
public class LockSupportDemo3
{
    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 class T1
{
    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");
    }
}
 
 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/424978.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号