第三章 - 共享模型之管程(四)

线程状态

重新理解线程状态转换 (重点)

Untitled

Untitled

Untitled

假设有线程 Thread t

情况 1 NEW --> RUNNABLE

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

情况 2 RUNNABLE <–> WAITING

  • t 线程用 synchronized(obj) 获取了对象锁后
  • 调用 obj.wait( ) 方法时,t 线程从 RUNNABLE --> WAITING
  • 调用 obj.notify( ) , obj.notifyAll( ) , t.interrupt( ) 时
    • 竞争锁成功,t 线程从 WAITING --> RUNNABLE
    • 竞争锁失败,t 线程从 WAITING --> BLOCKED
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {final static Object obj = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t1").start();new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t2").start();// 主线程两秒后执行sleep(0.5);log.debug("唤醒 obj 上其它线程");synchronized (obj) {obj.notifyAll(); // 唤醒obj上所有等待线程}}
}

Monitor示意图:

Untitled

示例调试步骤

  • 设置3个断点

Untitled

  • 顺序执行代码,线程t1、t2都wait了,断点来到主线程的notifyAll( )

Untitled

  • 向下执行,主线程notifyAll( )唤醒所有waiting的线程,这些线程从锁对象obj的waitSet移动到EntryList竞争对象锁

Untitled

  • 主线程向下执行,出了synchronized块,释放了锁。t1、t2可以竞争锁。可以看到t2竞争锁成功,成为锁对象obj的owner,t1仍然在entryList中阻塞。

Untitled

  • t2线程向下执行,出了synchronized块,释放锁,t1线程拿到锁

Untitled

情况 3 RUNNABLE <–> WAITING

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

情况 4 RUNNABLE <–> WAITING

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

情况 5 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

情况 6 RUNNABLE <–> TIMED_WAITING

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

情况 7 RUNNABLE <–> TIMED_WAITING

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

情况 8 RUNNABLE <–> TIMED_WAITING

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

情况 9 RUNNABLE <–> BLOCKED

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

情况 10 RUNNABLE <–> TERMINATED

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

多把锁

多把不相干的锁

  • 一间大屋子有两个功能:睡觉、学习,互不相干。
  • 现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
    • 小南获得锁之后,学习完之后,小女才能进来睡觉
  • 解决方法是准备多个房间(多个对象锁)

代码示例

@Slf4j(topic = "c.BigRoomTest")
public class BigRoomTest {public static void main(String[] args) {BigRoom bigRoom = new BigRoom();new Thread(() -> {bigRoom.study();}, "小南").start();new Thread(() -> {bigRoom.sleep();}, "小女").start();}}@Slf4j(topic = "c.BigRoom")
class BigRoom {public void sleep() {synchronized (this) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (this) {log.debug("study 1 小时");Sleeper.sleep(1);}}}

Untitled

改进方法:让小南, 小女获取不同的锁即可

@Slf4j(topic = "c.BigRoomTest")
public class BigRoomTest {public static void main(String[] args) {BigRoom bigRoom = new BigRoom();new Thread(() -> {bigRoom.study();}, "小南").start();new Thread(() -> {bigRoom.sleep();}, "小女").start();}}@Slf4j(topic = "c.BigRoom")
class BigRoom {private static final BigRoom bedRoom = new BigRoom(); //卧室private static final BigRoom studyRoom = new BigRoom(); //书房public void sleep() {synchronized (bedRoom) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (studyRoom) {log.debug("study 1 小时");Sleeper.sleep(1);}}}

Untitled

  • 将锁的粒度细分
    • 好处,是可以增强并发度
    • 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

活跃性

  • 因为某种原因,使得代码一直无法执行完毕,这样的现象叫做 活跃性
  • 活跃性相关的一系列问题都可以用 ReentrantLock 进行解决。

死锁 (重点)

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

线程1获取A对象锁, 线程2获取B对象锁; 此时线程1又想获取B对象锁, 线程2又想获取A对象锁; 它们都等着对象释放锁, 此时就称为死锁

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {public static void main(String[] args) {test1();}private static void test1() {Object A = new Object();Object B = new Object();Thread t1 = new Thread(() -> {synchronized (A) {log.debug("lock A");sleep(1);synchronized (B) {log.debug("lock B");log.debug("操作...");}}}, "t1");Thread t2 = new Thread(() -> {synchronized (B) {log.debug("lock B");sleep(0.5);synchronized (A) {log.debug("lock A");log.debug("操作...");}}}, "t2");t1.start();t2.start();}}

Untitled

发生死锁的必要条件 (重点)

