Summary of common java locks
Distinguish between various lock mechanisms and how to use them
Usage methods | Lock name |
---|---|
Check whether the thread wants to lock the synchronization resource | Optimistic lock and pessimistic lock |
After locking the synchronization resource, do you want to block? | If you do not block, you can use the spin lock |
One thread and multiple processes acquire the same lock | Reentrant lock |
Multiple threads share a common lock | Read-write lock (shared lock for writing) |
Should multiple threads compete to queue up? | Fair lock and unfair lock |
Pessimistic locking: Multiple people cannot be executed at the same time. Lock first when executing. Many such locking mechanisms are used in traditional relational databases, such as row locks, table locks, read locks, write locks, etc., which are all locked before operations.
Optimistic locking: through version Whether the numbers are consistent or not is to add a version to the data, update the data synchronously and add a version number. It will not be locked, the version number can be determined, and can be operated by multiple people, similar to ticket grabbing in life. Every time you go to get the data, you think that others will not modify it, so it will not be locked. However, when updating, you will judge whether others have updated the data during this period. You can use mechanisms such as version numbers. Optimistic locking is suitable for multi-read application types, which can improve throughput. Redis uses this check-and-set mechanism to implement transactions
(optimistic locking can be implemented using the version number mechanism and CAS algorithm)
Through specific Case demonstrates pessimistic locking and optimistic locking
In the redis framework
Before executing multi, execute the command watch
The specific format is as follows
watch key1 [key2]
The specific code format is as follows
127.0.0.1:6379> flushdb OK 127.0.0.1:6379> set add 100 OK 127.0.0.1:6379> watch add OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> incrby add 20 QUEUED 127.0.0.1:6379(TX)> exec 1) (integer) 120 127.0.0.1:6379>
flushdb clears the database
But if you enter exec on another server, an error will be displayed
because the Optimistic lock, the version will change after being modified
In general:
Pessimistic lock: When each person completes the thing alone, the lock and unlock are executed. To solve the problem of concurrency, it does not support concurrent operations. It can only operate one at a time, which is inefficient.
Optimistic lock: Every time something is executed, the data version number will be compared. Whoever submits first will submit the version number first
Fair lock: first come, first served
Unfair lock: not in order, you can jump in the queue
Fair lock: relatively low efficiency
Unfair lock: high efficiency, but threads are prone to starvation
Lock through this function lock = new ReentrantLock(true);. Create a reentrant lock, true means fair lock, false means unfair lock. Default unfair lock
By viewing the source code
ReentrantLock(true) with parameters is a fair lock
ReentrantLock(false) is an unfair lock
Mainly call NonfairSync() and FairSync()
public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
Specify the source code of unfair lock and fair lock
View the source code of fair lock
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; /** * Acquires only if reentrant or queue is empty. */ final boolean initialTryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedThreads() && compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } } else if (getExclusiveOwnerThread() == current) { if (++c < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(c); return true; } return false; }
Detailed operation through code examples
//第一步 创建资源类,定义属性和和操作方法 class LTicket { //票数量 private int number = 30; //创建可重入锁 private final ReentrantLock lock = new ReentrantLock(true); //卖票方法 public void sale() { //上锁 lock.lock(); try { //判断是否有票 if(number > 0) { System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number); } } finally { //解锁 lock.unlock(); } } } public class LSaleTicket { //第二步 创建多个线程,调用资源类的操作方法 //创建三个线程 public static void main(String[] args) { LTicket ticket = new LTicket(); new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } },"AA").start(); new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } },"BB").start(); new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } },"CC").start(); } }
The result screenshot is as follows
A thread is executed, but BC thread is not executed, and unfair lock occurs
To specifically change its settings, you can use a parameterized constructor in the reentrant lock
Modify the code to private final ReentrantLock lock = new ReentrantLock(true);
The code screenshot is
Reentrant lock is also called recursive lock
And with the reentrant lock, the first crack is After that, you can enter the inner structure all the way
Object o = new Object(); new Thread(()->{ synchronized(o) { System.out.println(Thread.currentThread().getName()+" 外层"); synchronized (o) { System.out.println(Thread.currentThread().getName()+" 中层"); synchronized (o) { System.out.println(Thread.currentThread().getName()+" 内层"); } } } },"t1").start();
synchronized (o) means locking the code block in the current { }
The above are all synchronized lock mechanisms
Explained below lock mechanism
public class SyncLockDemo { public synchronized void add() { add(); } public static void main(String[] args) { //Lock演示可重入锁 Lock lock = new ReentrantLock(); //创建线程 new Thread(()->{ try { //上锁 lock.lock(); System.out.println(Thread.currentThread().getName()+" 外层"); try { //上锁 lock.lock(); System.out.println(Thread.currentThread().getName()+" 内层"); }finally { //释放锁 lock.unlock(); } }finally { //释放做 lock.unlock(); } },"t1").start(); //创建新线程 new Thread(()->{ lock.lock(); System.out.println("aaaa"); lock.unlock(); },"aa").start(); } }
For nested locks in the same lock, the internal nested lock can still be output without being unlocked, but if you jump out of the thread and execute another thread, it will cause a deadlock
To grasp the concept of locking and unlocking, you must write
Read lock is Shared locks and write locks are exclusive locks
A specific implementation of shared locks
Read-write locks manage a group of locks, one is only There are read locks and one is a write lock.
Read-write lock: A resource can be accessed by multiple read threads or by one write thread, but read and write threads cannot exist at the same time, read-write mutual exclusion, read-read sharing (Write lock is exclusive, read lock is shared, write lock priority is higher than read lock)
Read-write lock ReentrantReadWriteLock
Read lock is ReentrantReadWriteLock.ReadLock, readLock() method
The write lock is ReentrantReadWriteLock.WriteLock, writeLock() method
Create a read-write lock object private ReadWriteLock rwLock = new ReentrantReadWriteLock();
Write lock rwLock.writeLock().lock( );, unlocking is rwLock.writeLock().unlock();
Read lock and lock rwLock.readLock().lock();, unlocking is rwLock.readLock().unlock();
Case analysis:
Simulate multi-threading to fetch and read data in the map
The complete code is as follows
//资源类 class MyCache { //创建map集合 private volatile Map<String,Object> map = new HashMap<>(); //创建读写锁对象 private ReadWriteLock rwLock = new ReentrantReadWriteLock(); //放数据 public void put(String key,Object value) { //添加写锁 rwLock.writeLock().lock(); try { 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; } } public class ReadWriteLockDemo { public static void main(String[] args) throws InterruptedException { 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(); } TimeUnit.MICROSECONDS.sleep(300); //创建线程取数据 for (int i = 1; i <=5; i++) { final int num = i; new Thread(()->{ myCache.get(num+""); },String.valueOf(i)).start(); } } }
5. Mutex lock
Mutual exclusion lock is a conventional implementation of exclusive lock, which means that a certain resource only allows one visitor to access it at the same time, and is unique and exclusive
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//创建互斥锁并初始化 pthread_mutex_lock(&mutex);//对线程上锁,此时其他线程阻塞等待该线程释放锁 //要执行的代码段 pthread_mutex_unlock(&mutex);//执行完后释放锁
Check Baidu Encyclopedia’s explanation, the details are as follows:
它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名
通俗的来说就是一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务。
其特点:
持有锁时间等待过长,消耗CPU
无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题
自旋锁不会使线程状态发生切换,处于用户态(不会到内核态进行线程的状态转换),一直都是活跃,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
其模拟算法如下
do{ b=1; while(b){ lock(bus); b = test_and_set(&lock); unlock(bus); } //临界区 //lock = 0; //其余部分 }while(1)
无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功
偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价
轻量级锁:锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能
重量级锁:线程并发加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,还有线程要访问
The above is the detailed content of What are the mechanisms of various locks in java. For more information, please follow other related articles on the PHP Chinese website!