Java锁--悲观锁、乐观锁
前言
锁的种类很多,我们今天就来梳理一下。Java中的好多锁系列之悲观锁、乐观锁。
一:悲观锁
悲观的认为所有的线程都会导致数据错误,每一个线程都需要排队等待。优点:数据一致性,缺点:效率低
1.1:synchronized
-
jdk内置的锁,熟悉native的朋友会知道,synchronized是调用了jr.jar里的放
-
灵魂的拷问,他是怎么实现的。答案对象头object head,通过Object Header对象头
Java对象的对象头由 Mark Word 和 Klass pointer 两部分组成,
mark word存储了同步状态、标识、hashcode、GC状态等等。
Klass pointer存储对象的类型指针,该指针指向它的类元数据
值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。
我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。
以64位操作系统为例,对象头存储内容图例。
简单介绍一下各部分的含义 lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。 biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:Java GC标记位对象年龄,4位最大是15,所以minor Gc的次数就是15次。 identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。 thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。 epoch:偏向时间戳。 ptr_to_lock_record:指向栈中锁记录的指针。 ptr_to_heavyweight_monitor:指向线程Monitor的指针。
输出的第一行内容和锁状态内容对应 unused:1 | age:4 | biased_lock:1 | lock:2 0 0000 0 01 代表A对象正处于无锁状态
第三行中表示的是被指针压缩为32位的klass pointer 第四行则是我们创建的A对象属性信息 1字节的boolean值 第五行则代表了对象的对齐字段 为了凑齐64位的对象,对齐字段
-
对象锁:synchronized
锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念。
synchronized是对类的当前实例(当前对象)进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。
import java.util.concurrent.TimeUnit;
public class Test1 {
public synchronized void start() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",start");
}
public synchronized void end() {
System.out.println(Thread.currentThread().getName() + ",end");
}
public static void main(String args[]) {
Test1 test = new Test1();
new Thread(test::start, "线程A").start();
new Thread(test::end, "线程B").start();
}
}
执行结果:
-
类锁:static synchronized
该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
public synchronized void start() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",start");
}
public synchronized void end() {
System.out.println(Thread.currentThread().getName() + ",end");
}
public static void main(String args[]) {
StaticTest a = new StaticTest();
StaticTest b = new StaticTest();
new Thread(a::start, "线程A").start();
new Thread(b::end, "线程c").start();
}
执行结果:
线程A,end 线程c,start
更改start、end方法为static后,执行结果:
线程c,start 线程A,end
-
synchronized+static synchronized
public class StaticTest {
public synchronized void start() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",start");
}
public static synchronized void end() {
System.out.println(Thread.currentThread().getName() + ",end");
}
public static void main(String args[]) {
StaticTest a = new StaticTest();
StaticTest b = new StaticTest();
new Thread(() -> a.start(), "线程c").start();
new Thread(() -> a.end(), "线程A").start();
}
}
执行结果:
线程A,end 线程c,start
synchronized 与static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。
1.2:Lock
未完待续
二:乐观锁
2.1:AtomicInteger
public class VersionTest {
final static int LOOP = 10000;
int count = 0;
public void add() {
count++;
System.out.println(Thread.currentThread().getName() + " add number :" + count);
}
public void subtract() {
count--;
System.out.println(Thread.currentThread().getName() + " subtract number :" + count);
}
public static void main(String args[]) throws InterruptedException {
VersionTest test = new VersionTest();
for (int i = 0; i < VersionTest.LOOP; i++) {
new Thread(() -> test.add()).start();
new Thread(() -> test.subtract()).start();
}
TimeUnit.SECONDS.sleep(1);
System.out.println(test.count);
}
}
执行结果:
Thread-19996 add number :1
Thread-19997 subtract number :0
Thread-19998 add number :1
Thread-19999 subtract number :0
Thread-19962 add number :1
Thread-19971 subtract number :0
Thread-19975 subtract number :-1
Thread-19881 subtract number :-2
-2
public class AtomicIntegerTest {
final static int LOOP = 10000;
AtomicInteger count = new AtomicInteger(0);
public void add() {
count.addAndGet(1);
System.out.println(Thread.currentThread().getName() + " add number :" + count.get());
}
public void subtract() {
count.decrementAndGet();
System.out.println(Thread.currentThread().getName() + " subtract number :" + count.get());
}
public static void main(String args[]) throws InterruptedException {
AtomicIntegerTest test = new AtomicIntegerTest();
for (int i = 0; i < AtomicIntegerTest.LOOP; i++) {
new Thread(() -> test.add()).start();
new Thread(() -> test.subtract()).start();
}
TimeUnit.SECONDS.sleep(1);
System.out.println(test.count.get());
}
}
执行结果:
Thread-19989 subtract number :0 Thread-19988 add number :1 Thread-19973 subtract number :0 Thread-19990 add number :1 Thread-19999 subtract number :0 Thread-19996 add number :1 Thread-19995 subtract number :0 0
2.2:CAS算法
compare and swap比较与交换,实现原理:
当前主内存变量的值V,线程本地变量预期值A(主内存变量V的副本),线程本地待更新值B。当需要更新变量值的时候,会先获取到内存变量值V然后跟预期值A进行比较,如果相同则更新为B,如果不同,线程本地变量预期值A更新为主内存变量V的值。