1、鎖java
線程安全安全
``` 多線程網站統計訪問人數 使用鎖,維護計數器的串行訪問與安全性 多線程訪問ArrayList ``` ``` public static List<Integer> numberList =new ArrayList<Integer>(); public static class AddToList implements Runnable{ int startnum=0; public AddToList(int startnumber){ startnum=startnumber; } @Override public void run() { int count=0; while(count<1000000){ numberList.add(startnum); startnum+=2; count++; } } } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(new AddToList(0)); Thread t2=new Thread(new AddToList(1)); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ Thread.sleep(1); } System.out.println(numberList.size()); } ``` ``` Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 24552 at java.util.ArrayList.add(ArrayList.java:463) at LockRunnable$AddToList.run(JvmLockCompare.java:101) at java.lang.Thread.run(Thread.java:748) 1010143 ```
對象頭Mark多線程
Mark Word,對象頭的標記,32位 描述對象的hash、鎖信息,垃圾回收標記,年齡 指向鎖記錄的指針 指向monitor的指針 GC標記 偏向鎖線程ID
偏向鎖併發
大部分狀況是沒有競爭的,因此能夠經過偏向來提升性能 所謂的偏向,就是偏愛,即鎖會偏向於當前已經佔有鎖的線程 將對象頭Mark的標記設置爲偏向,並將線程ID寫入對象頭Mark 只要沒有競爭,得到偏向鎖的線程,在未來進入同步塊,不須要作同步 當其餘線程請求相同的鎖時,偏向模式結束 -XX:+UseBiasedLocking 默認啓用 在競爭激烈的場合,偏向鎖會增長系統負擔
本例中,使用偏向鎖,能夠得到5%以上的性能提高app
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0ide
-XX:-UseBiasedLocking性能
public static List<Integer> numberList =new Vector<Integer>(); public static void main(String[] args) throws InterruptedException { long begin=System.currentTimeMillis(); int count=0; int startnum=0; while(count<10000000){ numberList.add(startnum); startnum+=2; count++; } long end=System.currentTimeMillis(); System.out.println(end-begin); }
輕量級鎖優化
若是輕量級鎖失敗,表示存在競爭,升級爲重量級鎖(常規鎖) 在沒有鎖競爭的前提下,減小傳統鎖使用OS互斥量產生的性能損耗 在競爭激烈時,輕量級鎖會多作不少額外操做,致使性能降低
自旋鎖網站
當競爭存在時,若是線程能夠很快得到鎖,那麼能夠不在OS層掛起線程,讓線程作幾個空操做(自旋) JDK1.6中-XX:+UseSpinning開啓 JDK1.7中,去掉此參數,改成內置實現 若是同步塊很長,自旋失敗,會下降系統性能 若是同步塊很短,自旋成功,節省線程掛起切換時間,提高系統性能
偏向鎖,輕量級鎖,自旋鎖總結this
不是Java語言層面的鎖優化方法 內置於JVM中的獲取鎖的優化方法和獲取鎖的步驟 偏向鎖可用會先嚐試偏向鎖 輕量級鎖可用會先嚐試輕量級鎖 以上都失敗,嘗試自旋鎖 再失敗,嘗試普通鎖,使用OS互斥量在操做系統層掛起
減小鎖持有時間
減少鎖粒度
ConcurrentHashMap 若干個Segment :Segment<K,V>[] segments Segment中維護HashEntry<K,V> put操做時 先定位到Segment,鎖定一個Segment,執行put 在減少鎖粒度後, ConcurrentHashMap容許若干個線程同時進入
鎖分離
鎖粗化
一般狀況下,爲了保證多線程間的有效併發,會要求每一個線程持有鎖的時間儘可能短,即在使用完公共資源後,應該當即釋放鎖。只有這樣,等待在這個鎖上的其餘線程才能儘早的得到資源執行任務。可是,凡事都有一個度,若是對同一個鎖不停的進行請求、同步和釋放,其自己也會消耗系統寶貴的資源,反而不利於性能的優化
示例1
示例2
鎖消除
在即時編譯器時,若是發現不可能被共享的對象,則能夠消除這些對象的鎖操做
public static void main(String args[]) throws InterruptedException { long start = System.currentTimeMillis(); for (int i = 0; i < CIRCLE; i++) { craeteStringBuffer("JVM", "Diagnosis"); } long bufferCost = System.currentTimeMillis() - start; System.out.println("craeteStringBuffer: " + bufferCost + " ms"); } public static String craeteStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); //同步操做 sb.append(s1); sb.append(s2); return sb.toString(); }
CIRCLE=20000000
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
craeteStringBuffer: 1057 ms
-server -XX:+DoEscapeAnalysis -XX:-EliminateLocks
craeteStringBuffer: 1857 ms
無鎖
鎖是悲觀的操做 無鎖是樂觀的操做 無鎖的一種實現方式 CAS(Compare And Swap) 非阻塞的同步 CAS(V,E,N) 在應用層面判斷多線程的干擾,若是有干擾,則通知線程重試
示例
鎖使用錯誤實例
錯誤:
/** * description: * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/10/6 12:53 AM */ public class IntegerLock { static Integer i = 0; public static class AddThread extends Thread { public void run() { for (int k = 0; k < 100000; k++) { synchronized (i) { i++; } } } } public static void main(String[] args) throws InterruptedException { AddThread t1 = new AddThread(); AddThread t2 = new AddThread(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * description: * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/10/6 12:53 AM */ public class IntegerLock { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static class ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i = i; } public void run() { try { Date t = sdf.parse("2015-03-29 19:29:" + i % 60); System.out.println(i + ":" + t); } catch (ParseException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { es.execute(new ParseDate(i)); // SimpleDateFormat被多線程訪問 } } } }
若是使用共享實例,起不到效果
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * description: * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/10/6 12:53 AM */ public class IntegerLock { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); static ThreadLocal<SimpleDateFormat> tl=new ThreadLocal<SimpleDateFormat>(); public static class ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i = i; } public void run() { try { if(tl.get()==null){ tl.set(sdf); } Date t=tl.get().parse("2015-03-29 19:29:"+i%60); System.out.println(i + ":" + t); } catch (ParseException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { es.execute(new ParseDate(i)); // SimpleDateFormat被多線程訪問 } } } }
正確:
/** * description: * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/10/6 12:53 AM */ public class IntegerLock { static Integer i = 0; static Object f = new Object(); public static class AddThread extends Thread { public void run() { for (int k = 0; k < 100000; k++) { synchronized (f) { i++; } } } } public static void main(String[] args) throws InterruptedException { AddThread t1 = new AddThread(); AddThread t2 = new AddThread(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
爲每個線程分配一個實例
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * description: * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/10/6 12:53 AM */ public class IntegerLock { static ThreadLocal<SimpleDateFormat> tl=new ThreadLocal<SimpleDateFormat>(); public static class ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i = i; } public void run() { try { if(tl.get()==null){ tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); } Date t=tl.get().parse("2015-03-29 19:29:"+i%60); System.out.println(i + ":" + t); } catch (ParseException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { es.execute(new ParseDate(i)); // SimpleDateFormat被多線程訪問 } } } }