  • 互斥条件
    • 在一段时间内,一种资源只能被一个进程所使用
  • 请求和保持条件
    • 进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源
  • 不可抢占条件
    • 进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放
  • 循环等待条件
    • 发生死锁时,必然存在一个进程——资源的循环链。

定位死锁的方法

方式一、JPS + JStack 进程ID

  • jps先找到JVM进程
  • jstack 进程ID
    • 在Java控制台中的Terminal中输入 jps 指令可以查看正在运行中的进程ID,使用 jstack 进程ID 可以查看进程状态。
jps

Untitled

jstack 59114

Untitled

  • 避免死锁要注意加锁顺序
  • 另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查

方式二、 jconsole检测死锁

Untitled

Untitled

哲学家就餐问题

Untitled

有五位哲学家,围坐在圆桌旁。

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
  • 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
  • 如果筷子被身边的人拿着,自己就得等待

代码示例

public class TestDeadLock {public static void main(String[] args) {Chopstick c1 = new Chopstick("1");Chopstick c2 = new Chopstick("2");Chopstick c3 = new Chopstick("3");Chopstick c4 = new Chopstick("4");Chopstick c5 = new Chopstick("5");new Philosopher("苏格拉底", c1, c2).start();new Philosopher("柏拉图", c2, c3).start();new Philosopher("亚里士多德", c3, c4).start();new Philosopher("赫拉克利特", c4, c5).start();new Philosopher("阿基米德", c5, c1).start();}}@Slf4j(topic = "c.Philosopher")
// 哲学家类
class Philosopher extends Thread {Chopstick left; // 左边的筷子Chopstick right; // 右边的筷子public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}@Overridepublic void run() {while (true) {// 尝试获得左手筷子synchronized (left) {// 尝试获得右手筷子synchronized (right) {// 吃饭eat();}// 放下右手筷子}// 放下左手筷子}}private void eat() {log.debug("eating...");Sleeper.sleep(0.5);}
}// 筷子类
class Chopstick {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}

执行一会儿,就不再执行下去了

Untitled

  • 使用 jconsole 检测死锁,发现

Untitled

