共享模型之管程(3)

简介: 共享模型之管程

共享模型之管程(2)https://developer.aliyun.com/article/1530871

8、park/unpark

(1)基本使用

park/unpark都是LockSupport类中的的方法

//暂停线程运行
LockSupport.park;
//恢复线程运行
LockSupport.unpark(thread);Copy
public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(()-> {
      System.out.println("park");
            //暂停线程运行
      LockSupport.park();
      System.out.println("resume");
    }, "t1");
    thread.start();
    Thread.sleep(1000);
    System.out.println("unpark");
      //恢复线程运行
    LockSupport.unpark(thread);
  }

主线程可以先进行unpark ,

(2)特点

与wait/notify的区别

  • wait,notify 和 notifyAll 必须配合Object Monitor一起使用==(先获得锁)==,而park,unpark不必
  • park ,unpark 是以线程为单位阻塞唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么精确
  • park & unpark 可以先 unpark,而 wait & notify 不能先 notify
  • park不会释放锁,而wait会释放锁

(3)原理

每个线程都有一个自己的Park对象,并且该对象**_counter, _cond,__mutex**组成

  • 先调用park再调用unpark时
  • 先调用park
  • 线程运行时,会将Park对象中的**_counter的值设为0**;
  • 调用park时,会先查看counter的值是否为0,如果为0,则将线程放入阻塞队列cond中
  • 放入阻塞队列中后,会再次将counter设置为0
  • 然后调用unpark
  • 调用unpark方法后,会将counter的值设置为1
  • 去唤醒阻塞队列cond中的线程
  • 线程继续运行并将counter的值设为0

  • 先调用unpark,再调用park
  • 调用unpark
  • 会将counter设置为1(运行时0)
  • 调用park方法
  • 查看counter是否为0
  • 因为unpark已经把counter设置为1,所以此时将counter设置为0,但不放入阻塞队列cond中

9、线程中的状态转换

情况一:NEW –> RUNNABLE

  • 当调用了t.start()方法时,由 NEW –> RUNNABLE

情况二: RUNNABLE <–> WAITING

  • 当调用了t 线程用 synchronized(obj) 获取了对象锁后
  • 调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING
  • 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
  • 竞争锁成功,t 线程从 WAITING –> RUNNABLE
  • 竞争锁失败,t 线程从 WAITING –> BLOCKED

情况三:RUNNABLE <–> WAITING

  • 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING
  • 注意是当前线程在t 线程对象的监视器上等待
  • t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE

情况四: RUNNABLE <–> WAITING

  • 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING
  • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –> RUNNABLE

情况五: RUNNABLE <–> TIMED_WAITING

t 线程用 synchronized(obj) 获取了对象锁后

  • 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING
  • t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
  • 竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE
  • 竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED

情况六:RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING
  • 注意是当前线程在t 线程对象的监视器上等待
  • 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING –> RUNNABLE

情况七:RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING
  • 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE

情况八:RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE –> TIMED_WAITING
  • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE

情况九:RUNNABLE <–> BLOCKED

  • t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED
  • 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED

情况十: RUNNABLE <–> TERMINATED

当前线程所有代码运行完毕,进入 TERMINATED

10、多把锁

将锁的粒度细分

class BigRoom {
    //额外创建对象来作为锁
  private final Object studyRoom = new Object();
  private final Object bedRoom = new Object();
}
  • 好处,可以增强并发度
  • 坏处,如果一个线程需要获得多把锁,就容易发生死锁

11、活跃性

(1)定义

因为某种原因,使得代码一直无法执行完毕,这样的现象叫做活跃性

(2)死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁

如:t1线程获得A对象 锁,接下来想获取B对象的锁t2线程获得B对象锁,接下来想获取A对象的锁

