并发编程---synchronized关键字,以及synchronized同步锁
文章目录
- Synchronized 的使用
- synchronized 在普通方法上的使用(对象锁)
- `synchronized` 在静态方法上的使用(类锁)
- `synchronized` 在代码块上的使用
- JVM 中锁的优化
- 锁的类型
- 自旋锁与自适应自旋锁
- 自旋锁(Spin Lock)
- 工作原理:
- 优点:
- 缺点:
- 自适应自旋锁(Adaptive Spin Lock)
- 工作原理:
- 优点:
- 缺点:
- 自旋锁 vs 自适应自旋锁
- 无锁(No Lock)
- 典型应用:
- 优点:
- 缺点:
- 偏向锁
- 工作原理:
- 偏向锁的撤销
- 优点:
- 缺点:
- 轻量锁(Lightweight Locking)
- 轻量级锁加锁
- 工作原理:
- 优点:
- 缺点:
- 重量锁(Heavyweight Locking)
- 工作原理:
- 优点:
- 缺点:
- 总结
synchronized
是 Java 提供的一种内置的同步机制,用来保证多线程并发时对共享资源的访问是线程安全的。它通过获取和释放锁来实现线程之间的同步。synchronized
可以用于普通方法、静态方法以及代码块上,但其具体行为和性能特性有所不同。
Synchronized 的使用
在应用 Sychronized 关键字时需要把握如下注意点:
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
- 每个实例都对应有自己的一把锁(this), 不同实例之间互不影响;例外:锁对象是*.class 以及 synchronized 修饰的是 static 方法的时候,所有对象公用同一把锁
- synchronized 修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
synchronized 在普通方法上的使用(对象锁)
- 锁住的是当前对象实例:当
synchronized
修饰一个普通实例方法时,它锁住的是 当前实例对象(this
),即方法所属的对象实例。每个对象实例有一个独立的锁。 - 作用:多个线程访问同一个对象的实例方法时,只有一个线程能够获取该对象实例的锁,确保同一时刻只有一个线程可以执行该方法,避免多个线程同时修改实例变量导致数据不一致的问题。
示例:
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance = new SynchronizedObjectLock();@Overridepublic void run() {method();}// 这个方法被 synchronized 修饰,意味着它是同步的,只有一个线程可以访问public synchronized void method() {// 输出当前线程的名称System.out.println("我是线程 " + Thread.currentThread().getName());try {// 模拟处理过程,当前线程休眠 3 秒钟Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 输出当前线程结束System.out.println(Thread.currentThread().getName() + " 结束");}public static void main(String[] args) {// 创建两个线程,两个线程共享同一个对象实例 instanceThread t1 = new Thread(instance);Thread t2 = new Thread(instance);// 启动线程t1.start();t2.start();}
}
执行过程:
- 线程启动: 在
main()
方法中,我们创建了两个线程t1
和t2
,它们都会调用instance
的method()
方法。 method()
被同步:由于method()
被synchronized
修饰,它是一个 对象锁。意味着这两个线程 必须共享同一个对象锁,并且 同一时刻只有一个线程可以进入method()
。- 线程执行:
- 当
t1
第一个获得锁并执行method()
时,t2
会被阻塞,直到t1
执行完并释放锁。 t1
执行完后,t2
才能进入method()
方法。
- 当
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
synchronized
在静态方法上的使用(类锁)
- 锁住的是类的
Class
对象:当synchronized
修饰一个静态方法时,它锁住的是 类的Class
对象,而不是对象实例(this
)。因此,所有实例共享同一个锁。 - 作用:静态方法属于类,而不是对象,所以多个线程访问同一个类的静态方法时,只有一个线程能获得类级别的锁,确保静态方法在同一时刻只有一个线程执行。
示例:
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {method();}// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把public static synchronized void method() {System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}
执行过程:
- 线程
t1
启动后,它会进入method()
方法并获取类锁,进入方法并休眠 3 秒。 - 线程
t2
启动后,由于method()
是静态方法并且使用了synchronized
,线程t2
必须等待线程t1
执行完并释放类锁才能进入method()
方法。
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
synchronized
在代码块上的使用
- 锁住特定的对象:当
synchronized
修饰一个代码块时,可以通过指定锁对象来控制锁的粒度。这样可以锁住特定的对象,而不是整个方法。通常使用synchronized
来锁住一些共享的资源或者代码块,防止多个线程同时访问导致线程安全问题。 - 锁对象:可以选择任何对象作为锁对象,通常是实例对象(
this
)或自定义的锁对象。
示例 1:
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance = new SynchronizedObjectLock();@Overridepublic void run() {// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行synchronized (this) {System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();}
}
执行过程:
- 初始化:创建了一个
SynchronizedObjectLock
的实例instance
,并将其作为Runnable
实现传递给两个线程(t1
和t2
)。 - 线程启动:
t1
和t2
分别调用start()
方法启动两个线程。 - 同步代码块:线程执行
run()
方法时,进入synchronized (this)
块。这里的this
表示当前的SynchronizedObjectLock
对象,因此t1
和t2
线程对同一个instance
实例加锁。 - 线程执行:由于
t1
和t2
都在争夺同一个对象的锁,它们会按照顺序一个一个地获取锁。第一个获取锁的线程会执行System.out.println("我是线程" + Thread.currentThread().getName())
打印出自己的线程名,然后进入try
块,执行Thread.sleep(3000)
来模拟一个耗时的操作。在Thread.sleep(3000)
期间,该线程会持有锁,并且阻塞自己 3 秒钟,其他线程在此期间无法获取锁。 - 线程交替执行:假设
t1
先获得锁并执行,t2
会被阻塞,直到t1
释放锁。t1
执行完毕后,释放锁,t2
获得锁,执行相同的代码块,最终打印出t2
的线程信息。
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
示例 2:
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance = new SynchronizedObjectLock();// 创建2把锁Object block1 = new Object();Object block2 = new Object();@Overridepublic void run() {// 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行synchronized (block1) {System.out.println("block1锁,我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");}synchronized (block2) {System.out.println("block2锁,我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();}
}
执行过程:
-
初始化:
-
创建了一个
SynchronizedObjectLock
的实例instance
,并将其作为Runnable
实现传递给两个线程(t1
和t2
)。 -
在
SynchronizedObjectLock
类中,定义了两个锁对象block1
和block2
,分别对应两个不同的同步代码块。
-
-
线程启动:
t1
和t2
通过start()
启动,开始执行run()
方法。
-
同步代码块:
-
线程首先执行第一个同步代码块,
synchronized (block1)
,此时线程会争夺block1
锁。假设t1
先获得了block1
锁,它就会执行这个代码块中的代码,输出:block1锁,我是线程Thread-0
-
t1
会在Thread.sleep(3000)
期间持有block1
锁 3 秒钟,这时,t2
线程如果到达此代码块,则会被阻塞,直到t1
释放block1
锁。
-
-
第二个同步代码块:
-
同时,由于
block1
和block2
是两个不同的锁对象,当t1
执行完第一个同步代码块并释放了block1
锁后,t2
可以继续执行第一个同步代码块,但它会进入synchronized (block2)
这一段代码。 -
block2
锁是独立的,因此t2
不会被t1
的block1
锁阻塞。t2
进入第二个同步代码块后会输出:block2锁,我是线程Thread-1
-
输出结果:
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
示例 3:
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {// 所有线程需要的锁都是同一把synchronized(SynchronizedObjectLock.class){System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}
执行过程:
-
创建实例:
-
创建了两个
SynchronizedObjectLock
的实例:instance1
和instance2
。 -
但是,重要的是,尽管这两个实例是不同的对象,但同步块是通过
SynchronizedObjectLock.class
来锁定的。
-
-
同步的本质:
-
synchronized(SynchronizedObjectLock.class)
锁定的是SynchronizedObjectLock
类的Class
对象,而不是instance1
或instance2
的实例对象。 -
这意味着,所有线程无论是通过
instance1
还是instance2
启动,只要它们访问到这个同步代码块,都将争夺同一个类对象锁。因此,无论线程使用哪个实例,都会竞争SynchronizedObjectLock.class
这一把类锁。
-
-
线程启动:
-
线程
t1
使用instance1
启动,线程t2
使用instance2
启动。 -
尽管它们操作的是不同的实例(
instance1
和instance2
),但是因为同步的锁是类的锁SynchronizedObjectLock.class
,所以它们会相互阻塞。
-
-
执行过程:
-
当线程
t1
执行时,它会首先获取SynchronizedObjectLock.class
锁,然后进入同步代码块。此时,t2
不能进入同步代码块,必须等待t1
完成释放锁后才能进入。 -
线程
t1
会输出:我是线程Thread-0
然后
Thread.sleep(3000)
阻塞 3 秒钟。 -
在
t1
执行完毕并释放类锁后,t2
才可以获取锁,进入同步代码块,输出:我是线程Thread-1
然后再阻塞 3 秒钟,完成后输出:
Thread-1结束
-
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
JVM 中锁的优化
锁的类型
在 Java SE 1.6 里 Synchronied 同步锁,一共有四种状态:无锁
、偏向锁
、轻量级锁
、重量级锁
,它会随着竞争情况逐渐升级。锁可以升级但是不可以降级,目的是为了提供获取锁和释放锁的效率。
锁膨胀方向: 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 (此过程是不可逆的)
自旋锁与自适应自旋锁
自旋锁(Spin Lock)
自旋锁是一种基于忙等待(busy-waiting)机制的锁。在自旋锁中,当一个线程试图获取锁时,如果锁已经被其他线程占用,它会 反复检查锁的状态,而不是进入阻塞状态。这种机制叫做“自旋”,因为线程一直在执行而没有进入休眠。
工作原理:
- 线程获取锁时,如果锁未被占用,线程就可以成功获取锁并执行任务。
- 如果锁已经被占用,线程不会被挂起,而是不断地检查锁的状态(自旋),直到锁被释放。
- 如果自旋的时间很长,线程仍然没有获得锁,它可能会转为 阻塞 状态(例如进入操作系统的调度队列)。
优点:
- 在锁争用不激烈的情况下,自旋锁的开销小,避免了线程的上下文切换和系统调用,性能高效。
- 对于临界区非常短的情况,自旋锁可以大幅减少因线程阻塞而产生的开销。
缺点:
- 自旋消耗 CPU 资源:如果线程自旋等待时间过长,CPU 资源会被浪费,降低系统效率。
- 如果锁竞争激烈,线程自旋的时间会很长,造成性能问题。
示例:自旋锁实现
public class SpinLockExample {// 使用 volatile 修饰 lock,确保多线程间的可见性private volatile boolean lock = false;// 锁定方法public void lock() {while (true) {// 如果锁没有被占用,尝试获取锁if (!lock) {// 将 lock 设置为 true,表示当前线程已占有锁lock = true;break; // 获取锁成功,退出循环}}}// 解锁方法public void unlock() {// 将 lock 设置为 false,释放锁lock = false;}public static void main(String[] args) throws InterruptedException {// 创建 SpinLockExample 实例SpinLockExample spinLock = new SpinLockExample();// 任务:获取锁并执行工作,之后释放锁Runnable task = () -> {// 获取锁spinLock.lock();System.out.println(Thread.currentThread().getName() + " obtained the lock");try {// 模拟任务处理,休眠 100msThread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 释放锁spinLock.unlock();System.out.println(Thread.currentThread().getName() + " released the lock");};// 创建并启动两个线程,执行 task 任务Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start(); // 启动线程 t1t2.start(); // 启动线程 t2}
}
在这个例子中,当一个线程尝试获取锁时,它会自旋等待,直到锁被释放。这个实现是一个非常基础的自旋锁。
自适应自旋锁(Adaptive Spin Lock)
工作原理:
- 自适应自旋锁会根据 锁竞争的历史(如上一次自旋失败的次数)调整自旋的时间。当锁竞争较少时,自旋时间较短;而当锁竞争较激烈时,自旋时间会适当增加。
- JVM 或操作系统会根据硬件、操作系统调度和锁的特性调整自旋次数。
- 在大多数现代 JVM 中,操作系统和 JVM 本身会做自适应优化。比如,HotSpot JVM 使用了自适应自旋机制,称为 Adaptive Spin Lock,它根据前一段时间自旋的情况调整自旋的次数,通常是基于硬件的缓存和上下文切换的成本进行调优。
优点:
- 更智能的自旋:根据当前的锁竞争情况动态调整自旋时间,提高了性能。
- 降低了 CPU 的浪费:如果锁竞争较少,自旋时间较短;如果锁竞争激烈,则减少自旋转而进入阻塞状态。
缺点:
- 需要较复杂的实现,可能比简单的自旋锁稍微消耗更多的资源。
- 对系统的硬件和 JVM 的支持要求较高。
示例:自适应自旋锁的实现
public class AdaptiveSpinLockExample {// 使用 volatile 修饰 lock,确保多线程间的可见性private volatile boolean lock = false;// 最大自旋次数,当自旋次数超过此阈值时,进行休眠private static final int MAX_SPIN_COUNT = 100;// 锁定方法public void lock() {int spinCount = 0; // 用于记录自旋次数while (true) {// 如果锁没有被占用,尝试获取锁if (!lock) {// 获取锁,设置 lock 为 truelock = true;break; // 获取锁成功,退出循环} else {spinCount++; // 自旋次数加 1// 如果自旋次数超过最大自旋次数,进行休眠来减少 CPU 占用if (spinCount > MAX_SPIN_COUNT) {try {// 休眠 1 毫秒,避免过多的 CPU 占用Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace(); // 捕获并处理 InterruptedException}}}}}// 解锁方法public void unlock() {// 释放锁,将 lock 设置为 falselock = false;}public static void main(String[] args) throws InterruptedException {// 创建 AdaptiveSpinLockExample 实例AdaptiveSpinLockExample adaptiveLock = new AdaptiveSpinLockExample();// 任务:获取锁并执行工作,之后释放锁Runnable task = () -> {adaptiveLock.lock(); // 获取锁System.out.println(Thread.currentThread().getName() + " obtained the lock");try {// 模拟任务处理,休眠 100 毫秒Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}adaptiveLock.unlock(); // 释放锁System.out.println(Thread.currentThread().getName() + " released the lock");};// 创建并启动两个线程,执行 task 任务Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start(); // 启动线程 t1t2.start(); // 启动线程 t2}
}
在这个例子中,自适应自旋锁会根据自旋的次数来决定是否进行线程休眠,从而减少 CPU 资源浪费。如果自旋次数过多,则线程会进入休眠,避免一直占用 CPU。
自旋锁 vs 自适应自旋锁
自旋锁:
- 总是进行固定次数的自旋。
- 适用于锁竞争非常小、锁的持有时间很短的场景。
- 自旋时间不变,可能导致不必要的 CPU 资源浪费。
自适应自旋锁:
- 动态调整自旋时间,基于锁的竞争情况和历史自旋情况来决定自旋的次数。
- 适用于锁竞争情况较为复杂的场景,可以减少不必要的自旋带来的性能损失。
- 自适应自旋锁通过调节自旋次数和引入适当的线程休眠,能够更高效地利用 CPU 资源。
总结:
- 自旋锁 适合于 低竞争 和 短时间锁持有 的场景,通过不断尝试获取锁来避免线程的上下文切换,但长时间的自旋会浪费大量 CPU 资源。
- 自适应自旋锁 在自旋的基础上加入了动态调节的机制,能够根据实际的锁竞争情况来调整自旋次数,避免了过多的自旋等待和 CPU 浪费,是一种更智能的自旋机制。
无锁(No Lock)
无锁 机制指的是 没有使用任何锁 来保证线程安全,而是通过其他并发控制机制来实现线程同步。比如,利用原子操作(AtomicInteger
、AtomicLong
等)来保证数据的原子性和一致性。
典型应用:
- CAS(Compare and Swap):原子操作
compareAndSet
,通过对内存进行比较并更新的方式实现无锁操作。 - 无锁数据结构:如无锁队列、无锁栈等。
优点:
- 性能最高,不需要线程争抢锁资源。
- 不会发生死锁。
缺点:
- 适用于某些场景,如计数器、栈等,对于复杂的共享资源操作,难以实现。
偏向锁
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID,以后该线程在进入和退出同步块时不需要进行 CAS 操作来加锁和解锁。只需要简单的测试一下对象头的 Mark Word
里是否存储着指向当前线程的偏向锁。如果成功,表示线程已经获取到了锁。
偏向锁是一种优化技术,它在单线程访问锁的情况下,不会加锁。当一个线程访问一个对象时,它会认为自己“偏向”该对象,并且后续不再加锁,直到发生竞争时才会升级为轻量锁。
工作原理:
- 偏向锁的初衷是 减少不必要的锁操作,当一个线程获得锁时,JVM 会将锁标记为偏向锁,并且在该线程后续访问该对象时,JVM 不会进行加锁操作。
- 如果没有其他线程竞争,偏向锁会一直存在。
- 当另一个线程尝试访问该对象时,偏向锁会被撤销,升级为轻量锁。
偏向锁的撤销
偏向锁使用了一种等待竞争出现才会释放锁的机制。所以当其他线程尝试获取偏向锁时,持有偏向锁的线程才会释放锁。但是偏向锁的撤销需要等到全局安全点(就是当前线程没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,让你后检查持有偏向锁的线程是否活着。如果线程不处于活动状态,直接将对象头设置为无锁状态。如果线程活着,JVM 会遍历栈帧中的锁记录,栈帧中的锁记录和对象头要么偏向于其他线程,要么恢复到无锁状态或者标记对象不适合作为偏向锁。
优点:
- 在单线程访问的情况下,避免了加锁的开销,提高了性能。
缺点:
- 如果多个线程竞争锁,偏向锁会被撤销,锁会升级为轻量锁或重量锁,带来额外的开销。
偏向锁示例:
public class BiasedLockingExample {// 定义一个静态的锁对象,所有线程将竞争这个锁private static Object lock = new Object();public static void main(String[] args) throws InterruptedException {// 创建线程 t1,它尝试获取锁并执行同步代码块Thread t1 = new Thread(() -> {// 使用 synchronized 关键字获取锁synchronized (lock) {// 当线程 t1 获取到锁后,执行以下代码System.out.println("Thread 1 holds the lock.");}// 同步代码块执行完毕后,锁会自动释放});// 创建线程 t2,它同样尝试获取锁并执行同步代码块Thread t2 = new Thread(() -> {// 使用 synchronized 关键字获取锁synchronized (lock) {// 当线程 t2 获取到锁后,执行以下代码System.out.println("Thread 2 holds the lock.");}// 同步代码块执行完毕后,锁会自动释放});// 启动线程 t1t1.start();// 启动线程 t2t2.start();// 使用 join() 方法等待线程 t1 和 t2 执行完毕t1.join();t2.join();// 主线程在所有子线程执行完毕后继续执行System.out.println("Main thread finished.");}
}
在上面的例子中,lock
最初会处于偏向锁状态,如果线程 t1
一直持有锁,那么就不会有加锁开销,直到 t2
争夺该锁时,才会升级为轻量锁。
轻量锁(Lightweight Locking)
轻量锁是一种用于减少线程竞争时加锁的开销的优化技术。轻量锁通过 自旋锁 机制实现,适用于没有线程竞争或线程竞争非常短的情况下。
如果要理解轻量级锁,那么必须先要了解 HotSpot 虚拟机中对象头的内存布局。上面介绍 Java 对象头也详细介绍过。在对象头中(Object Header
)存在两部分。第一部分用于存储对象自身的运行时数据,HashCode
、GC Age
、锁标记位
、是否为偏向锁
。等。一般为 32 位或者 64 位(视操作系统位数定)。官方称之为 Mark Word
,它是实现轻量级锁和偏向锁的关键。 另外一部分存储的是指向方法区对象类型数据的指针(Klass Point
),如果对象是数组的话,还会有一个额外的部分用于存储数据的长度。
轻量级锁加锁
在线程执行同步块之前,JVM 会先在当前线程的栈帧中创建一个名为锁记录(Lock Record
)的空间,用于存储锁对象目前的 Mark Word
的拷贝(JVM 会将对象头中的 Mark Word
拷贝到锁记录中,官方称为 Displaced Mark Ward
)这个时候线程堆栈与对象头的状态如图:
如上图所示:如果当前对象没有被锁定,那么锁标志位为 01 状态,JVM 在执行当前线程时,首先会在当前线程栈帧中创建锁记录 Lock Record
的空间用于存储锁对象目前的 Mark Word
的拷贝。
然后,虚拟机使用 CAS 操作将标记字段 Mark Word 拷贝到锁记录中,并且将 Mark Word
更新为指向 Lock Record
的指针。如果更新成功了,那么这个线程就拥用了该对象的锁,并且对象 Mark Word 的锁标志位更新为(Mark Word
中最后的 2bit)00,即表示此对象处于轻量级锁定状态,如图:
如果这个更新操作失败,JVM 会检查当前的 Mark Word
中是否存在指向当前线程的栈帧的指针,如果有,说明该锁已经被获取,可以直接调用。如果没有,则说明该锁被其他线程抢占了,如果有两条以上的线程竞争同一个锁,那轻量级锁就不再有效,直接膨胀为重量级锁,没有获得锁的线程会被阻塞。此时,锁的标志位为 10.Mark Word
中存储的指向重量级锁的指针。
轻量级解锁时,会使用原子的 CAS 操作将 Displaced Mark Word
替换回到对象头中,如果成功,则表示没有发生竞争关系。如果失败,表示当前锁存在竞争关系。锁就会膨胀成重量级锁。两个线程同时争夺锁,导致锁膨胀的流程图如下:
因为自旋会消耗 CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程视图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。
工作原理:
- 当一个线程第一次尝试获取锁时,JVM 会在对象的 锁记录 中保存线程的信息(即锁标识符)。如果没有其他线程竞争,线程会直接获得锁。
- 如果该对象已经被其他线程持有,JVM 会让当前线程 自旋等待,如果自旋成功,则会继续执行。如果自旋失败,则会升级为重量锁。
优点:
- 当只有一个线程持有锁时,性能几乎不受影响。
- 减少了线程上下文切换的开销。
缺点:
- 线程竞争时的性能较低,可能导致 CPU 浪费。
- 适用于线程竞争较少的场景。
轻量锁示例:
public class LightweightLockExample {// 定义一个静态的锁对象,所有线程将竞争这个锁private static Object lock = new Object();public static void main(String[] args) throws InterruptedException {// 创建线程 t1,它尝试获取锁并执行同步代码块Thread t1 = new Thread(() -> {// 使用 synchronized 关键字获取锁synchronized (lock) {// 当线程 t1 获取到锁后,执行以下代码System.out.println("Thread 1 holds the lock.");// 模拟任务执行,线程 t1 休眠 100 毫秒try {Thread.sleep(100);} catch (InterruptedException e) {// 如果线程在休眠期间被中断,打印异常信息e.printStackTrace();}}// 同步代码块执行完毕后,锁会自动释放});// 创建线程 t2,它同样尝试获取锁并执行同步代码块Thread t2 = new Thread(() -> {// 使用 synchronized 关键字获取锁synchronized (lock) {// 当线程 t2 获取到锁后,执行以下代码System.out.println("Thread 2 holds the lock.");}// 同步代码块执行完毕后,锁会自动释放});// 启动线程 t1t1.start();// 启动线程 t2t2.start();// 使用 join() 方法等待线程 t1 和 t2 执行完毕t1.join();t2.join();// 主线程在所有子线程执行完毕后继续执行System.out.println("Main thread finished.");}
}
在上面的例子中,lock
对象初始是轻量锁。线程 t1
先获取锁并在执行时进行了睡眠,线程 t2
会在 t1
释放锁之前自旋,最终成功获得锁。
重量锁(Heavyweight Locking)
重量锁通常也叫做互斥锁,它是最传统的锁机制。当线程竞争激烈时,JVM 会将轻量锁升级为重量锁,以此保证线程安全。重量锁依赖于操作系统的 互斥量(mutex) 来实现同步。
工作原理:
- 当线程在获取轻量锁时,如果发生竞争,会导致锁的升级。
- 锁的升级会引发阻塞操作,即当前线程会被挂起,直到能够获得锁。
- JVM 会使用 操作系统的原子操作 来控制锁的获取。
优点:
- 适用于竞争非常激烈的场景,能够保证线程安全。
- 通过操作系统的调度机制来处理锁竞争,具有较强的可靠性。
缺点:
- 锁的升级会带来线程上下文切换的开销,导致性能下降。
- 如果发生频繁的锁竞争,会严重影响程序性能。
重量锁示例:
public class HeavyweightLockExample {// 定义一个静态的锁对象,所有线程将竞争这个锁// 由于锁对象是静态的,因此它在类加载时就被创建,并且所有线程共享同一个锁private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {// 创建多个线程,模拟高并发竞争// threadCount 表示要创建的线程数量int threadCount = 100;Thread[] threads = new Thread[threadCount];// 启动多个线程来竞争锁for (int i = 0; i < threadCount; i++) {// 创建线程,每个线程都会执行相同的任务threads[i] = new Thread(() -> {// 使用 synchronized 关键字来获取锁// 只有获取到锁的线程才能进入同步块synchronized (lock) {// 模拟一些任务的执行,休眠一段时间让线程持有锁更长try {// 每个线程持有锁100毫秒Thread.sleep(100);} catch (InterruptedException e) {// 如果线程在休眠期间被中断,打印异常堆栈信息e.printStackTrace();}// 线程获取锁后打印当前线程的名称System.out.println(Thread.currentThread().getName() + " holds the lock.");}// 同步块结束后,线程释放锁,其他线程可以竞争获取锁});// 启动线程threads[i].start();}// 等待所有线程执行完毕// 使用 join() 方法确保主线程等待所有子线程执行完毕后再继续执行for (Thread t : threads) {t.join();}// 所有线程执行完毕后,打印结束信息System.out.println("All threads finished.");}
}
- 多个线程竞争锁:这个示例创建了 100 个线程(可以调整
threadCount
的值),它们都尝试获取相同的lock
锁。通过增加线程数,我们增加了锁竞争的情况。 Thread.sleep()
:每个线程在获取锁后,都会休眠 100 毫秒。这样做的目的是让每个线程持有锁的时间较长,从而增加了锁的持有时间,促使锁从轻量级锁升级为重量级锁。- 锁的升级:在高并发情况下,当多个线程竞争同一个锁并且线程持有锁的时间较长时,Java 会将该锁从轻量级锁升级为重量级锁。这意味着如果有线程已经持有该锁,其他线程将需要进入 阻塞状态,而不是像轻量级锁那样通过自旋的方式竞争锁。重量级锁会导致 线程阻塞 和 上下文切换,这就带来了性能损失。
总结
锁类型 | 解释 | 特点 | 适用场景 |
---|---|---|---|
无锁 | 通过原子操作(如 CAS )避免加锁,避免线程同步开销。 | - 无锁操作是通过硬件支持的原子操作实现的。 - 没有锁的开销。 | - 适用于高并发、读多写少的场景。 |
偏向锁 | 一种优化的锁机制,当线程访问锁时,会首先尝试将锁标记为偏向当前线程。 | - 适用于单线程长时间持有锁的场景。 - 在没有竞争的情况下,锁不会发生竞争。 | - 当一个线程多次访问同一个锁时,性能提升明显。 |
轻量锁 | 线程在进入临界区时,采用自旋锁的方式避免阻塞,只有当竞争激烈时,才会升级为重量锁。 | - 线程尝试使用自旋来竞争锁,只有在锁竞争激烈时才会升级为重量锁。 | - 适用于竞争较轻的场景。 |
重量锁 | 线程在竞争激烈时,采用传统的阻塞锁机制,性能开销较大。 | - 线程需要进入内核态来进行锁操作,性能开销较大。 - 使用操作系统的互斥量来实现。 | - 适用于高竞争场景,锁的持有时间较长。 |
-
无锁:完全避免锁的开销,适合高并发场景。
-
偏向锁:适用于单线程持有锁的场景,可以减少不必要的竞争。
-
轻量锁:适用于锁竞争较小的场景,使用自旋减少线程切换的开销。
-
重量锁:用于锁竞争激烈的场景,牺牲性能以确保正确性。
|
|------------|-------------------------------------------------------------|--------------------------------------------------------------|----------------------------------------|
| 无锁 | 通过原子操作(如CAS
)避免加锁,避免线程同步开销。 | - 无锁操作是通过硬件支持的原子操作实现的。
- 没有锁的开销。 | - 适用于高并发、读多写少的场景。 |
| 偏向锁 | 一种优化的锁机制,当线程访问锁时,会首先尝试将锁标记为偏向当前线程。 | - 适用于单线程长时间持有锁的场景。
- 在没有竞争的情况下,锁不会发生竞争。 | - 当一个线程多次访问同一个锁时,性能提升明显。 |
| 轻量锁 | 线程在进入临界区时,采用自旋锁的方式避免阻塞,只有当竞争激烈时,才会升级为重量锁。 | - 线程尝试使用自旋来竞争锁,只有在锁竞争激烈时才会升级为重量锁。 | - 适用于竞争较轻的场景。 |
| 重量锁 | 线程在竞争激烈时,采用传统的阻塞锁机制,性能开销较大。 | - 线程需要进入内核态来进行锁操作,性能开销较大。
- 使用操作系统的互斥量来实现。 | - 适用于高竞争场景,锁的持有时间较长。 | -
无锁:完全避免锁的开销,适合高并发场景。
-
偏向锁:适用于单线程持有锁的场景,可以减少不必要的竞争。
-
轻量锁:适用于锁竞争较小的场景,使用自旋减少线程切换的开销。
-
重量锁:用于锁竞争激烈的场景,牺牲性能以确保正确性。
相关文章:
并发编程---synchronized关键字,以及synchronized同步锁
文章目录 Synchronized 的使用synchronized 在普通方法上的使用(对象锁)synchronized 在静态方法上的使用(类锁)synchronized 在代码块上的使用 JVM 中锁的优化锁的类型自旋锁与自适应自旋锁自旋锁(Spin Lockÿ…...
Vue学习笔记5(Vue3)
Vue3学习笔记 一、create-vue搭建vue3项目 create-vue是vue官方新的脚手架工具,底层切换到了vite 步骤: 查看环境条件 node -v版本需要在16.0及以上创建一个vue应用 npm init vuelatest 这一指令会安装并执行create-vue 二、项目目录和关键文件 in…...
VoIP之音视频会议中的混音技术
在VoIP音视频会议中,需要将多路参会方音频流混合成一路音频流再发送给各参会方,以达到参会方可以听到每个与会人声音的目的,这种技术叫混音。 一、混音基础原理 在实际生活中,我们所处的生活和工作环境就是一个自然的混音场&…...
Baklib一站式云平台:全场景赋能企业知识资产激活
内容概要 在数字化浪潮推动下,企业知识资产的高效管理与价值释放成为核心议题。Baklib作为一站式云平台,以全场景赋能为核心定位,通过构建知识中台架构,为企业提供从资源整合到应用落地的闭环解决方案。该平台不仅支持文本、图像…...
基于nuScenes数据集和DeepSeek模型的端到端自动驾驶解决方案
结合DeepSeek模型进行知识蒸馏,以提高模型性能。这需要将nuScenes中的多模态数据(如摄像头图像、雷达点云、车辆状态等)整合到模型中,同时使用DeepSeek的生成能力进行蒸馏。 接下来,我需要考虑用户可能的背景。用户可能…...
《AI大模型开发笔记》deepseek提示词技巧
为什么你的 AI 助手总是答非所问? 「写篇产品分析」 → 收到一堆不知所云的文字 「做个竞品对比」 → 得到几页没有重点的废话 揭秘:不是 AI 不够聪明,而是你的指令太“高冷”! 一、新手进阶: 5 大法则,让…...
学习笔记-人脸识别相关编程基础
通过编程实现人脸识别功能,需要掌握一定的技术基础,包括编程语言、图像处理、机器学习以及相关的库和框架: 1. 编程语言 Python:Python 是实现人脸识别最常用的语言之一,因为它有大量的库和框架支持,如 Op…...
Java发展史
JavaEE的由来 语言的诞生 Java的前身是Oak语言,其目的是搞嵌入式开发开发智能面包机 叮~~~🍞🍞🍞 产品以失败告终 巅峰 网景公司需要网景浏览器打开网页,Oak->Java,进行前端开发(相关技…...
SAP-ABAP:SAP中REPORT程序和online程序的区别对比
在SAP中,REPORT程序和Online程序(通常指Dialog程序)是两种常见的ABAP程序类型,它们在用途、结构和用户交互方式上有显著区别。以下是它们的详细对比: 1. 用途 REPORT程序Online程序主要用于数据查询、报表生成和批量数…...
【第2章:神经网络基础与实现——2.1 前馈神经网络的结构与工作原理】
老铁们好!今天我们要来一场长达两万字的超详细技术探险,我会像拆解乐高积木一样把前馈神经网络(Feedforward Neural Network)的每个零件摆在台面上,用最接地气的方式让你彻底搞懂这个深度学习基石的工作原理。准备好了吗?我们开始吧! 第一章:神经网络的 “乐高积木” 1…...
Pythong 解决Pycharm 运行太慢
Pythong 解决Pycharm 运行太慢 官方给Pycharm自身占用的最大内存设低估了限制,我的Pycharm刚开始默认是256mb。 首先找到自己的Pycharm安装目录 根据合适自己的改 保存,重启Pycharm...
P6792 [SNOI2020] 区间和 Solution
Description 给定序列 a ( a 1 , a 2 , ⋯ , a n ) a(a_1,a_2,\cdots,a_n) a(a1,a2,⋯,an),有 m m m 个操作分两种: chmax ( l , r , v ) \operatorname{chmax}(l,r,v) chmax(l,r,v):对每个 i ∈ [ l , r ] i \in [l,r] i∈[l,…...
基于ArduPilot开发无人机飞控自动驾驶仪
目录 1、项目参数 2、硬件设计解析 2.1、主控与协处理器架构 2.2、高精度传感器集成 2.3、数据存储与恢复 2.4、电源管理与保护 2.5、通信与接口 本项目基于开源飞行控制固件 ArduPilot 开发,设计并实现了一款高度集成的 自动驾驶仪,可广泛应用于…...
Kotlin Lambda
Kotlin Lambda 在探索Kotlin Lambda之前,我们先回顾下Java中的Lambda表达式,Java 的 Lambda 表达式是 Java 8 引入的一项强大的功能,它使得函数式编程风格的代码更加简洁和易于理解。Lambda 表达式允许你以一种更简洁的方式表示实现接口&…...
UniApp 中制作一个横向滚动工具栏
前言 最近在用 UniApp 开发项目时,需要一个横向滑动的工具栏。常见的工具栏一般都是竖着的,但横向滑动的工具栏不仅能展示更多内容,还能让界面看起来更加丰富。不过很多朋友可能会发现,如何让内容“横着”展示又不变形、能流畅滚…...
Qt的QListWidget样式设置
以下是关于QListWidget样式设置的详细说明,包含常用样式配置和进阶技巧: 1. 基础列表样式 // 设置整体列表容器样式 listWidget->setStyleSheet("QListWidget {"" background-color: #f5f5f5;" // 背景颜色" borde…...
OpenCV 模板匹配
模板匹配算法是一种在目标图像中寻找与模板图像相似区域的方法,模板匹配就是拿一个模板图片在一张比模板图像要大的搜索图像上寻找与模板图像相似的区域,以此来得到目标在搜索图像上的位置,其核心是将模板图像在待搜索图像上从左到右、从上到下依次逐像素平移滑动,每次滑动…...
Vue 3 30天精进之旅:Day 25 - PWA支持
一、引言 在前面的24天中,我们已经深入探讨了Vue 3的许多核心概念和高级特性。今天,我们将进入一个全新的领域——PWA(Progressive Web App)。PWA是一种现代Web应用程序的开发模式,它结合了Web和原生应用的优点&#…...
arm linux下的中断处理过程。
本文基于ast2600 soc来阐述,内核版本为5.10 1.中断gic初始化 start_kernel() -> init_IRQ() -> irqchip_init() of_irq_init()主要是构建of_intc_desc. 489-514: 从__irqchip_of_table中找到dts node中匹配的of_table(匹配matches->compatible)…...
Linux上Elasticsearch 集群部署指南
Es 集群部署 Es 集群部署 Es 集群部署 准备好三台服务器。示例使用:110.0.5.141/142/143 1、es用户和用户组创建,使用root账号 groupadd esuseradd -g es es2、将es安装包和ik分词器上传到:/home/es/目录下(任意目录都行&#…...
SpringBoot+shardingsphere实现按月分表功能
SpringBootshardingsphere实现按月分表功能 文章目录 前言 ShardingSphere 是一套开源的分布式数据库中间件解决方案,旨在简化数据库分片、读写分离、分布式事务等复杂场景的管理。它由 Apache 软件基金会支持,广泛应用于需要处理大规模数据的系统中 一…...
如何设置 Nginx 连接超时并进行测试(Nginx优化)
🏡作者主页:点击! Nginx-从零开始的服务器之旅专栏:点击! 🐧Linux高级管理防护和群集专栏:点击! ⏰️创作时间:2025年2月15日14点22分 在高并发场景下,如…...
Python实现AWS Fargate自动化部署系统
一、背景介绍 在现代云原生应用开发中,自动化部署是提高开发效率和保证部署质量的关键。AWS Fargate作为一项无服务器计算引擎,可以让我们专注于应用程序开发而无需管理底层基础设施。本文将详细介绍如何使用Python实现AWS Fargate的完整自动化部署流程。 © ivwdcwso (ID…...
ubuntu20.04声音设置
step1:打开pavucontrol,设置Configuration和Output Devices, 注意需要有HDMI / DisplayPort (plugged in)这个图标。如果没有,就先选择Configuration -> Digital Stereo (HDMI 7) Output (unplugged) (unvailable),…...
AWS Database Migration Service
AWS Database Migration Service (DMS) 是亚马逊 Web 服务(AWS)提供的一项服务,旨在帮助用户将数据库迁移到 AWS 云环境中。无论是将现有的数据库迁移到 Amazon RDS(关系型数据库服务)、Amazon Aurora、Amazon Redshif…...
ROS学习
1.ROS工作空间 存放项目开发相关文件的文件夹; src:代码空间(Source Space)install:安装空间(Install Space)build:编译空间(Build Space)log:日志空间(Log Space) 2.c…...
【NLP 24、模型训练方式】
你的痛苦,我都心疼,想为你解决 —— 25.2.15 一、按学习范式分类 1. 监督学习(Supervised Learning) 核心思想:使用带有标签(已知输入-输出对)的数据训练模型。 常见任务:分类&…...
【算法】【区间和】acwing算法基础 802. 区间和 【有点复杂,但思路简单】
题目 假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。 现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。 接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] …...
DeepSeek 从入门到精通学习指南,2025清华大学《DeepSeek从入门到精通》正式发布104页pdf版超全解析
DeepSeek 是一款强大的 AI 搜索引擎,广泛应用于企业级数据检索和分析。无论您是初学者还是有经验的用户,掌握 DeepSeek 的使用都能为您的工作带来极大的便利。本文将从入门到精通,详细介绍如何学习和使用 DeepSeek。 链接: https://pan.baid…...
PyQt6/PySide6 的自定义信号实现
一、信号与槽基础概念 核心机制:观察者模式的实现,支持对象间的松耦合通信原生信号:内置控件(如QPushButton)的clicked等预定义信号自定义优势:实现业务逻辑解耦,增强组件复用性 PyQt6/PySide…...
什么是FCC认证
联邦通信委员会(FCC)认证是美国一种强制性的认证,确保电子设备在上市前符合特定标准。 联邦通讯委员会(FCC)----管理进口和使用无线电频率装置,包括电脑、传真机、电子装置、无线电接收和传输设备、无线电…...
共享设备管理难?MDM助力Kiosk模式一键部署
目录 1. 简化设备部署与配置:实现一键式部署 2. 自动化应用更新与内容推送:确保设备始终保持最新状态 3. 权限控制与设备安全:防止滥用与数据泄露 4. 远程管理与故障诊断:保障设备长期稳定运行 5. 数据分析与报告:…...
RAMinit 程序编译运行考古记录
本科的时候浅浅研究了DOSBox,今天看到网上挺多关于雷军代码的新闻,我把雷军代码在web上编译出来了,但是我是业余选手,所以做得比较差,不过大家感兴趣可以关注我的dosplay项目,旨在利用js-dos实现汇编语言在…...
今日AI和商界事件(2025-02-15)
根据2025年2月15日的科技动态,以下是今日AI领域的重要事件及相关进展总结: 1. DeepSeek日活突破3000万,开源生态加速AI普惠 里程碑意义:开源大模型DeepSeek宣布日活跃用户数突破3000万,其R1模型凭借开源策略和低成本优…...
鸿蒙开发:熟知@BuilderParam装饰器
前言 本文代码案例基于Api13。 在实际的开发中,我们经常会遇到自定义组件的情况,比如通用的列表组件,选项卡组件等等,由于使用方的样式不一,子组件是动态变化的,针对这一情况,就不得不让使用方把…...
基于和声搜索(Harmony Search, HS)的多中心点选址优化算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于和声搜索(Harmony Search, HS)的多中心点选址优化算法matlab仿真。可以设置多个不同的中心点。 2.测试软件版本以及运行结果展示 matlab2022a/matlab2024b版…...
word文档提取信息
目录 一、说明二、Aspose-words方式2.1、Aspose-words介绍2.2、Aspose-words使用说明2.3、Aspose-words解析核心代码一、说明 项目中遇到这样的一个需求问题:“一个docx文档,用户根据关键词能搜索定位到文档的哪一页”。docx文档主要有文本、表格、图片、附件这几类组合,为…...
单、双 链 表
数组模拟链表 ①单链表:邻接表{储存图和树} ②双链表:优化某些问题 单链表 // head 表示头结点的下标 // e[i] 表示节点i的值 // ne[i] 表示节点i的next指针是多少 // idx 存储当前已经用到了哪个点 e[i]和ne[i]用下标关联起来ÿ…...
SpringBoot整合easy-es
一、easy-es简介 EasyES是一款基于Elasticsearch官方提供的RestHighLevelClient开发的ORM框架,旨在简化开发流程并提高效率。 EasyES在保持RestHighLevelClient原有功能的基础上进行增强,而不做任何改变。它采用与Mybatis-Plus相似的语法,使得…...
2025年2月15日(ipython 换行-deepseek)
好的,用户现在想知道在IPython中如何换行而不运行代码。之前他们问过关于树莓派安装matplotlib的问题,现在转向IPython的使用,可能他们正在学习Python,并且可能在使用树莓派进行项目开发。需要确定用户的使用场景,可能…...
MyBatis:动态SQL高级标签使用方法指南
一、引言 目前互联网大厂在搭建后端Java服务时,常使用Springboot搭配Mybatis/Mybatis-plus的框架。Mybatis/Mybatis-plus之所以能成为当前国内主流的持久层框架,与其本身的优点有关:支持定制动态 SQL、存储过程及高级映射,简化数…...
Python常见面试题的详解5
1. re 模块中 search () 和 match () 的区别 要点:在 Python 的 re 模块中,re.match() 和 re.search() 是用于正则表达式匹配的两个重要函数,它们的主要区别在于匹配的起始位置:re.match():从字符串的开头开始匹配正则…...
python包的管理
管理python包 python能跻身最欢迎编程语言前列的一个主要原因是python有着活跃的社区提供丰富的包,诸如numpy,pandas,scikit-learn等等。 python的包都存放PyPI中,PyPI即Python Package Index,是python的软件仓库。所…...
性能测试流程、主流性能工具
性能测试流程 性能测试流程 测试测试需求分析 性能测试计划和方案 测什么: 测试背景 测试目的 测试范围 谁来测: 进度和分工 交付清单 怎么测: 测试策略 性能测试用例设计 性能测试测试执行 性能分析和调优 性能测试报告 测试报告是…...
Lua闭包的使用以及需要注意的问题
1. 闭包的基本概念 在 Lua 中,闭包是一个函数值,它包含了函数本身以及该函数所创建时的环境。闭包允许函数访问其外部函数作用域中的变量,即使外部函数已经执行完毕。 2.闭包的简单使用 代码:在下面的代码中,create…...
【个人开发】deepseed+Llama-factory 本地数据多卡Lora微调
文章目录 1.背景2.微调方式2.1 关键环境版本信息2.2 步骤2.2.1 下载llama-factory2.2.2 准备数据集2.2.3 微调模式2.2.4 微调脚本 2.3 踩坑经验2.3.1 问题一:ValueError: Undefined dataset xxxx in dataset_info.json.2.3.2 问题二: ValueError: Target…...
LabVIEW 天然气水合物电声联合探测
天然气水合物被认为是潜在的清洁能源,其储量丰富,预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性,天然气水合物的探测面临诸多挑战,涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…...
VisualStudio 2012 fatal error C1083: 无法打开包括文件:“stdio.h 找不到 sdkddkver.h
今天安装了一个VC 2012 Express 学习版,提示找不不到stdio.h, 提示找不到sdkddkver.h 发现是没有安装windows8.0 SDK ,还有一个些VC头文件没有安装. 真是太奇怪了,可能版本太多,安装出问题. 我这里放一个备份文件,省得以后,不能安装的时候,没地方找这些头文件. 无法打开包…...
什么是access token和refresh token?
access token 验证身份有效时间相对refresh token 时间短一点 refresh token 用于辅助access token 过期,避免用户反复登录的问题当accesstoken过期直接拿refreshtoken去获取最新的token...
盛铂科技 SCP4006/4018/4040:国产袖珍式功率计 射频微波功率探头 平均功率计
在通信、电子测量等领域,功率计是确保信号稳定、系统高效运行的关键设备。盛铂科技自主研发的 SCP4000 系列自带 USB 接口的袖珍式 CW 信号平均功率计,以其卓越的性能、高性价比和便捷的操作,在众多同类产品中脱颖而出,成为行业内…...