  • 这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,(死锁是谁也无法执行)例如

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {static volatile int count = 10;static final Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 期望减到 0 退出循环while (count > 0) {sleep(0.2);count--;log.debug("count: {}", count);}}, "t1").start();new Thread(() -> {// 期望超过 20 退出循环while (count < 20) {sleep(0.2);count++;log.debug("count: {}", count);}}, "t2").start();}
}

避免活锁的方法

  • 在线程执行时,中途给予 不同的间隔时间, 让某个线程先结束即可。

死锁与活锁的区别

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

饥饿

  • 很多教程中把饥饿定义为:一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
  • 下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题

Untitled

顺序加锁的解决方案

Untitled

虽然不会发生死锁了,但是会发生饥饿

public class TestDeadLock {public static void main(String[] args) {Chopstick c1 = new Chopstick("1");Chopstick c2 = new Chopstick("2");Chopstick c3 = new Chopstick("3");Chopstick c4 = new Chopstick("4");Chopstick c5 = new Chopstick("5");new Philosopher("苏格拉底", c1, c2).start();new Philosopher("柏拉图", c2, c3).start();new Philosopher("亚里士多德", c3, c4).start();new Philosopher("赫拉克利特", c4, c5).start();new Philosopher("阿基米德", c1, c5).start();}
}
  • 由之前的
new Philosopher("阿基米德", c5, c1).start();
  • 改为了
new Philosopher("阿基米德", c1, c5).start();
  • 可以看到,程序没有发生死锁,但可以看到有些线程根本没有机会执行

Untitled

ReentrantLock (重点)

ReentrantLock 的特点 (synchronized不具备的)

  • 支持可重入
    • 可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此 有权利再次获取这把锁
  • 可中断
    • lock.lockInterruptibly( ) : 可以被其他线程打断的中断锁
  • 可以设置超时时间
    • lock.tryLock(时间) : 尝试获取锁对象, 如果超过了设置的时间, 还没有获取到锁, 此时就退出阻塞队列, 并释放掉自己拥有的锁
  • 可以设置为公平锁
    • (先到先得) 默认是非公平, true为公平 new ReentrantLock(true)
  • 支持多个条件变量( 有多个waitSet )
    • (可避免虚假唤醒) - lock.newCondition( )创建条件变量对象; 通过条件变量对象调用 await/signal方法, 等待/唤醒

基本语法

//获取ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
//加锁
lock.lock();
try {// 临界区
}finally {// 释放锁lock.unlock();
}

支持可重入

  • 可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
/*** @author xiexu* @create 2022-02-01 8:56 AM*/
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {// 获取ReentrantLock对象private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {// 如果有竞争就进入"阻塞队列",一直等待着,不能被打断lock.lock();try {log.debug("entry main...");m1();} finally {lock.unlock();}}private static void m1() {lock.lock();try {log.debug("entry m1...");m2();} finally {lock.unlock();}}private static void m2() {lock.lock();try {log.debug("entry m2....");} finally {lock.unlock();}}}

Untitled

可中断 (针对于lockInterruptibly( )方法获得的中断锁) 直接退出阻塞队列, 获取锁失败

synchronizedreentrantlock.lock( ) 的锁, 是不可被打断的; 也就是说别的线程已经获得了锁, 我的线程就需要一直等待下去,不能中断

  • 可被中断的锁, 通过 lock.lockInterruptibly( ) 获取的锁对象, 可以通过调用阻塞线程的interrupt( ) 方法
  • 如果某个线程处于阻塞状态,可以调用其interrupt方法让其停止阻塞获得锁失败
    • 处于阻塞状态的线程,被打断了就不用阻塞了,直接停止运行
  • 可中断的锁, 在一定程度上可以被动的减少死锁的概率, 之所以被动, 是因为我们需要手动调用阻塞线程的interrupt方法;

测试使用lock.lockInterruptibly()可以从阻塞队列中,打断

/*** 演示RenntrantLock中的可打断锁方法 lock.lockInterruptibly();** @author xiexu* @create 2022-02-01 10:38 AM*/
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("t1线程启动...");try {// 如果没有竞争那么此方法就会获取 lock 对象锁// 如果有竞争就进入阻塞队列,可以被其他线程用 interrupt 方法打断log.debug("尝试获得锁");lock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();log.debug("等锁的过程中被打断了,没有获得锁,返回"); //没有获得锁就不需要往下走了return;}try {log.debug("t1线程获得了锁");} finally {log.debug("t1线程释放了锁");lock.unlock();}}, "t1");// 主线程获得锁(此锁不可被打断)lock.lock();log.debug("main线程获得了锁");// 启动t1线程t1.start();try {Sleeper.sleep(1);log.debug("打断 t1 线程");t1.interrupt();            //打断t1线程} finally {lock.unlock();}}
}

Untitled

测试使用lock.lock()不可以从阻塞队列中打断, 一直等待别的线程释放锁

@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("t1线程启动...");log.debug("尝试获得锁");lock.lock();try {log.debug("t1线程获得了锁");} finally {log.debug("t1线程释放了锁");lock.unlock();}}, "t1");// 主线程获得锁(此锁不可被打断)lock.lock();log.debug("main线程获得了锁");// 启动t1线程t1.start();try {Sleeper.sleep(1);log.debug("打断 t1 线程");t1.interrupt();            //打断t1线程} finally {log.debug("main线程释放了锁");lock.unlock();}}
}
  • lock( )锁不能被打断, 在主线程中调用t1.interrupt( ), 没用, 当主线程释放锁之后, t1线程获得了锁

Untitled

锁超时 (lock.tryLock( )) 直接退出阻塞队列, 获取锁失败

防止无限制等待, 减少死锁

  • 使用 lock.tryLock( ) 方法会返回获取锁是否成功。如果成功则返回true,反之则返回false。
  • 并且tryLock方法可以设置指定等待时间,参数为:tryLock(long timeout, TimeUnit unit) , 其中timeout为最长等待时间,TimeUnit为时间单位
  • 获取锁的过程中, 如果超过等待时间或者被打断, 就直接从阻塞队列移除, 此时获取锁就失败了, 不会一直阻塞着 ! (可以用来实现死锁问题)

不设置等待时间, 立即失败

/*** 演示RenntrantLock中的tryLock()** @author xiexu* @create 2022-02-01 10:38 AM*/
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("尝试获得锁");// 此时肯定获取失败, 因为主线程已经获得了锁对象if (!lock.tryLock()) {log.debug("获取不到锁,返回");return;}try {log.debug("获得到锁");} finally {log.debug("释放了锁");lock.unlock();}}, "t1");lock.lock(); // main线程先获得锁log.debug("获得到锁");t1.start();// 主线程2s之后才释放锁Sleeper.sleep(2);log.debug("释放了锁");lock.unlock();}}

Untitled

设置等待时间, 超过等待时间还没有获得锁, 失败, 从阻塞队列中移除该线程

