JAVA常考點4css
二、Java中Integer型和int型的差異 3mysql
九、servlet裏面有哪些方法?說一說get和post方法的差異 5
十一、具體解釋synchronized與Lock的差異與使用 6
十二、悲觀鎖。樂觀鎖,行鎖。表鎖,頁鎖,共享鎖。排他鎖 17
弄清怎麼個邏輯達到元素不反覆的,源代碼先上
HashSet 類中的add()方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
類中map和PARENT的定義:
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map用來匹配Map中後面的對象的一個虛擬值
private static final Object PRESENT = new Object();
put()方法:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
可以看到for循環中,遍歷table中的元素,
假設hash碼值不一樣樣。說明是一個新元素,存。
假設沒有元素和傳入對象(也就是add的元素)的hash值相等,那麼就以爲這個元素在table中不存在,將其加入進table;
假設hash碼值一樣,且equles推斷相等,說明元素已經存在,不存;
假設hash碼值一樣,且equles推斷不相等。說明元素不存在,存;
假設有元素和傳入對象的hash值相等,那麼,繼續進行equles()推斷,假設仍然相等,那麼就以爲傳入元素已經存在,再也不加入。結束,不然仍然加入;
可見hashcode()和equles()在此顯得很是關鍵了,如下就來解讀一下hashcode和equles:
首先要明白:僅僅經過hash碼值來推斷兩個對象時否一樣合適嗎?答案是不合適的,因爲有可能兩個不一樣的對象的hash碼值一樣;
什麼是hash碼值?
在java中存在一種hash表結構,它經過一個算法,計算出的結果就是hash碼值;這個算法叫hash算法。
hash算法是怎麼計算的呢?
是經過對象中的成員來計算出來的結果;
假設成員變量是基本數據類型的值。 那麼用這個值 直接參與計算;
假設成員變量是引用數據類型的值,那麼獲取到這個成員變量的哈希碼值後。再參數計算
如:新建一個Person對象。重寫hashCode方法
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ?
0 : name.hashCode());
return result;
}
可以看出,Person對象內兩個參數name,age。hash碼值是這二者計算後的記過,那麼全然有可能兩個對象name。age都不一樣,hash碼值一樣;
如下看下equles()方法:
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
equles方法內部是分別對name,age進行推斷,是否相等。
綜合上述,在向hashSet中add()元素時。推斷元素是否存在的根據,不只僅是hash碼值就可以肯定的,同一時候還要結合equles方法。
a.Java 中的數據類型分爲基本數據類型和引用數據類型。int是基本數據類型,直接存放值。而而Integer是對象。用一個引用指向這個對象。Ingeter是int的包裝類。int的初值爲0。Ingeter的初值爲null。
b.初始化
int i =1。
Integer i= new Integer(1)。
有了本身主動裝箱和拆箱。使得對Integer類也可以使用:
Integer i= 100;
實際上上面這個代碼調用了:
Integer i = Integer.valueOf(100);
c.Integer是int的封裝類,int和Integer都可以表示某一個數值,int和Integer不可以互用,因爲他們兩種不一樣的數據類型。
接口不只可以繼承接口,而且一個接口可以繼承多個接口,接口是特殊的抽象類。
抽象類不可以繼承接口。但可以實現接口。抽象類可以繼承實體類,但是不能繼承接口。
長處:建立索引主要可以大大提升系統性能,主要優點有如下5點:
經過建立惟一性索引,可以保證數據庫表中每一行數據的惟一性。
可以大大加快 數據的檢索速度,這也是建立索引的最基本的緣由。
可以加速表和表之間的鏈接,特別是在實現數據的參考完整性方面特別有意義。
在使用分組和排序 子句進行數據檢索時,一樣可以顯著下降查詢中分組和排序的時間。
經過使用索引。可以在查詢的過程當中。使用優化隱藏器,提升系統的性能。
缺點:
建立索引和維護索引要耗費時間。這樣的時間隨着數據 量的添加而添加。
索引需要佔物理空間,除了數據表佔數據空間以外,每一個索引還要佔必定的物理空間,假設要創建聚簇索引,那麼需要的空間就會更大。
當對錶中的數據進行添加、刪除和改動的時候,索引也要動態的維護。這樣就下降了數據的維護速度。
索引主要有兩個特徵:惟一性索引和複合索引。
ALTER TABLE 表名 MODIFY COLUMN 字段名 字段類型定義;(注意不是update)
HAVING 就像WHERE條件同樣。按指定要求來取數據集。
僅僅只是WHERE通常數據查詢來指定條件,HAVING是用在GROUP BY 分組來指定條件。
HAVING 子句運作起來很是象 WHERE 子句。 僅僅用於對那些知足 HAVING 子句裏面給出的條件的組進行計算。
事實上,WHERE 在分組和彙集以前過濾掉咱們不需要的輸入行, 而 HAVING 在 GROUP 以後那些不需要的組. 所以,WHERE 沒法使用一個彙集函數的結果. 而另外一方面,咱們也沒有理由寫一個不涉及彙集函數的 HAVING. 假設你的條件不包括彙集。那麼你也可以把它寫在 WHERE 裏面。 這樣就可以避免對那些你準備拋棄的行進行的彙集運算.
歸併排序:簡單來講就是先將數組不斷細分紅最小的單位,而後每一個單位分別排序,排序完成後合併,反覆以上過程最後就可以獲得排序結果。
複雜度:O(nlogn) 妥當
高速排序:簡單來講就是先選定一個基準元素,而後以該基準元素劃分數組,再在被劃分的部分反覆以上過程,最後可以獲得排序結果。
複雜度:O(nlogn) 不穩定
二者都是用分治法的思想。只是最後歸併排序的合併操做比高速排序的要繁瑣。
final修飾類,類不能被繼承
final修飾方法。方法不能被覆寫
final修飾的變量初始化後則不能被改動。
init() 、destroy() 、service()等。
在servlet開發中,以doGet()和doPost()分別處理get和post方法。另外另外一個doService(), 它是一個調度方法,當一個請求發生時,首先運行doService(),不管是get仍是post。在HttpServlet這個基類中實現了一個角度。首先推斷是請求時get仍是post,假設是get就調用doGet(), 假設是post就調用doPost()。
你也可以直接過載doService()方法,這樣你可以不管是get仍是post。都會運行這種方法。
get和post方法的差異:
a)順序存儲結構:
長處:
隨機讀取(時間複雜度爲O(1))
無需爲表示表中元素之間的邏輯關係而添加額外的存儲空間
缺點:
插入、刪除操做需要移動大量元素。效率低(時間複雜度爲O(n))。
表的長度難以肯定
b)鏈式存儲結構.
長處:
插入、刪除不需要移動數據。效率高(時間複雜度爲O(1));
缺點:
存取時需要遍歷,效率低(時間複雜度爲O(n));
順序存儲結構通常用於:頻繁查找,很是少插入、刪除;而鏈式存儲結構通常用於頻繁插入、刪除;
引言:
昨天在學習別人分享的面試經驗時,看到Lock的使用。
想起本身在上次面試也遇到了synchronized與Lock的差異與使用。
因而。我整理了二者的差異和使用狀況,同一時候,對synchronized的使用過程一些常見問題的總結。最後是參照源代碼和說明文檔。對Lock的使用寫了幾個簡單的Demo。
請你們批評指正。
技術點:
(1)線程與進程:
在開始以前先把進程與線程進行區分一下。一個程序最少需要一個進程,而一個進程最少需要一個線程。
關係是線程–>進程–>程序的大體組成結構。因此線程是程序運行流的最小單位。而進程是系統進行資源分配和調度的一個獨立單位。如下咱們所有討論的都是創建在線程基礎之上。
(2)Thread的幾個重要方法:
咱們先了解一下Thread的幾個重要方法。
a、start()方法,調用該方法開始運行該線程;b、stop()方法,調用該方法強制結束該線程運行。c、join方法,調用該方法等待該線程結束。d、sleep()方法,調用該方法該線程進入等待。
e、run()方法,調用該方法直接運行線程的run()方法,但是線程調用start()方法時也會運行run()方法。差異就是一個是由線程調度運行run()方法,一個是直接調用了線程中的run()方法!
!
看到這裏,可能有些人就會問啦。那wait()和notify()呢?要注意。事實上wait()與notify()方法是Object的方法。不是Thread的方法。!同一時候,wait()與notify()會配合使用,分別表示線程掛起和線程恢復。
這裏另外一個非常常見的問題,順帶提一下:wait()與sleep()的差異。簡單來講wait()會釋放對象鎖而sleep()不會釋放對象鎖。
這些問題有很是多的資料,再也不贅述。
(3)線程狀態:
線程總共同擁有5大狀態,經過上面第二個知識點的介紹。理解起來就簡單了。
新建狀態:新建線程對象。並無調用start()方法以前
就緒狀態:調用start()方法以後線程就進入就緒狀態。但是並不是說僅僅要調用start()方法線程就當即變爲當前線程,在變爲當前線程以前都是爲就緒狀態。
值得一提的是,線程在睡眠和掛起中恢復的時候也會進入就緒狀態哦。
運行狀態:線程被設置爲當前線程,開始運行run()方法。就是線程進入運行狀態
堵塞狀態:線程被暫停,比方說調用sleep()方法後線程就進入堵塞狀態
死亡狀態:線程運行結束
(4)鎖類型
可重入鎖:在運行對象中所有同步方法不用再次得到鎖
可中斷鎖:在等待獲取鎖過程當中可中斷
公平鎖: 按等待獲取鎖的線程的等待時間進行獲取,等待時間長的具備優先獲取鎖權利
讀寫鎖:對資源讀取和寫入的時候拆分爲2部分處理。讀的時候可以多線程一塊兒讀,寫的時候必須同步地寫
synchronized與Lock的差異
(1))我把二者的差異分類到了一個表中,方便你們對照:
也許,看到這裏還對LOCK所知甚少,那麼接下來,咱們進入LOCK的深刻學習。
Lock具體介紹與Demo
如下是Lock接口的源代碼。筆者修剪以後的結果:
public interface Lock {
/**
* Acquires the lock.
*/
void lock();
/**
* Acquires the lock unless the current thread is
* {@linkplain Thread#interrupt interrupted}.
*/
void lockInterruptibly() throws InterruptedException;
/**
* Acquires the lock only if it is free at the time of invocation.
*/
boolean tryLock();
/**
* Acquires the lock if it is free within the given waiting time and the
* current thread has not been {@linkplain Thread#interrupt interrupted}.
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* Releases the lock.
*/
void unlock();
}
從Lock接口中咱們可以看到主要有個方法。這些方法的功能從凝視中可以看出:
lock():獲取鎖,假設鎖被暫用則一直等待
unlock():釋放鎖
tryLock(): 注意返回類型是boolean。假設獲取鎖的時候鎖被佔用就返回false,不然返回true
tryLock(long time, TimeUnit unit):比起tryLock()就是給了一個時間期限,保證等待參數時間
lockInterruptibly():用該鎖的得到方式,假設線程在獲取鎖的階段進入了等待,那麼可以中斷此線程,先去作別的事
經過 以上的解釋,大體可以解釋在上個部分中「鎖類型(lockInterruptibly())」,「鎖狀態(tryLock())」等問題。還有就是前面子所獲取的過程我所寫的「大體就是可以嘗試得到鎖,線程可以不會一直等待」用了「可以」的緣由。
如下是Lock通常使用的樣例,注意ReentrantLock是Lock接口的實現。
lock():
package com.brickworkers;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
//需要參與同步的方法
private void method(Thread thread){
lock.lock();
try {
System.out.println("線程名"+thread.getName() + "得到了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名"+thread.getName() + "釋放了鎖");
lock.unlock();
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//線程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t2");
。t1.start();
。t2.start();
}
}
//運行狀況:線程名t1得到了鎖
// 線程名t1釋放了鎖
// 線程名t2得到了鎖
// 線程名t2釋放了鎖
tryLock():
package com.brickworkers;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
//需要參與同步的方法
private void method(Thread thread){
/* lock.lock();
try {
System.out.println("線程名"+thread.getName() + "得到了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名"+thread.getName() + "釋放了鎖");
lock.unlock();
}*/
if(lock.tryLock()){
try {
System.out.println("線程名"+thread.getName() + "得到了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名"+thread.getName() + "釋放了鎖");
lock.unlock();
}
}else{
System.out.println("我是"+Thread.currentThread().getName()+"有人佔着鎖,我就不要啦");
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//線程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t2");
。t1.start();
。t2.start();
}
}
//運行結果: 線程名t2得到了鎖
// 我是t1有人佔着鎖,我就不要啦
// 線程名t2釋放了鎖
看到這裏相信你們也都會使用怎樣使用Lock了吧。關於tryLock(long time, TimeUnit unit)和lockInterruptibly()再也不贅述。前者主要存在一個等待時間,在測試代碼中寫入一個等待時間。後者主要是等待中斷,會拋出一箇中斷異常,常用度不高,喜歡探究可以本身深刻研究。
前面比較重提到「公平鎖」。在這裏可以提一下ReentrantLock對於平衡鎖的定義。在源代碼中有這麼兩段:
/**
* Sync object for non-fair locks
*/
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);
}
}
/**
* Sync object for fair locks
*/
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;
}
}
從以上源代碼可以看出在Lock中可以本身控制鎖是否公平,而且,默認的是非公平鎖,如下是ReentrantLock的構造函數:
public ReentrantLock() {
sync = new NonfairSync();//默認非公平鎖
}
尾記錄:
筆者水平通常,只是此博客在引言中的目的已所有達到。這僅僅是筆者在學習過程當中的總結與歸納,如存在不對的,歡迎你們批評指出。
延伸學習:對於LOCK底層的實現。你們可以參考:
點擊Lock底層介紹博客
兩種同步方式性能測試,你們可以參考:
點擊查看兩種同步方式性能測試博客
博主18年3月新增:
回來看本身博客。
發現東西闡述的不夠完整。這裏在作補充,因爲這篇博客訪問較大,因此爲了避免誤導你們,儘可能介紹給你們正確的表述:
(1)兩種鎖的底層實現方式:
synchronized:咱們知道java是用字節碼指令來控制程序(這裏不包括熱點代碼編譯成機器碼)。在字節指令中,存在有synchronized所包括的代碼塊。那麼會造成2段流程的運行。
咱們點擊查看SyncDemo.java的源代碼SyncDemo.class,可以看到例如如下:
如上就是這段代碼段字節碼指令。沒你想的那麼難吧。言歸正傳。咱們可以清晰段看到。事實上synchronized映射成字節碼指令就是添加來兩個指令:monitorenter和monitorexit。
當一條線程進行運行的遇到monitorenter指令的時候,它會去嘗試得到鎖。假設得到鎖那麼鎖計數+1(爲何會加一呢,因爲它是一個可重入鎖,因此需要用這個鎖計數推斷鎖的狀況),假設沒有得到鎖,那麼堵塞。當它遇到monitorexit的時候,鎖計數器-1,當計數器爲0。那麼就釋放鎖。
那麼有的朋友看到這裏就疑惑了,那圖上有2個monitorexit呀?當即回答這個問題:上面我曾經寫的文章也有表述過,synchronized鎖釋放有兩種機制,一種就是運行完釋放;第二種就是發送異常。虛擬機釋放。圖中第二個monitorexit就是發生異常時運行的流程,這就是我開頭說的「會有2個流程存在「。
而且,從圖中咱們也可以看到在第13行。有一個goto指令,也就是說假設正常運行結束會跳轉到19行運行。
這下,你對synchronized是否是瞭解的很是清晰了呢。接下來咱們再聊一聊Lock。
Lock:Lock實現和synchronized不同。後者是一種悲觀鎖,它膽子很是小,它很是怕有人和它搶吃的,因此它每次吃東西前都把本身關起來。
而Lock呢底層事實上是CAS樂觀鎖的體現,它無所謂。別人搶了它吃的,它又一次去拿吃的就好啦,因此它很是樂觀。具體底層怎麼實現,博主不在細述。有機會的話。我會對concurrent包如下的機制好好和你們說說,假設面試問起。你就說底層主要靠volatile和CAS操做實現的。
現在,纔是我真正想在這篇博文後面加的,我要說的是:儘量去使用synchronized而不要去使用LOCK
什麼概念呢?我和你們打個比方:你叫jdk,你生了一個孩子叫synchronized,後來呢,你領養了一個孩子叫LOCK。起初。LOCK剛來到新家的時候,它很是乖,很是懂事。各個方面都表現的比synchronized好。
你很是開心,但是你心裏深處又有一點淡淡的憂傷。你不但願你本身親生的孩子竟然還不如一個領養的孩子乖巧。這個時候,你對親生的孩子教育更加深入了。你想證實。你的親生孩子synchronized並不會比領養的孩子LOCK差。(博主僅僅是打個比方)
那怎樣教育呢?
在jdk1.6~jdk1.7的時候,也就是synchronized1六、7歲的時候。你做爲爸爸,你給他優化了,具體優化在哪裏呢:
(1)線程自旋和適應性自旋
咱們知道。java’線程事實上是映射在內核之上的。線程的掛起和恢復會極大的影響開銷。
而且jdk官方人員發現,很是多線程在等待鎖的時候。在很是短的一段時間就得到了鎖,因此它們在線程等待的時候,並不需要把線程掛起,而是讓他無目的的循環。通常設置10次。這樣就避免了線程切換的開銷,極大的提高了性能。
而適應性自旋,是賦予了自旋一種學習能力。它並不固定自旋10次一下。
他可以根據它前面線程的自旋狀況,從而調整它的自旋,甚至是不通過自旋而直接掛起。
(2)鎖消除
什麼叫鎖消除呢?就是把沒必要要的同步在編譯階段進行移除。
那麼有的小夥伴又迷糊了,我本身寫的代碼我會不知道這裏要不要加鎖?我加了鎖就是表示這邊會有同步呀?
並不是這樣。這裏所說的鎖消除並不必定指代是你寫的代碼的鎖消除,我打一個比方:
在jdk1.5曾經。咱們的String字符串拼接操做事實上底層是StringBuffer來實現的(這個你們可以用我前面介紹的方法。寫一個簡單的demo,而後查看class文件裏的字節碼指令就清晰了),而在jdk1.5以後,那麼是用StringBuilder來拼接的。
咱們考慮前面的狀況,比方例如如下代碼:
String str1="qwe";
String str2="asd";
String str3=str1+str2;
底層實現會變成這樣:
StringBuffer sb = new StringBuffer();
sb.append("qwe");
sb.append("asd");
咱們知道。StringBuffer是一個線程安全的類。也就是說兩個append方法都會同步。經過指針逃逸分析(就是變量不會外泄),咱們發現在這段代碼並不存在線程安全問題。這個時候就會把這個同步鎖消除。
(3)鎖粗化
在用synchronized的時候,咱們都講究爲了不大開銷,儘可能同步代碼塊要小。那麼爲何還要加粗呢?
咱們繼續以上面的字符串拼接爲例,咱們知道在這一段代碼中,每一個append都需要同步一次。那麼我可以把鎖粗化到第一個append和最後一個append(這裏不要去糾結前面的鎖消除。我僅僅是打個比方)
(4)輕量級鎖
(5)偏向鎖
關於最後這兩種,我但願留個有緣的讀者本身去查找,我不但願我把一件事情描寫敘述的那麼具體,本身動手獲得纔是你本身的,博主可以告訴你的是。最後兩種並不難。
。加油吧,各位。
悲觀鎖:
顧名思義,很是悲觀,就是每次拿數據的時候都以爲別的線程會改動數據,因此在每次拿的時候都會給數據上鎖。上鎖以後,當別的線程想要拿數據時。就會堵塞。直到給數據上鎖的線程將事務提交或者回滾。傳統的關係型數據庫裏就用到了很是多這樣的鎖機制,比方行鎖。表鎖。共享鎖,排他鎖等,都是在作操做以前先上鎖。
行鎖:
如下演示行鎖,打開兩個mysql命令行界面。兩個線程分別運行例如如下操做:(左邊先運行)
左邊的線程,在事務中經過select for update語句給sid = 1的數據行上了鎖。右邊的線程此時可以使用select語句讀取數據,但是假設也使用select for update語句,就會堵塞,使用update。add,delete也會堵塞。
當左邊的線程將事務提交(或者回滾),右邊的線程就會獲取鎖,線程再也不堵塞:
此時,右邊的線程獲取鎖。左邊的線程假設運行類似操做,也會被堵塞:
表鎖:
上述樣例中,假設使用例如如下語句就是使用的表鎖:
select * from student for update;
頁鎖:
行鎖鎖指定行。表鎖鎖整張表。頁鎖是折中實現。即一次鎖定相鄰的一組記錄。
共享鎖:
共享鎖又稱爲讀鎖,一個線程給數據加上共享鎖後,其它線程僅僅能讀數據,不能改動。
排他鎖:
排他鎖又稱爲寫鎖。和共享鎖的差異在於。其它線程既不能讀也不能改動。
樂觀鎖:
樂觀鎖事實上不會上鎖。顧名思義。很是樂觀,它默認別的線程不會改動數據,因此不會上鎖。
僅僅是在更新前去推斷別的線程在此期間有沒有改動數據。假設改動了。會交給業務層去處理。
常用的實現方式是使用版本號戳,好比在一張表中加入一個整型字段version,每更新version++,比方某個時刻version=1,線程A讀取了此version=1,線程B也讀取了此version=1,當線程A更新數據以前,推斷version仍然爲1。更新成功。version++變爲2。但是當線程B再提交更新時,發現version變爲2了,與以前讀的version=1不一致,就知道有別的線程更新了數據,這個時候就會進行業務邏輯的處理。
一般狀況下,寫操做較少時,使用樂觀鎖,寫操做較多時,使用悲觀鎖。