目录
一、并发编程
1、多线程锁分类
Synchronized实现同步的基础:Java中每一个对象都可以作为锁。
1、对于普通同步方法,锁是当前实例对象。
2、对于静态同步方法,锁是当前类的class对象。
3、对于同步方法块,锁是Synchronized括号里配置的对象。
1.1、公平锁 & 非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
非公平锁特点是:有的线程会饿死,优点是效率高。
公平锁特点是:阳光普照,效率比非公平锁低。公平锁会先去询问一下hasQueuedPredecessors()
两者区别:
1、公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
2、非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。
ReentrantLock :默认是非公平锁。
Synchronized也是一种非公平锁。
public class ReentrantLock implements Lock, java.io.Serializable {
...
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
...
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
1.2、可重入锁 & 不可重入锁
Java中提供的synchronized,ReentrantLock,ReentrantReadWriteLock都是可重入锁。
可重入锁也称为递归锁:指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
重入:当前线程获取到A锁,在获取之后尝试再次获取A锁是可以直接拿到的。
不可重入:当前线程获取到A锁,在获取之后尝试再次获取A锁,无法获取到的,因为A锁被当前线程占用着,需要等待自己释放锁再获取锁。
Synchronized和Lock都是可重入锁。
例如:回到家里,只需要把家里的大门打开,家里的卧室门都不需要打开就能自由进入。这就是可重入的意思。
可重入锁示例:
public class ThreadDemo5 {
public static void main(String[] args) {
Object obj = new Object();
new Thread(()-> {
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+" 外层");
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+" 中层");
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+" 内层");
}
}
}
},"t1").start();
}
}
public class ThreadDemo5 {
public synchronized void add() {
add();
}
public static void main(String[] args) {
new ThreadDemo5().add();
}
}
执行上面出现:java.lang.StackOverflowError,因为是可重入锁。导致递归调用add()方法。
1.3、自旋锁
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
java环境下jre/lib/rt.jar下的sun.misc下
package sun.misc;
public final class Unsafe
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public native int getIntVolatile(Object paramObject, long paramLong);
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
{
i = getIntVolatile(paramObject, paramLong);//获取最新值
//比较并交换,不成功则继续执行do代码块中的获取当前最新值,再进行比较并交换:自旋
} while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}
手写一个自旋锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
// 原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "\t come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnlock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(thread.getName() + "\t myUnlock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
}, "A").start();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
}, "B").start();
}
}
1.4、死锁
死锁:两个或两个以上进程在执行过程中,因为争夺资源而造成一种相互等待的现象,如果没有外力干涉,他们无法再执行下去,这种现象被称为死锁。
产生死锁的原因?
1、系统资源不足
2、进程运行推进顺序不合适
3、资源分配不当
死锁示例:
public class DeadLock {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(() ->{
synchronized (a) {
System.out.println(Thread.currentThread().getName() +"持有锁a,试图获取锁b");
synchronized (b) {
System.out.println(Thread.currentThread().getName() +"持有锁b");
}
}
},"t1").start();
new Thread(() ->{
synchronized (b) {
System.out.println(Thread.currentThread().getName() +"持有锁b,试图获取锁a");
synchronized (a) {
System.out.println(Thread.currentThread().getName() +"持有锁a");
}
}
},"t2").start();
}
}
linux ps -ef|grep xxxx ls -l
windows下的java运行程序,也有类似的ps的查看进程的命令,但是目前我们需要查看的只是java
jps=java ps jps -l
验证是否是死锁
1.JDK bin目录 jps.exe
2.jstack 进程号
需要在环境变量path中把jdk安装bin目录配置到path环境变量中。否则jps和jstack命令使用不了
控制台上
cmd--->jps -l 命令
cmd--->jstack 进程号
C:\Users\Administrator>jps -l
7748 com.lwz.study.day04.DeadLock
3768 jdk.jcmd/sun.tools.jps.Jps
5820
C:\Users\Administrator>jstack 7748
2022-04-05 14:08:27
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002532800 nid=0x3600 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"t2" #12 prio=5 os_prio=0 tid=0x000000001a61d000 nid=0x3918 waiting for monitor entry [0x000000001afef000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.lwz.study.day04.DeadLock.lambda$1(DeadLock.java:21)
- waiting to lock <0x00000000d5cbc010> (a java.lang.Object)
- locked <0x00000000d5cbc020> (a java.lang.Object)
at com.lwz.study.day04.DeadLock$$Lambda$2/135721597.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"t1" #11 prio=5 os_prio=0 tid=0x000000001a61b800 nid=0x2a48 waiting for monitor entry [0x000000001aeef000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.lwz.study.day04.DeadLock.lambda$0(DeadLock.java:13)
- waiting to lock <0x00000000d5cbc020> (a java.lang.Object)
- locked <0x00000000d5cbc010> (a java.lang.Object)
at com.lwz.study.day04.DeadLock$$Lambda$1/531885035.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x00000000198c8800 nid=0xc5c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000000001983a000 nid=0x3834 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001982e000 nid=0x10f8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001982a000 nid=0x114 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001981f000 nid=0x1c64 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001981c800 nid=0x3bac waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x00000000197c8800 nid=0x1ce4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x00000000197b1800 nid=0x8b4 in Object.wait() [0x0000000019d8f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5c08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000d5c08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000197b0800 nid=0x9c8 in Object.wait() [0x0000000019c8e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5c06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d5c06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x00000000179b9800 nid=0x3b90 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002547800 nid=0x3174 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002549000 nid=0x3220 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000254a800 nid=0x828 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000254c000 nid=0x528 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000254f800 nid=0x498 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002550800 nid=0x1344 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002554000 nid=0x1294 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002555000 nid=0x1be4 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000000198ee800 nid=0xcb0 waiting on condition
JNI global references: 310
Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x000000000262bc08 (object 0x00000000d5cbc010, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x000000000262e6a8 (object 0x00000000d5cbc020, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2":
at com.lwz.study.day04.DeadLock.lambda$1(DeadLock.java:21)
- waiting to lock <0x00000000d5cbc010> (a java.lang.Object)
- locked <0x00000000d5cbc020> (a java.lang.Object)
at com.lwz.study.day04.DeadLock$$Lambda$2/135721597.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"t1":
at com.lwz.study.day04.DeadLock.lambda$0(DeadLock.java:13)
- waiting to lock <0x00000000d5cbc020> (a java.lang.Object)
- locked <0x00000000d5cbc010> (a java.lang.Object)
at com.lwz.study.day04.DeadLock$$Lambda$1/531885035.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
1.5、悲观锁&乐观锁
Java中提供的synchronized,ReentrantLock,ReentrantReadWriteLock都是悲观锁。
Java中提供的CAS操作,就是乐观锁的一种实现。
乐观锁:获取不到锁资源,可以再次让CPU调度,重新尝试获取锁资源。
Atomic原子性类中,就是基于CAS乐观锁实现的。
悲观锁:获取不到锁资源时,会将当前线程挂起(进入BLOCKED、WAITING),线程挂起会涉及到用户态和内核态的切换,而这种切换是比较消耗资源的。
1、用户态:JVM可以自行执行的指令,不需要借助操作系统执行。
2、内核态:JVM不可以自行执行,需要操作系统才可以执行。
悲观锁:先上锁,再操作,操作完解锁。不支持并发操作。
乐观锁:只是在修改完成后,提交时对数据进行上锁比较数据的完整性。
1.6、互斥锁 & 共享锁
Java中提供的synchronized、ReentrantLock是互斥锁。
Java中提供的ReentrantReadWriteLock,有互斥锁也有共享锁。
互斥锁:同一时间点,只会有一个线程持有者当前互斥锁。
共享锁:同一时间点,当前共享锁可以被多个线程同时持有。
1.7、表锁 & 行锁
表锁:操作表中某一条数据会把整张表进行锁住。表锁不会发生死锁。
行锁:操作表中的某一行数据,对这行数据进行锁定。行锁会发生死锁。
1.8、读锁 & 写锁
读锁:共享锁。读锁会发生死锁。
写锁:独占锁。写锁会发生死锁。
读写锁:一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程。读写互斥,读读共享。
例:
线程1修改操作,需要等待线程2读之后。
线程2修改的时候,需要等待线程1读之后。
读写锁示例:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <= 5; i++) {
final int num= i;
new Thread(()->{
myCache.put(num+"", num+"");
},String.valueOf(i)).start();
}
//创建线程取数据
for (int i = 1; i <= 5; i++) {
final int num= i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
public void put(String key,Object value) {
System.out.println(Thread.currentThread().getName() + " 正在写操作"+key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写完了"+key);
}
public Object get(String key) {
Object result=null;
System.out.println(Thread.currentThread().getName() + " 正在读取操作"+key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 取完了"+key);
return result;
}
}
运行结果:
2 正在写操作2
4 正在写操作4
5 正在写操作5
3 正在写操作3
1 正在写操作1
1 正在读取操作1
2 正在读取操作2
3 正在读取操作3
4 正在读取操作4
5 正在读取操作5
4 取完了4
3 写完了3
1 写完了1
5 取完了5
2 写完了2
1 取完了1
3 取完了3
2 取完了2
4 写完了4
5 写完了5
效果是:未写完数据就开始读取,肯定会出问题。解决方式?
ReentrantReadWriteLock读写锁
ReadWriteLock接口的实现类 ReentrantReadWriteLock
public class ReentrantReadWriteLockextends Objectimplements ReadWriteLock, Serializable
ReentrantReadWriteLock中的嵌套类:
1.static class ReentrantReadWriteLock.ReadLock
readLock() 方法返回的锁。
2.static class ReentrantReadWriteLock.WriteLock
writeLock() 方法返回的锁。
ReentrantReadWriteLock构造方法:
1.ReentrantReadWriteLock():使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock.
2.ReentrantReadWriteLock(boolean fair):使用给定的公平策略创建一个新的ReentrantReadWriteLock.
解决读写问题示例:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <= 5; i++) {
final int num= i;
new Thread(()->{
myCache.put(num+"", num+"");
},String.valueOf(i)).start();
}
//创建线程取数据
for (int i = 1; i <= 5; i++) {
final int num= i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,Object value) {
try {
rwLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " 正在写操作"+key);
TimeUnit.MICROSECONDS.sleep(300);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwLock.writeLock().unlock();
}
}
public Object get(String key) {
rwLock.readLock().lock();
Object result=null;
try {
System.out.println(Thread.currentThread().getName() + " 正在读取操作"+key);
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwLock.readLock().unlock();
}
return result;
}
}
运行结果:
2 正在写操作2
2 写完了2
1 正在写操作1
1 写完了1
4 正在写操作4
4 写完了4
3 正在写操作3
3 写完了3
5 正在写操作5
5 写完了5
1 正在读取操作1
2 正在读取操作2
3 正在读取操作3
4 正在读取操作4
5 正在读取操作5
1 取完了1
2 取完了2
4 取完了4
3 取完了3
5 取完了5
上面结果可以看出,写锁是独占锁,而读锁是共享锁。
ReentrantReadWriteLock读写锁特点和缺点?
特点:读写互斥,读读共享。
缺点:
1.造成锁饥饿,一直读,没有写操作
2.读时不能进行写操作,只有读完成才能进行写。写操作可以读。
锁降级
将写锁降级为读锁:获取写锁--->获取读锁--->释放写锁--->释放读锁
例:抄作业-->只有写完,才有的抄。
只能从写锁降级为读锁,而不能从读锁升级为写锁。
锁降级示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo1 {
public static void main(String[] args) {
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
//锁降级
writeLock.lock();
System.out.println("write");
readLock.lock();
System.out.println("--read");
writeLock.unlock();
readLock.unlock();
}
}
运行结果:
write
--read
反过来读锁升级写锁,则进程不能结束
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo1 {
public static void main(String[] args) {
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
readLock.lock();
System.out.println("--read");
writeLock.lock();
System.out.println("write");
readLock.unlock();
writeLock.unlock();
}
}
运行结果:如下,进程不能停止。结论:不能从读锁升级为写锁
--read
1.9、类锁 & 对象锁
synchronized的使用一般就是同步方法和同步代码块。
synchronized的锁是基于对象实现的。
如果使用同步方法
static:此时使用的是当前类.class作为锁(类锁)
非static:此时使用的是当前对象做为锁(对象锁)
public class ThreadTest {
public static void main(String[] args) {
// 锁的是,当前Test.class
Test.a();
Test test = new Test();
// 锁的是new出来的test对象
test.b();
}
}
class Test{
public static synchronized void a(){
System.out.println("1111");
}
public synchronized void b(){
System.out.println("2222");
}
}
1.10、邮戳锁
面试:有没有比读写锁更快的锁?
邮戳锁StampedLock
StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。
邮戳锁也叫票据锁
stamp(戳记,long类型),代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。
它是由锁饥饿问题引出:
锁饥饿问题:ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,假如当前1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那1个写线程就悲剧了,因为当前有可能会一直存在读锁,而无法获得写锁,根本没机会写。
如何缓解锁饥饿问题?
使用公平策略可以一定程度上缓解这个问题,new ReentrantReadWriteLock(true)
但是公平策略是以牺牲系统吞吐量为代价的。
ReentrantReadWriteLock:允许多个线程同时读,但是只允许一个线程写,在线程获取到写锁的时候,其他写操作和读操作都会处于阻塞状态,读锁和写锁也是互斥的,所以在读的时候是不允许写的,读写锁比传统的synchronized速度要快很多,原因就是在于ReentrantReadWriteLock支持读并发,读读可以共享。
StampedLock:ReentrantReadWriteLock的读锁被占用的时候,其他线程尝试获取写锁的时候会被阻塞。但是StampedLock采取乐观获取锁后。其他线程尝试获取写锁时不会被阻塞,这其实是对读锁的优化,所以,在获取乐观读锁后,还需要对结果进行校验。
StampedLock的特点
1、所有获取锁的方法,都返回一个邮戳(stamp),stamp为0代表获取失败,其余都代表成功。
2、所有释放锁的方法,都需要一个邮戳(stamp),这个stamp必须是和成功获取锁时得到的stamp一致。
3、StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
4、StampedLock有三种访问模式
①Readding(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
③Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式
StampedLock=ReentrantReadWriteLock+读的过程中也允许获取写锁介入
//返回一个稍后可以验证的戳,如果以独占方式锁定,则返回零。
public long tryOptimisticRead() {
long s;
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}
//如果自给定邮票发行以来未以独占方式获取锁,则返回true。
//如果戳为零,则始终返回false。如果戳表示当前持有的锁,则始终返回true。
public boolean validate(long stamp) {
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}
读写示例
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
public class StampedLockTest {
static int number = 57;
static StampedLock stampedLock = new StampedLock();
public void write() {
long stamp = stampedLock.writeLock();
System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");
try {
number = number + 23;
} finally {
stampedLock.unlockWrite(stamp);
}
System.out.println(Thread.currentThread().getName() + "\t" + "写线程结束修改");
}
public void read() {
long stamp = stampedLock.readLock();
System.out.println(Thread.currentThread().getName() + "\t" + "读线程准备读");
for (int i = 0; i < 4; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "正在读取中...");
}
try {
int result = number;
System.out.println(Thread.currentThread().getName() + "\t" + "获得结果result=" + result);
} finally {
stampedLock.unlockRead(stamp);
}
System.out.println(Thread.currentThread().getName() + "\t" + "读线程结束读");
}
//乐观读,读的过程中也允许获取写锁的介入
public void tryOptimisticRead() {
long stamp = stampedLock.tryOptimisticRead();
int result = number;
System.out.println("stampedLock.validate(stamp) 返回true:无修改,false:有修改,stampedLock.validate(stamp)=" + stampedLock.validate(stamp));
for (int i = 0; i < 4; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "正在读取中..." + i + "次后,stampedLock.validate(stamp)=" + stampedLock.validate(stamp));
}
if (!stampedLock.validate(stamp)) {
System.out.println("有人修改过----有写操作");
stamp = stampedLock.readLock();
try {
System.out.println("从乐观读 升级为 悲观读");
result = number;
System.out.println("重新悲观读后result=" + result);
} finally {
stampedLock.unlockRead(stamp);
}
}
System.out.println("最终结果result=" + result);
}
public static void main(String[] args) throws InterruptedException {
StampedLockTest stampedLockTest = new StampedLockTest();
//读写锁
System.out.println("====传统版====");
new Thread(() -> {
stampedLockTest.read();
}, "readThread").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
stampedLockTest.write();
}, "writeThread").start();
Thread.sleep(5000);
//乐观读
System.out.println("====邮戳版====");
new Thread(() -> {
stampedLockTest.tryOptimisticRead();
}, "readThread_new").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
stampedLockTest.write();
}, "writeThread_new").start();
}
}
StampedLock的缺点:
1、StampedLock不支持重入,没有Re开头
2、StampedLock的悲观读和写锁都不支持条件变量(Condition),这个也需要注意
3、使用StampedLock一定不要调用中断操作,即不要调用interrupt()方法
Java 并发编程(三)-Callable & JUC辅助类
每天⽤⼼记录⼀点点。内容也许不重要,但习惯很重要!
一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!