Java 多线程:锁(一)

作者:Grey

原文地址:

博客园:Java 多线程:锁(一)

CSDN:Java 多线程:锁(一)

CAS

比较与交换的意思

举个例子,内存有个值是 3,如果用 Java 通过多线程去访问这个数,每个线程都要把这个值 +1。

之前是需要加锁,即synchronized关键字来控制。但是 JUC 的包出现后,有了 CAS 操作,可以不需要加锁来处理,流程是:

第一个线程:把 3 拿过来,线程本地区域做计算加 1,然后把 4 写回去。

第二个线程:也把 3 这个数拿过来,线程本地区域做计算加 1 后,在回写回去的时候,会做一次比较,如果原来的值还是 3,那么说明这个值之前没有被打扰过,就可以把 4 写回去,如果这个值变了,假设变为了 4,那么说明这个值已经被其他线程修改过了,那么第二个线程需要重新执行一次,即把最新的 4 拿过来继续计算,回写回去的时候,继续做比较,如果内存中的值依然是 4,说明没有其他线程处理过,第二个线程就可以把 5 回写回去了。

流程图如下

CAS 会出现一个 ABA 的问题,即在一个线程回写值的时候,其他线程其实动过那个原始值,只不过其他线程操作后这个值依然是原始值。

如何来解决 ABA 问题呢?

我们可以通过版本号或者时间戳来控制,比如数据原始的版本是 1.0,处理后,我们把这个数据的版本改成变成 2.0 版本, 时间戳来控制也一样。

以 Java 为例,AtomicStampedReference这个类,它内部不仅维护了对象值,还维护了一个时间戳。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值,写入才会成功。因此,即使对象值被反复读写,写回原值,只要时间戳发生变化,就能防止不恰当的写入。

代码示例