/*** 演示RenntrantLock中的tryLock(long mills), * 超过锁设置的等待时间或者被打断,就从阻塞队列移除** @author xiexu* @create 2022-02-01 10:38 AM*/
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("尝试获得锁");try {// 设置等待时间, 超过等待时间或者被打断, 都会获取锁失败; 退出阻塞队列if (!lock.tryLock(3, TimeUnit.SECONDS)) {log.debug("获取不到锁,返回");return;}} catch (InterruptedException e) {e.printStackTrace();log.debug("被打断了, 获取锁失败, 返回");return;}try {log.debug("获得到锁");} finally {log.debug("释放了锁");lock.unlock();}}, "t1");lock.lock(); // main线程先获得锁log.debug("获得到锁");t1.start();t1.interrupt(); // 打断t1线程// 主线程5s之后才释放锁Sleeper.sleep(5);lock.unlock();log.debug("释放了锁");}}

Untitled

Untitled

通过lock.tryLock( )来解决, 哲学家就餐问题 (重点)

lock.tryLock(时间) : 尝试获取锁对象, 如果超过了设置的时间, 还没有获取到锁, 此时就退出阻塞队列, 并释放掉自己拥有的锁

/*** 使用了ReentrantLock锁, 该类中有一个tryLock()方法, 在指定时间内获取不到锁对象, 就从阻塞队列移除,不用一直等待。* 当获取了左手边的筷子之后, 尝试获取右手边的筷子, 如果该筷子被其他哲学家占用, 获取失败, 此时就先把自己左手边的筷子,* 给释放掉. 这样就避免了死锁问题*/
@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {public static void main(String[] args) {Chopstick c1 = new Chopstick("1");Chopstick c2 = new Chopstick("2");Chopstick c3 = new Chopstick("3");Chopstick c4 = new Chopstick("4");Chopstick c5 = new Chopstick("5");new Philosopher("苏格拉底", c1, c2).start();new Philosopher("柏拉图", c2, c3).start();new Philosopher("亚里士多德", c3, c4).start();new Philosopher("赫拉克利特", c4, c5).start();new Philosopher("阿基米德", c5, c1).start();}}@Slf4j(topic = "c.Philosopher")
// 哲学家类
class Philosopher extends Thread {Chopstick left; // 左边的筷子Chopstick right; // 右边的筷子public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}@Overridepublic void run() {while (true) {// 获得了左手边筷子 (针对五个哲学家, 它们刚开始肯定都可获得左筷子)if (left.tryLock()) {try {// 此时发现它的right筷子被占用了, 使用tryLock(),// 尝试获取失败, 此时它就会将自己左筷子也释放掉// 临界区代码if (right.tryLock()) { //尝试获取右手边筷子, 如果获取失败, 则会释放左边的筷子try {eat();} finally {right.unlock();}}} finally {left.unlock(); //释放自己手里左手的筷子}}}}private void eat() {log.debug("eating...");Sleeper.sleep(0.5);}
}// 继承ReentrantLock, 让筷子类称为锁
class Chopstick extends ReentrantLock {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}}

Untitled

公平锁 new ReentrantLock(true)

  • ReentrantLock默认是非公平锁, 但是可以指定为公平锁。
  • 在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。
  • 一般不要设置ReentrantLock为公平的, 会降低并发度
  • Synchronized底层的Monitor锁就是不公平的, 和谁先进入阻塞队列是没有关系的。
//默认是不公平锁,需要在创建时指定为公平锁
ReentrantLock lock = new ReentrantLock(true);

什么是公平锁? 什么是非公平锁?

  • 公平锁 (new ReentrantLock(true))
    • 公平锁, 可以把竞争的线程放在一个先进先出的阻塞队列上
    • 只要持有锁的线程执行完了, 唤醒阻塞队列中的下一个线程获取锁即可; 此时先进入阻塞队列的线程先获取到锁
  • 非公平锁 (synchronized, new ReentrantLock( ))
    • 非公平锁, 当阻塞队列中已经有等待的线程A了, 此时后到的线程B, 先去尝试看能否获得到锁对象. 如果获取成功, 此时就不需要进入阻塞队列了. 这样以来后来的线程B就先获得到锁了
  • 所以公平和非公平的区别就是 : 线程执行同步代码块时, 是否会去尝试获取锁, 如果会尝试获取锁, 那就是非公平的, 如果不会尝试获取锁, 直接进入阻塞队列, 再等待被唤醒, 那就是公平的

条件变量 (可避免虚假唤醒) - lock.newCondition( )创建条件变量对象; 通过条件变量对象调用await/signal方法, 等待/唤醒

  • Synchronized 中也有条件变量,就是Monitor监视器中的 waitSet 等待集合,当条件不满足时进入waitSet 等待
  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量。
    • synchronized 是那些不满足条件的线程都在一间休息室等通知; (此时会造成虚假唤醒)
    • ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒的; (可以避免虚假唤醒)

