Java 并发编程(二)-多线程锁分类

目录

一、并发编程

1、多线程锁分类

1.1、公平锁 & 非公平锁

1.2、可重入锁 & 不可重入锁

1.3、自旋锁

1.4、死锁

死锁示例:

验证是否是死锁

1.5、悲观锁&乐观锁

1.6、互斥锁 & 共享锁

1.7、表锁 & 行锁

1.8、读锁 & 写锁

ReentrantReadWriteLock读写锁

锁降级

1.9、类锁 & 对象锁

1.10、邮戳锁


一、并发编程

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 并发编程(一)-JUC

Java 并发编程(三)-Callable & JUC辅助类

每天⽤⼼记录⼀点点。内容也许不重要,但习惯很重要!

一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀神lwz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值