package git.snippets.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference; /**
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2022/9/10
* @since
*/
public class ABATest {
public static void main(String[] args) throws InterruptedException {
abaCorrect();
} private static void abaCorrect() throws InterruptedException {
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(10, 0);
Thread threadA = new Thread(() -> {
try {
int[] stamp = new int[1];
Integer value = ref.get(stamp); //同时获取时间戳和数据,防止获取到数据和版本不是一致的 System.out.println(String.format("%s 启动,当前值是:%s,版本:%s", Thread.currentThread().getName(), ref.getReference(), stamp[0]));
TimeUnit.MILLISECONDS.sleep(1000); int newValue = value + 1;
boolean writeOk = ref.compareAndSet(value, newValue, stamp[0], stamp[0] + 1); System.out.println(String.format("%s:%s,%s", Thread.currentThread().getName(), "10->11", writeOk ? stamp[0] + 1 : stamp[0]));
stamp = new int[1];
value = ref.get(stamp); //同时获取时间戳和数据,防止获取到数据和版本不是一致的
newValue = value - 1;
writeOk = ref.compareAndSet(value, newValue, stamp[0], stamp[0] + 1);
System.out.println(String.format("%s:%s,%s", Thread.currentThread().getName(), "10->11->10", writeOk ? stamp[0] + 1 : stamp[0]));
} catch (InterruptedException e) {
}
}, "线程A"); Thread threadB = new Thread(() -> {
try {
int[] stamp = new int[1];
Integer value = ref.get(stamp); //同时获取时间戳和数据,防止获取到数据和版本不是一致的 System.out.println(String.format("%s 启动,当前值是:%s,版本:%s", Thread.currentThread().getName(), ref.getReference(), stamp[0]));
TimeUnit.MILLISECONDS.sleep(2000); int newValue = value + 2;
boolean writeOk = ref.compareAndSet(value, newValue, stamp[0], stamp[0] + 1); System.out.println(String.format("%s: index是预期的10:%s,新值是:%s,版本:%s", Thread.currentThread().getName(), writeOk, ref.getReference(), writeOk ? stamp[0] + 1 : stamp[0]));
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程B"); threadA.start();
threadB.start(); threadA.join();
threadB.join();
} }

CAS 的底层调用了汇编的 LOCK_IF_MP 方法:

lock cmpxchg

虽然cmpxchg指令不是原子的,但是加了lock指令后,则cmpxhg被上锁,不允许被打断。 在单核 CPU 中,无须加lock,在多核 CPU 中,必须加lock,可以参考 stackoverflow 上的这个回答: is-x86-cmpxchg-atomic-if-so-why-does-it-need-lock

使用 CAS 好处

jdk 早期是重量级别锁 ,通过0x80中断 进行用户态和内核态转换,所以效率比较低,有了 CAS 操作,大大提升了效率。

锁升级

过程如下:

偏向锁

synchronized 代码段多数时间是一个线程在运行,谁先来,这个就偏向谁,用当前线程标记一下。

轻量级锁(自旋锁,无锁)

  1. 偏向锁撤销,然后竞争,每个线程在自己线程栈中存一个LR(lock record)锁记录

  2. 偏向锁和轻量级锁都是用户空间完成的,重量级锁需要向操作系统申请。

  3. 两个线程争抢的方式将lock record的指针,指针指向哪个线程的LR,哪个线程就拿到锁,另外的线程用 CAS 的方式继续竞争

重量级锁

JVM 的 ObjectMonitor 去操作系统申请。

如果发生异常,synchronized会自动释放锁,

示例代码如下:

package git.snippets.juc;

import java.util.concurrent.TimeUnit;

public class ExceptionCauseUnLock {
/*volatile */ boolean stop = false; public static void main(String[] args) {
ExceptionCauseUnLock t = new ExceptionCauseUnLock();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (t.stop) {
int m = 1 / 0;
}
} synchronized void m() {
while (!stop) {
stop = true;
}
}
}

其中

int m = 1 / 0;

会抛出异常,锁会自动释放。

锁重入

synchronized是可重入锁, 可重入次数必须记录,因为解锁需要对应可重入次数的记录。

偏向锁:记录在线程栈中,每重入一次,LR 加 1,备份原来的markword

轻量级锁:类似偏向锁

重量级锁:记录在ObjectMonitor的一个字段中

自旋锁什么时候升级为重量级锁?

  • 有线程超过十次自旋

  • -XX:PreBlockSpin(jdk1.6之前)

  • 自旋的线程超过CPU核数一半

  • jdk1.6 以后,JVM自己控制

为什么有偏向锁启动和偏向锁未启动?

未启动:普通对象001 已启动:匿名偏向101

为什么有自旋锁还需要重量级锁?

因为自旋会占用 CPU 时间,消耗 CPU 资源,如果自旋的线程多,CPU 资源会被消耗,所以会升级成重量级锁(队列)例如:ObjectMonitor里面的WaitSet,重量级锁会把线程都丢到WaitSet中冻结, 不需要消耗 CPU 资源

偏向锁是否一定比自旋锁效率高?

明确知道多线程的情况下,不一定。 因为偏向锁在多线程情况下,会涉及到锁撤销,这个时候直接使用自旋锁,JVM 启动过程,会有很多线程竞争,比如启动的时候,肯定是多线程的,所以默认情况,启动时候不打开偏向锁,过一段时间再打开,JVM 有一个参数可以配置:BiasedLockingStartupDelay默认是4s

synchronized

锁定对象

package git.snippets.juc;

/**
* synchronized锁定对象
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/15
* @since
*/
public class SynchronizedObject implements Runnable {
static SynchronizedObject instance = new SynchronizedObject();
final Object object = new Object();
static volatile int i = 0; @Override
public void run() {
for (int j = 0; j < 1000000; j++) {
// 任何线程要执行下面的代码,必须先拿到object的锁
synchronized (object) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}

锁定方法

锁定静态方法相当于锁定当前类

package git.snippets.juc;

/**
* synchronized锁定静态方法,相当于锁定当前类
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/15
* @since
*/
public class SynchronizedStatic implements Runnable {
static SynchronizedStatic instance = new SynchronizedStatic();
static volatile int i = 0; @Override
public void run() {
increase();
} // 相当于synchronized(SynchronizedStatic.class)
synchronized static void increase() {
for (int j = 0; j < 1000000; j++) {
i++;
}
} public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}

锁定非静态方法相当于锁定该对象的实例或synchronized(this)

package git.snippets.juc;

/**
* synchronized锁定方法
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/15
* @since
*/
public class SynchronizedMethod implements Runnable {
static SynchronizedMethod instance = new SynchronizedMethod();
static volatile int i = 0; @Override
public void run() {
increase();
}
void increase() {
for (int j = 0; j < 1000000; j++) {
synchronized (this) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}

脏读

package git.snippets.juc;

import java.util.concurrent.TimeUnit;

/**
* 模拟脏读
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/15
* @since
*/
public class DirtyRead {
String name;
double balance; public static void main(String[] args) {
DirtyRead a = new DirtyRead();
Thread thread = new Thread(() -> a.set("zhangsan", 100.0)); thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
} public synchronized void set(String name, double balance) {
this.name = name; try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} this.balance = balance;
} // 如果get方法不加synchronized关键字,就会出现脏读情况
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
}

其中的getBalance方法,如果不加synchronized,就会产生脏读的问题。

可重入锁

一个同步方法可以调用另外一个同步方法,

一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁(可重入锁)

子类synchronized,如果调用父类的synchronize方法:super.method(),如果不可重入,直接就会死锁。

package git.snippets.juc;

import java.io.IOException;

/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since
*/
public class SynchronizedReentry implements Runnable { public static void main(String[] args) throws IOException {
SynchronizedReentry myRun = new SynchronizedReentry();
Thread thread = new Thread(myRun, "t1");
Thread thread2 = new Thread(myRun, "t2");
thread.start();
thread2.start();
System.in.read(); } synchronized void m1(String content) {
System.out.println(this);
System.out.println("m1 get content is " + content);
m2(content);
} synchronized void m2(String content) {
System.out.println(this);
System.out.println("m2 get content is " + content); } @Override
public void run() {
m1(Thread.currentThread().getName());
}
}

程序在执行过程中,如果出现异常,默认情况锁会被释放 ,所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个 web app 处理过程中,多个Servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。因此要非常小心的处理同步业务逻辑中的异常。

示例代码

package git.snippets.juc;

import java.io.IOException;
import java.util.concurrent.TimeUnit; /**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
* 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
* 因此要非常小心的处理同步业务逻辑中的异常
*/
public class SynchronizedException implements Runnable {
int count = 0; public static void main(String[] args) throws IOException {
SynchronizedException myRun = new SynchronizedException();
Thread thread = new Thread(myRun, "t1");
Thread thread2 = new Thread(myRun, "t2");
thread.start();
thread2.start();
System.in.read(); } @Override
public void run() {
synchronized (this) {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("current thread is " + Thread.currentThread().getName() + " count is " + count);
if (count == 5) {
count++;
int m = 1 / 0;
}
count++;
}
}
} synchronized void m1(String content) {
System.out.println(this);
System.out.println("m1 get content is " + content);
m2(content);
} synchronized void m2(String content) {
System.out.println(this);
System.out.println("m2 get content is " + content); } }

synchronized 的底层实现

在早期的 JDK 使用的是操作系统级别的重量级锁

后来的改进锁升级的概念:

synchronized (Object)

  • markword 记录这个线程ID (使用偏向锁)

  • 如果线程争用:升级为 自旋锁

  • 10次自旋以后,升级为重量级锁 - OS

所以,如果

  • 执行时间短(加锁代码),线程数少,用自旋。

  • 执行时间长,线程数多,用系统锁。

注:synchronized不能锁定String常量,Integer,Long等基础类型

代码示例如下

package git.snippets.juc;

/**
* synchronized不能锁定String常量,Integer,Long等基础类型
* <p>
* 不要以字符串常量作为锁定对象
* 在下面的例子中,m1和m2其实锁定的是同一个对象
* 这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串“Hello”,
* 但是你读不到源码,所以你在自己的代码中也锁定了"Hello",这时候就有可能发生非常诡异的死锁阻塞,
* 因为你的程序和你用到的类库不经意间使用了同一把锁
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since
*/
public class SynchronizedBasicType implements Runnable {
public static Integer i = 0;
static SynchronizedBasicType instance = new SynchronizedBasicType();
static final String lock = "this is a lock";
static final String lock1 = "this is a lock"; public static void main(String[] args) throws InterruptedException {
m();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} public static void m() throws InterruptedException {
Thread m1 = new Thread(new Runnable() {
@Override
public void run() {
/*synchronized (this)*/
synchronized (lock) {
System.out.println("locked ...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
System.out.println("unlocked ...");
}
}
});
m1.start();
Thread.sleep(1000);
Thread m2 = new Thread(new Runnable() {
@Override
public void run() {
/*synchronized (this)*/
synchronized (lock1) {
System.out.println("locked lock1 ...");
System.out.println("unlocked lock1 ...");
}
}
});
m2.start();
m1.join();
m2.join();
} @Override
public void run() {
for (int j = 0; j < 10000000; j++) {
synchronized (i) {
i++;
}
}
}
}

锁定某对象 o,如果 o 的属性发生改变,不影响锁的使用; 但是如果 o 指向另外一个对象,则锁定的对象发生改变, 会影响锁的使用,所以应该避免将锁定对象的引用变成另外的对象。

package git.snippets.juc;

import java.util.concurrent.TimeUnit;

/**
* 锁定某对象o,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成另外一个对象,则锁定的对象发生改变
* 应该避免将锁定对象的引用变成另外的对象
*/
public class SyncSameObject {
Object object = new Object(); public static void main(String[] args) {
SyncSameObject t = new SyncSameObject();
new Thread(t::m).start();
Thread t2 = new Thread(t::m, "t2");
//锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会
t.object = new Object();
t2.start();
} void m() {
synchronized (object) {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("current thread is " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

以上代码,如果不执行t.object=new Object()这句,m2 线程将永远得不到执行。

死锁

两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,这就是死锁现象

死锁产生的原因主要有如下几点

  1. 系统的资源竞争

  2. 程序在执行过程中申请和释放资源的顺序不当

死锁产生的必要条件

  1. 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

  2. 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

  3. 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

  4. 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。

模拟死锁代码

/**
* 模拟死锁
*/
public class DeadLock implements Runnable {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object(); public static void main(String[] args) {
DeadLock lock = new DeadLock();
DeadLock lock2 = new DeadLock();
lock.flag = 1;
lock2.flag = 0;
Thread t1 = new Thread(lock);
Thread t2 = new Thread(lock2);
t1.start();
t2.start(); } @Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("0");
}
}
}
}
}

如何避免死锁?

1、让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实。

2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。

3、增加时限,比如使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后便会返回一个失败信息。

volatile

  • 保持线程之间的可见性(不保证操作的原子性),依赖 MESI 协议

  • 防止指令重排序,CPU的load fencestore fence原语支持

CPU 原来执行指令一步一步执行,现在是流水线执行,编译以后可能会产生指令的重排序,这样可以提高性能

关于volatile不保证原子性的代码示例

package git.snippets.juc;

/**
* Volatile保持线程之间的可见性(不保证操作的原子性)
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/19
* @since
*/
public class VolatileNOTAtomic {
volatile static Data data; public static void main(String[] args) {
Thread writer = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
data = new Data(i, i);
}
}); Thread reader = new Thread(() -> {
while (data == null) {
}
int a = data.a;
int b = data.b;
if (a != b) {
// 会出现这种情况是因为new Data(i,i)非原子操作,会产生中间状态的对象,导致a和b的值会不一致
System.out.printf("a = %s, b=%s%n", a, b);
}
});
writer.start();
reader.start();
try {
writer.join();
reader.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
} public static class Data {
int a;
int b; Data(int a, int b) {
this.a = a;
this.b = b;
}
}
}

volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized,

示例代码如下:

package git.snippets.juc;

import java.util.ArrayList;
import java.util.List; /**
* volatile并不能保证多个线程共同修改变量时所带来的不一致问题,也就是说volatile不能替代synchronized
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/19
* @since
*/
public class VolatileCanNotReplaceSynchronized {
volatile int count = 0;
int count2 = 0; public static void main(String[] args) {
VolatileCanNotReplaceSynchronized t = new VolatileCanNotReplaceSynchronized();
List<Thread> threads = new ArrayList<>();
List<Thread> threads2 = new ArrayList<>();
for (int i = 0; i < 20; i++) {
threads.add(new Thread(t::m));
threads2.add(new Thread(t::m2));
}
threads.forEach(item -> item.start());
threads2.forEach(item -> item.start());
threads.forEach(item -> {
try {
item.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads2.forEach(item -> {
try {
item.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
System.out.println(t.count2);
} void m() {
for (int i = 0; i < 1000; i++) {
count++;
}
} synchronized void m2() {
for (int i = 0; i < 1000; i++) {
count2++;
}
}
}

DCL 为什么一定要加 volatile?

什么是 DCL,请参考设计模式学习笔记中的单例模式说明。

在New对象的时候,编译完实际上是分了三步

  1. 对象申请内存,成员变量会被赋初始值

  2. 成员变量设为真实值

  3. 成员变量赋给对象

指令重排序可能会导致2和3进行指令重排,导致下一个线程拿到一个半初始化的对象,导致单例被破坏。所以 DCL 必须加volitile

此外,被volatile关键字修饰的对象作为类变量或实例变量时,其对象中携带的类变量和实例变量也相当于被volatile关键字修饰了

示例代码如下

package git.snippets.juc;

import java.util.concurrent.TimeUnit;

/**
* 被volatile关键字修饰的对象作为类变量或实例变量时,其对象中携带的类变量和实例变量也相当于被volatile关键字修饰了
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since 1.8
*/
public class VolatileRef {
volatile M tag = new M(); public static void main(String[] args) {
VolatileRef t = new VolatileRef();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.tag.n.x.stop = new Boolean(true);
} void m() {
while (!tag.n.x.stop) {
}
}
} class M {
N n = new N();
} class N {
X x = new X();
} class X {
public Boolean stop = new Boolean(false);
}

说明

本文涉及到的所有代码和图例

图例

代码

更多内容见:Java 多线程

参考资料

实战Java高并发程序设计(第2版)

深入浅出Java多线程

多线程与高并发-马士兵

Java并发编程实战

【并发编程】MESI--CPU缓存一致性协议

【并发编程】细说并发编程的三大特性

设计模式学习笔记

图解Java多线程设计模式

Java多线程:死锁

Java 多线程:锁(一)的更多相关文章

  1. Java多线程--锁的优化

    Java多线程--锁的优化 提高锁的性能 减少锁的持有时间 一个线程如果持有锁太长时间,其他线程就必须等待相应的时间,如果有多个线程都在等待该资源,整体性能必然下降.所有有必要减少单个线程持有锁的时间 ...

  2. synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解

    本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁 ...

  3. Java 多线程 锁 存款 取款

    http://jameswxx.iteye.com/blog/806968 最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: ja ...

  4. Java多线程——锁概念与锁优化

    为了性能与使用的场景,Java实现锁的方式有非常多.而关于锁主要的实现包含synchronized关键字.AQS框架下的锁,其中的实现都离不开以下的策略. 悲观锁与乐观锁 乐观锁.乐观的想法,认为并发 ...

  5. Java多线程——锁

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  6. [java多线程] - 锁机制&同步代码块&信号量

    在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突.冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突.按照我的理解在java中实现同步的方式分为三种,分别是:同 ...

  7. 详解Java多线程锁之synchronized

    synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法. synchronized的四种使用方式 修饰代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}括 ...

  8. Java——多线程锁的那些事

    引入 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率. 下面先带大家来总体预览一下锁的分类图 1.乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了 ...

  9. java多线程-锁

    自 Java 5 开始,java.util.concurrent.locks 包中包含了一些锁的实现,因此你不用去实现自己的锁了.但是你仍然需要去了解怎样使用这些锁. 一个简单的锁 让我们从 java ...

  10. Java多线程-锁的区别与使用

    目录 锁类型 可中断锁 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 Synchronized与Static Synchron ...

随机推荐

  1. Servlet之Request和Response 解析

    原理 tomcat服务器会根据请求url中的资源路径,创建对应的Servlet的对象 tomcat服务器.会创建request和response对象,request对象中封装请求消息数据. tomca ...

  2. Linux命令格式、终端类型和获取帮助的方法

    Linux用户类型 Root用户:超级管理员,权限很大 普通用户:权限有限 终端 terminal 终端类型 物理终端:鼠标.键盘.显示器 虚拟终端:软件模拟出来的终端 控制台终端: /dev/con ...

  3. Task.Run(), Task.Factory.StartNew() 和 New Task() 的行为不一致分析

    重现 在 .Net5 平台下,创建一个控制台程序,注意控制台程序的Main()方法如下: static async Task Main(string[] args) 方法的主体非常简单,使用Task. ...

  4. 关于C标准库stdarg.h

    看<数据结构(C语言版)>(严蔚敏)时看到p94上面va_list啥的,看不懂了,于是整理一下这一部分的知识. 1/当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表. i ...

  5. 【主流技术】Redis 在 Spring 框架中的实践

    前言 在Java Spring 项目中,数据与远程数据库的频繁交互对服务器的内存消耗比较大,而 Redis 的特性可以有效解决这样的问题. Redis 的几个特性: Redis 以内存作为数据存储介质 ...

  6. git无法使用Tab提示

    1.过去git版本: git version 2.获取git-completion.bash脚本,注意将下方链接的版本号改为和git版本一致. https://raw.githubuserconten ...

  7. APISpace 月出月落和月相API接口 免费好用

     月出和月落的位置,正如地球围绕太阳变化时产生的日出和日落一样,但是也和月相有关.一天中月亮升起的时间取决于它的月相.当你记得月相取决于太阳,月亮和地球的相对位置应该是明显的.月相是指从地球上看月球直 ...

  8. 类型转换_float()函数

    float()函数不能将文字类的字符串类型转换成小数类型 同时将整数转换成浮点数类型的时候会在整数后买你加上.0 print(float(1))//output:1.0 print(float('1' ...

  9. Mysql性能调优-工具篇

    EXPLAIN 首先祭出官方文档(这是5.7的,请自行选择版本): Understanding the Query Execution Plan 英文不想看,就看这篇吧: 全网最全 | MySQL E ...

  10. python:GUI图形化数据库巡检工具

    问题描述:时间过得真快,一眨眼又一个月过去,2022又过去大半,7月的尾巴,终于稍微做出来点 东西,本人也不是开发,也是在不断学习的一枚小白.这次使用tkinter制作了一个mysql的巡检工具,使用 ...