使用流程

  • await 前需要 获得锁
  • await 执行后,会释放锁,进入 conditionObject (条件变量)中等待
  • await 的线程被唤醒(或打断、或超时)去重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
  • signal 方法用来唤醒某个条件变量(休息室)的某一个等待的线程
  • signalAll 方法用来唤醒某个条件变量(休息室)中的所有线程
/*** ReentrantLock可以设置多个条件变量(多个休息室), 相对于synchronized底层monitor锁中waitSet** @author xiexu* @create 2022-02-03 10:18 AM*/
@Slf4j(topic = "c.ConditionVariable")
public class ConditionVariable {private static boolean hasCigarette = false;private static boolean hasTakeout = false;private static final ReentrantLock lock = new ReentrantLock();// 创建新的条件变量(休息室)// 等待烟的休息室static Condition waitCigaretteSet = lock.newCondition();// 等外卖的休息室static Condition waitTakeoutSet = lock.newCondition();public static void main(String[] args) {new Thread(() -> {lock.lock();try {log.debug("有烟没?[{}]", hasCigarette);while (!hasCigarette) {log.debug("没烟,先歇会!");try {// 此时小南进入到 等烟的休息室waitCigaretteSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("烟来咯, 可以开始干活了");} finally {lock.unlock();}}, "小南").start();new Thread(() -> {lock.lock();try {log.debug("外卖送到没?[{}]", hasTakeout);while (!hasTakeout) {log.debug("没外卖,先歇会!");try {// 此时小女进入到 等外卖的休息室waitTakeoutSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖来咯, 可以开始干活了");} finally {lock.unlock();}}, "小女").start();Sleeper.sleep(1);new Thread(() -> {lock.lock();try {log.debug("送外卖的来咯~");hasTakeout = true;// 唤醒等外卖的小女线程waitTakeoutSet.signal();} finally {lock.unlock();}}, "送外卖的").start();Sleeper.sleep(1);new Thread(() -> {lock.lock();try {log.debug("送烟的来咯~");hasCigarette = true;// 唤醒等烟的小南线程waitCigaretteSet.signal();} finally {lock.unlock();}}, "送烟的").start();}}

Untitled

同步模式之顺序控制

固定运行顺序

  • 假如有两个线程, 线程A打印1, 线程B打印2.
  • 要求: 程序先打印2, 再打印1

使用ReentrantLock的 await/signal

/*** 使用ReentrantLock的 await/sinal 来实现顺序打印 2, 1** @author xiexu* @create 2022-02-03 10:42 AM*/
@Slf4j(topic = "c.SyncPrintWaitTest")
public class SyncPrintWaitTest {public static final ReentrantLock lock = new ReentrantLock();public static Condition condition = lock.newCondition();// 表示 t2 线程是否运行过public static boolean t2Runned = false;public static void main(String[] args) {Thread t1 = new Thread(() -> {lock.lock();try {// 临界区while (!t2Runned) {try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("1");} finally {lock.unlock();}}, "t1");Thread t2 = new Thread(() -> {lock.lock();try {log.debug("2");t2Runned = true;condition.signal();} finally {lock.unlock();}}, "t2");t1.start();t2.start();}
}

Untitled

Wait/Notify版本实现

/*** 使用wait/notify来实现顺序打印 2, 1** @author xiexu* @create 2022-02-03 10:30 AM*/
@Slf4j(topic = "c.SyncPrintWaitTest")
public class SyncPrintWaitTest {public static final Object lock = new Object();// 表示 t2 线程是否运行过public static boolean t2Runned = false;public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock) {while (!t2Runned) {try {// 进入等待(waitSet), 会释放锁lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("1");}}, "t1");Thread t2 = new Thread(() -> {synchronized (lock) {log.debug("2");t2Runned = true;lock.notify();}}, "t2");t1.start();t2.start();}
}

Untitled

使用LockSupport中的 park/unpark

可以看到,wait/notify 在实现上很麻烦:

  • 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该wait
  • 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决此问题
  • 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
/*** 使用LockSupport中的park,unpark来实现, 顺序打印 2, 1** @author xiexu* @create 2022-02-03 10:48 AM*/
@Slf4j(topic = "c.SyncPrintWaitTest")
public class SyncPrintWaitTest {public static void main(String[] args) {Thread t1 = new Thread(() -> {LockSupport.park();log.debug("1");}, "t1");Thread t2 = new Thread(() -> {log.debug("2");LockSupport.unpark(t1);}, "t2");t1.start();t2.start();}}

Untitled

