總結java常見的鎖定
區分各個鎖定機制以及如何使用
鎖定名稱 | |
---|---|
樂觀鎖定與悲觀鎖定 | |
不阻塞可以使用自旋鎖定 | |
#可重入鎖定 | |
讀取寫入鎖定(寫入的共享鎖定) | |
# #多個執行緒競爭要不要排隊 | 公平鎖定與非公平鎖定 |
悲觀鎖:不能同時進行多人,執行的時候先上鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,例如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖
樂觀鎖:透過版本號一致與否,即為資料加上版本,同步更新資料以及加上版本號碼。不會上鎖,判斷版本號,可以多人操作,類似生活中的搶票。每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀取的應用類型,這樣可以提高吞吐量。 Redis就是利用這種check-and-set機制實現事務的
(樂觀鎖可以使用版本號機制和CAS演算法實現)
透過具體案例示範悲觀鎖定和樂觀鎖定
在redis框架中
執行multi之前,執行指令watch
#具體格式如下
watch key1 [key2]
具體程式碼格式如下
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是清空資料庫
但如果在另一個伺服器上,輸入exec,會顯示出錯
因為用的是樂觀鎖,被修改了之後版本會發生改變
總的來說:
悲觀鎖:單獨每個人完成事情的時候,執行上鎖解鎖。解決並發中的問題,不支援並發操作,只能一個一個操作,效率低
樂觀鎖:每執行一件事情,都會比較資料版本號,誰先提交,誰先提交版本號
公平鎖定:先來先到
非公平鎖定:不是依照順序,可插隊
公平鎖定:效率相對低
非公平鎖定:效率高,但是執行緒容易餓死
透過這個函數Lock lock = new ReentrantLock(true);。建立一個可重入鎖,true 表示公平鎖,false 表示非公平鎖。預設非公平鎖定
透過查看原始碼
帶有參數的ReentrantLock(true)為公平鎖定
ReentrantLock(false)為非公平鎖定
##主要是呼叫NonfairSync()與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(); }
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; }
//第一步 创建资源类,定义属性和和操作方法 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(); } }
3. 可重入鎖
可重入鎖也叫遞歸鎖
而且有了可重入鎖之後,破解第一把之後就可以一直進入到內層結構
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();
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(); } }
#讀鎖是共享鎖,寫鎖是獨佔鎖
共享鎖的一種具體實作
讀寫鎖管理一組鎖,一個是只讀的鎖,一個是寫鎖。
讀寫鎖定:一個資源可以被多個讀取線程訪問,也可以被一個寫線程訪問,但不能同時存在讀寫線程,讀寫互斥,讀讀共享(寫鎖獨佔,讀鎖共享,寫鎖優先權高於讀鎖)
讀寫鎖ReentrantReadWriteLock
讀鎖為ReentrantReadWriteLock.ReadLock,readLock()方法
寫鎖為ReentrantReadWriteLock.WriteLock,writeLock()方法
建立讀寫鎖定物件private ReadWriteLock rwLock = new ReentrantReadWriteLock();
寫入鎖定rwLock.writeLock().lock(writeLock().lock(writeLock().lock(writeLock().lock( );,解鎖為rwLock.writeLock().unlock();
案例分析:
###模擬多執行緒在map中取資料和讀取資料######完整程式碼如下###//资源类 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(); } } }
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//创建互斥锁并初始化 pthread_mutex_lock(&mutex);//对线程上锁,此时其他线程阻塞等待该线程释放锁 //要执行的代码段 pthread_mutex_unlock(&mutex);//执行完后释放锁
它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名
通俗的来说就是一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务。
其特点:
持有锁时间等待过长,消耗CPU
无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题
自旋锁不会使线程状态发生切换,处于用户态(不会到内核态进行线程的状态转换),一直都是活跃,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
其模拟算法如下
do{ b=1; while(b){ lock(bus); b = test_and_set(&lock); unlock(bus); } //临界区 //lock = 0; //其余部分 }while(1)
无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功
偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价
轻量级锁:锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能
重量级锁:线程并发加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,还有线程要访问
以上是java中各類鎖的機制是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!