public static void main(String[] args) {
    final Object A = new Object();
    final Object B = new Object();
    new Thread(()->{
      synchronized (A) {
        try {
          Thread.sleep(2000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        synchronized (B) {
        }
      }
    }).start();
    new Thread(()->{
      synchronized (B) {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        synchronized (A) {
        }
      }
    }).start();
  }Copy
发生死锁的必要条件
  • 互斥条件
  • 在一段时间内,一种资源只能被一个进程所使用
  • 请求和保持条件
  • 进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源
  • 不可抢占条件
  • 进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放
  • 循环等待条件
  • 发生死锁时,必然存在一个进程——资源的循环链。
定位死锁的方法
  • 检测死锁可以使用jconsole工具
  • jps+jstack ThreadID
  • 在JAVA控制台中的Terminal中输入jps指令可以查看运行中的线程ID,使用jstack ThreadID可以查看线程状态。

F:\Thread_study>jps
20672 RemoteMavenServer36
22880 Jps
4432 Launcher
5316 Test5
20184 KotlinCompileDaemon
11132
F:\Thread_study>jstack 5316Copy
  • 打印的结果
//找到一个java级别的死锁
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000017f40de8 (object 0x00000000d6188880, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000017f43678 (object 0x00000000d6188890, a java.lang.Object),
  which is held by "Thread-1"Copy
  • jconsole检测死锁


哲学家就餐问题

避免死锁的方法

在线程使用锁对象时**,顺序加锁**即可避免死锁

(3)活锁

活锁出现在两个线程互相改变对方的结束条件,后谁也无法结束。

避免活锁的方法

在线程执行时,中途给予不同的间隔时间即可。

死锁与活锁的区别
  • 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞停止运行的现象。
  • 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。

(4)饥饿

某些线程因为优先级太低,导致一直无法获得资源的现象。

在使用顺序加锁时,可能会出现饥饿现象

12、ReentrantLock

和synchronized相比具有的的特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁 (先到先得防止饥饿)
  • 支持多个条件变量( 具有多个waitset)

和synchronized一样,都支持可重入自己加的锁下一次自己也可以直接进去

基本语法

//获取ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
//加锁
lock.lock();
try {
  //需要执行的代码
}finally {
  //释放
  lock.unlock();
}
可重入
  • 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args)
{
    method1();
}
public static void method1()
{
    lock.lock();
    try
    {
        log.debug("execute method1");
        method2();
    }
    finally
    {
        lock.unlock();
    }
}
public static void method2()
{
    lock.lock();
    try
    {
        log.debug("execute method2");
        method3();
    }
    finally
    {
        lock.unlock();
    }
}
public static void method3()
{
    lock.lock();
    try
    {
        log.debug("execute method3");
    }
    finally
    {
        lock.unlock();
    }
}

结果:

17:59:11.862 [main] c.TestReentrant - execute method1
17:59:11.865 [main] c.TestReentrant - execute method2
17:59:11.865 [main] c.TestReentrant - execute method3

三个被锁住的方法都可以运行

共享模型之管程(4)https://developer.aliyun.com/article/1530879

相关文章
|
20天前
|
消息中间件 Java API
共享模型之管程(2)
共享模型之管程
20 0
|
20天前
共享模型之管程(4)
共享模型之管程
12 0
|
20天前
|
Java
共享模型之管程(5)
共享模型之管程
15 0
|
20天前
|
存储 安全 Java
共享模型之管程(1)
共享模型之管程
11 0
|
10月前
|
Linux 编译器 调度
【线程概念和线程控制】(一)
【线程概念和线程控制】(一)
89 0
|
10月前
|
存储
【线程概念和线程控制】(二)
【线程概念和线程控制】(二)
47 0
|
10月前
|
消息中间件 存储 缓存
【多线程系列-01】深入理解进程、线程和CPU之间的关系
【多线程系列-01】深入理解进程、线程和CPU之间的关系
32411 14
|
9月前
|
存储 Linux 调度
多线程——线程概念和线程控制
什么是线程,POSIX线程库,线程控制:pthread_create线程创建,pthread_exit线程终止,pthread_join线程回收,pthread_cancel线程取消,pthread_detach线程分离。线程id和地址空间分局,C++语言级别的多线程,二次封装线程库
88 0
多线程——线程概念和线程控制
|
10月前
|
消息中间件 并行计算 调度
多线程和进程的关系
多线程和进程的关系
64 0
|
存储 Linux 调度
Linux多线程:线程概念、线程间的独有与共享、多线程VS多进程,线程控制:线程创建、线程终止、线程等待、线程分离
Linux多线程:线程概念、线程间的独有与共享、多线程VS多进程,线程控制:线程创建、线程终止、线程等待、线程分离
202 0