  • park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』,不需要『同步对象』和『运行标记』
  • 因为wait-notify其实是运用了保护性暂停模式,而park-unpark的底层实现其实就是保护性暂停的体现

交替输出

线程1 输出 a 5次, 线程2 输出 b 5次, 线程3 输出 c 5次。现在要求输出abcabcabcabcabcabc

wait/notify 版本

/*** Description: 使用wait/notify来实现三个线程交替打印 abcabcabcabcabc** @author xiexu* @create 2022-02-03 10:50 AM*/
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {public static void main(String[] args) {WaitNotify waitNotify = new WaitNotify(1, 5);new Thread(() -> {waitNotify.print("a", 1, 2);}, "a线程").start();new Thread(() -> {waitNotify.print("b", 2, 3);}, "b线程").start();new Thread(() -> {waitNotify.print("c", 3, 1);}, "c线程").start();}
}@Slf4j(topic = "c.WaitNotify")
@Data
class WaitNotify {// 等待标记private int flag;// 循环次数private int loopNumber;public WaitNotify(int flag, int loopNumber) {this.flag = flag;this.loopNumber = loopNumber;}/*输出内容    等待标记    下一个标记a           1          2b           2          3c           3          1*//*** @param str      打印的内容* @param waitFlag 等待标记* @param nextFlag 下一个标记*/public void print(String str, int waitFlag, int nextFlag) {for (int i = 0; i < loopNumber; i++) {synchronized (this) {while (waitFlag != this.flag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(str);this.flag = nextFlag;this.notifyAll();}}}}

Untitled

await/signal版本

/*** 使用await/signal来实现三个线程交替打印 abcabcabcabcabc** @author xiexu* @create 2022-02-03 12:38 AM*/
@Slf4j(topic = "c.TestWaitNotify")
public class TestAwaitSignal {public static void main(String[] args) throws InterruptedException {AwaitSignal awaitSignal = new AwaitSignal(5);// a线程的休息室Condition a_condition = awaitSignal.newCondition();// b线程的休息室Condition b_condition = awaitSignal.newCondition();// c线程的休息室Condition c_condition = awaitSignal.newCondition();new Thread(() -> {awaitSignal.print("a", a_condition, b_condition);}, "a").start();new Thread(() -> {awaitSignal.print("b", b_condition, c_condition);}, "b").start();new Thread(() -> {awaitSignal.print("c", c_condition, a_condition);}, "c").start();Thread.sleep(1000);System.out.println("==========开始=========");awaitSignal.lock();try {a_condition.signal();  //首先唤醒a线程} finally {awaitSignal.unlock();}}
}class AwaitSignal extends ReentrantLock {// 循环次数private final int loopNumber;public AwaitSignal(int loopNumber) {this.loopNumber = loopNumber;}/*** @param str     打印的内容* @param current 当前线程的休息室* @param next    下一个线程的休息室*/public void print(String str, Condition current, Condition next) {for (int i = 0; i < loopNumber; i++) {lock();try {try {current.await();//System.out.print("i:==="+i);System.out.print(str);next.signal();} catch (InterruptedException e) {e.printStackTrace();}} finally {unlock();}}}
}

Untitled

LockSupport的park/unpark实现

/*** 使用park/unpark来实现三个线程交替打印 abcabcabcabcabc** @author xiexu* @create 2022-02-03 12:50 AM*/
@Slf4j(topic = "c.TestWaitNotify")
public class TestParkUnpark {static Thread t1;static Thread t2;static Thread t3;public static void main(String[] args) {ParkUnpark parkUnpark = new ParkUnpark(5);t1 = new Thread(() -> {parkUnpark.print("a", t2);}, "t1");t2 = new Thread(() -> {parkUnpark.print("b", t3);}, "t2");t3 = new Thread(() -> {parkUnpark.print("c", t1);}, "t3");t1.start();t2.start();t3.start();LockSupport.unpark(t1); //首先唤醒 t1 线程}
}class ParkUnpark {// 循环次数private final int loopNumber;public ParkUnpark(int loopNumber) {this.loopNumber = loopNumber;}/*** @param str* @param nextThread 下一个要唤醒的线程*/public void print(String str, Thread nextThread) {for (int i = 0; i < loopNumber; i++) {LockSupport.park(); // 阻塞住当前线程System.out.print(str);LockSupport.unpark(nextThread); // 唤醒下一个线程}}
}

Untitled

本章小结

Untitled

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 简单工厂、工厂模式、抽象工厂模式、迭代器模式原理及实例

    工厂模式 三种类型&#xff0c;简单工厂、工厂模式、抽象工厂。 将实例的创建交给工厂&#xff0c;像现实生活中的工厂一样批量生产产品。 简单工厂模式 基本介绍 简单工厂模式是属于创建型模式&#xff0c;是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产…...

    2024/4/13 17:58:36
  2. ZZULI OJ 1126: 布尔矩阵的奇偶性 C++(带你看懂每一步)

    1126: 布尔矩阵的奇偶性 题目描述 一个布尔方阵具有奇偶均势特性&#xff0c;当且仅当 每行、每列总和为偶数&#xff0c;即包含偶数个1。如下面这个4*4的矩阵就具有奇偶均势特性&#xff1a; 1 0 1 0 0 0 0 0 1 1 1 1 0 1 0 1 编写程序&#…...

    2024/5/2 13:46:57
  3. AQS基本原理

    什么是AQS&#xff1f; AQS即AbstractQueuedSynchronizer,是一个用于构建锁和同步器的框架。它能降低构建锁和同步器的工作量&#xff0c;还可以避免处理多个位置上发生的竞争问题。在基于AQS构建的同步器中&#xff0c;只可能在一个时刻发生阻塞&#xff0c;从而降低上下文切…...

    2024/4/13 17:58:21
  4. 2018年递增三元组{巧解}

    lower_bound头文件是algorithm 其用的是二分的思想。 #include<bits/stdc.h>//万能头。 using namespace std; int main(void) {int i,j,k,n,a[100],b[100],c[100],sum0;cin>>n;for(i0;i<n;i){cin>>a[i];}for(i0;i<n;i){cin>>b[i];}for(i0;i…...

    2024/4/28 3:12:55
  5. 虚拟存储系统

    虚拟存储器 和操作系统中的一样页式存储 我们的程序很大&#xff0c;需要连续的主存空间不现实所以将一个程序&#xff0c;在逻辑上拆分为若干个大小相等的页面&#xff0c;离散的放入不同的主存块中 每个页面大小和物理块的大小相同 两个地址 逻辑地址&#xff1a;程序员视角…...

    2024/4/13 17:58:31
  6. 使用Vue.js搭建后台管理系统

    vue.js是核心 * element UI是一个支持vue.js的UI组件库 * mock.js&#xff1a;由于没有后端接口&#xff0c;所以需要使用本地数据来假装成后端的数据。 文章目录技术栈初始化项目项目结构分析用法根实例根实例管理区域渲染页面修改首页注册组件router-view修改初始化页面使用路…...

    2024/4/13 17:58:31
  7. RK3399平台开发系列讲解(同步与互斥篇)12.6、RCU原理与实现

    平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 文章目录 一、RCU 原理二、RCU 使用2.1、RCU 读者2.2、RCU 更新者三、RCU 实现3.1、rcu_data 结构3.2、时钟中断沉淀、分享、成长,让自己和他人都能有所收获!😄 📢...

    2024/4/25 15:33:03
  8. ROS 学习笔记(10)—— 坐标系管理系统

    坐标系管理系统步骤一&#xff1a;安装软件包步骤二&#xff1a;启动 launch 脚本步骤三&#xff1a;启动键盘控制节点步骤四&#xff1a;修改文件步骤五&#xff1a;运行坐标可视化工具步骤六&#xff1a;通过命令行工具获取坐标系之间的位置关系步骤七&#xff1a;运行 rviz …...

    2024/4/13 17:58:41
  9. 运行时异常和编译异常

    1.概念&#xff1a; Java中将程序执行中发生的不正常情况称为异常(Exception) 2.分类 Error(错误)&#xff1a;JVM无法解决的严重问题&#xff0c;程序会崩溃&#xff0c;比如JVM系统内部错误、资源耗尽等Exception&#xff1a;因编程错误等外在因素导致的一般性问题&#xff0…...

    2024/4/7 20:16:02
  10. JavaScript(7):闭包

    在百度百科中&#xff0c;有闭包的解释。   【百度百科】官方对闭包的解释是&#xff1a;一个拥有许多变量和绑定了这些变量的环境的表达式&#xff08;通常是一个函数&#xff09;&#xff0c;因而这些变量也是该表达式的一部分。   【百度百科】闭包的特点&#xff1a;  …...

    2024/4/13 17:58:31
  11. 程序员文档写作能力(三)-如何处理好微信、邮件、开会时的话术

    简介 前面第二篇中我详细列举了写文档的虎头、猪肚、豹尾的大三段式。这是一种手法也是一种技巧。实际上这种方法论同样适合在平日工作中在工作群里发微信、邮件来往、开会时的报告甚至一些企业内部“PK会”。特别是像领导报告时这种手法用的特别多。 而这一篇要讲述的内容比…...

    2024/4/27 2:47:44
  12. vue实现 文件重命名

    1.定义一个弹框 <el-dialogtitle"文件重命名":visible.sync"RenameDialogVisible"width"40%"close"resetForm(FileFormRef)":close-on-click-modal"false"><el-form:model"FileForm"ref"FileFormRe…...

    2024/4/18 23:48:03
  13. 三天学会网络爬虫之Day02

    三天学会网络爬虫之Day02 第一章 课程计划第二章 2. WebMagic介绍2.1. 架构介绍2.1.1. WebMagic的四个组件2.1.2. 用于数据流转的对象2.2. 入门案例2.2.1. 加入依赖2.2.2. 加入配置文件2.2.3. 案例实现第三章 WebMagic功能3.1. 实现PageProcessor3.1.1. 抽取元素Selectable3.1.…...

    2024/4/13 17:59:21
  14. Spark追妻系列(Value类型的RDD)

    今天是大年初三&#xff0c;猴赛雷 小谈 这几天每天晚上给她发一个红包&#xff0c;拜年红包而且还可以添加表情包。感觉现在过年好没有年味吖。嗑瓜子磕的嗓子都疼了。 Spark中的算子有很多&#xff0c;有Value类型&#xff0c;双Value类型&#xff0c;这两天写的都是Value类型…...

    2024/4/20 10:22:39
  15. 赫夫曼编码的实现与分析

    目录 一.赫夫曼编码的基本实现 二&#xff0c;数据压缩&#xff08;赫夫曼树的应用举例&#xff09; 1.创建赫夫曼树 2.生成赫夫曼编码和赫夫曼编码后的数据 3.使用赫夫曼编码解码 三&#xff0c;赫夫曼树对文件的解压缩 四&#xff0c;赫夫曼编码压缩文件注意事项 一.赫…...

    2024/4/13 17:59:06
  16. buuctf初学者学习记录--web第16题

    [RoarCTF 2019]Easy Calc 打开靶场&#xff1a; 查看源代码&#xff0c;发现有一个calc.php文件&#xff0c;并且提示设置了waf 先尝试访问calc.php 得到源码&#xff0c;使用get方式传参赋值给num&#xff0c;并且使用正则表达式进行了过滤&#xff0c;只要能绕过过滤&…...

    2024/4/19 15:28:49
  17. POJ--Catch That Cow(BFS最短路)

    Catch That CowTime Limit: 2000MSMemory Limit: 65536KTotal Submissions: 194038Accepted: 58782Description Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a numb…...

    2024/4/13 17:59:21
  18. 2022.2.3 java学习笔记

    2022.2.2 问题 import java.io.File; import java.io.IOException;public class one {public static void main(String[] args){File file new File("E:\\test");File file1 new File(file,"one");System.out.println(file1.isDirectory());System.out.…...

    2024/4/13 17:59:11
  19. Flutter教程之Windows桌面应用程序开发

    Flutter教程之Windows桌面应用程序开发一、前言二、环境安装三、创建项目四、已有项目添加其他平台支持一、前言 Flutter 是 Google 开源的 UI 工具包&#xff0c;帮助开发者通过一套代码库高效构建多平台精美应用&#xff0c;支持移动、Web、桌面和嵌入式平台。中文官网是 ht…...

    2024/4/17 6:07:09
  20. vue组件化(vue2)

    vue组件化 任何应用都可以抽象成一颗组件树 组件注册过程 创建组件构造器 Vue.extend 要传一个对象,一个模板注册组件 Vue.component使用组件 //1.第一种方法//vue cli 脚手架构造vue项目 vue构造vue//必须得放在实例中&#xff0c;比如<div>标签下&#xff0c;否则无…...

    2024/4/26 7:45:34

最新文章

  1. 「笔试刷题」:数组中的最长连续子序列

    一、题目 描述 给定无序数组arr&#xff0c;返回其中最长的连续序列的长度(要求值连续&#xff0c;位置可以不连续,例如 3,4,5,6为连续的自然数&#xff09; 数据范围&#xff1a;1≤n≤10^5&#xff0c;数组中的值满足 1≤val≤10^8 要求&#xff1a;空间复杂度 O(n)&#…...

    2024/5/2 14:23:08
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. dp小兰走迷宫

    昨天学习了bfs的基本概念&#xff0c;今天来做一道经典习题练练手吧&#xff01; bfs常用的两类题型 1.从A出发是否存在到达B的路径(dfs也可) 2.从A出发到B的最短路径&#xff08;数小:<20才能用dfs&#xff09; 遗留的那个问题的答案- 题目&#xff1a;走迷宫 #incl…...

    2024/5/2 10:47:23
  4. 解决npm install安装node-sass包容易失败的问题

    具体问题如下&#xff1a; npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: XXX3.4.0 npm ERR! Found: webpack5.31.2 npm ERR! node_modules/webpack npm ERR! peer webpack”^4.0.0 || ^5.0.0″ from html-…...

    2024/5/1 13:13:30
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57