<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      深入理解Java線程安全與鎖優化

      一、概述:從現實世界到計算機世界

      在軟件開發的早期,程序員采用面向過程的編程思想,將數據和操作分離。而面向對象編程則更符合現實世界的思維方式,把數據和行為都封裝在對象中。然而,現實世界與計算機世界之間存在一個重要差異:在計算機世界中,對象的工作可能會被頻繁中斷和切換,屬性可能在中斷期間被修改,這導致了線程安全問題的產生。

      // 一個簡單的計數器類
      public class Counter {
          private int count = 0;
          
          public void increment() {
              count++; // 非原子操作,存在線程安全問題
          }
          
          public int getCount() {
              return count;
          }
      }
      

      當我們開始討論"高效并發"時,首先需要確保并發的正確性,然后才考慮如何實現高效。這正是本章要探討的核心內容。

      二、線程安全的定義與分類

      2.1 什么是線程安全?

      Brian Goetz在《Java并發編程實戰》中給出了一個精準的定義:

      "當多個線程同時訪問一個對象時,如果不用考慮這些線程在運行時環境下的調度和交替執行,也不需要進行額外的同步,或者在調用方進行任何其他的協調操作,調用這個對象的行為都可以獲得正確的結果,那就稱這個對象是線程安全的。"

      這個定義要求線程安全的代碼必須封裝所有必要的正確性保障手段,使調用者無需關心多線程問題。

      2.2 Java語言中的線程安全等級

      我們可以按照線程安全的"安全程度"將Java中的共享數據操作分為五類:

      1. 不可變(Immutable)

      不可變對象一定是線程安全的,因為它們的可見狀態永遠不會改變。

      // 使用final關鍵字創建不可變對象
      public final class ImmutableValue {
          private final int value;
          
          public ImmutableValue(int value) {
              this.value = value;
          }
          
          public int getValue() {
              return value;
          }
          
          // 返回新對象而不是修改現有對象
          public ImmutableValue add(int delta) {
              return new ImmutableValue(this.value + delta);
          }
      }
      

      Java中的String、Integer、Long等包裝類都是不可變的。

      2. 絕對線程安全

      絕對線程安全完全滿足Brian Goetz的定義,但實踐中很難實現。即使Java中標注為線程安全的類,如Vector,也并非絕對線程安全。

      // Vector的線程安全局限性示例
      public class VectorTest {
          private static Vector<Integer> vector = new Vector<>();
          
          public static void main(String[] args) {
              while (true) {
                  for (int i = 0; i < 10; i++) {
                      vector.add(i);
                  }
                  
                  Thread removeThread = new Thread(() -> {
                      for (int i = 0; i < vector.size(); i++) {
                          vector.remove(i);
                      }
                  });
                  
                  Thread printThread = new Thread(() -> {
                      for (int i = 0; i < vector.size(); i++) {
                          System.out.println(vector.get(i));
                      }
                  });
                  
                  removeThread.start();
                  printThread.start();
                  
                  // 不要同時產生過多線程,防止操作系統假死
                  while (Thread.activeCount() > 20) ;
              }
          }
      }
      

      上述代碼可能拋出ArrayIndexOutOfBoundsException,因為雖然Vector的每個方法都是同步的,但復合操作(先檢查再執行)仍需外部同步。

      3. 相對線程安全

      相對線程安全保證單次操作是線程安全的,但特定順序的連續調用可能需要外部同步。Java中大部分聲稱線程安全的類屬于此類,如Vector、HashTable等。

      4. 線程兼容

      線程兼容指對象本身不是線程安全的,但可以通過正確使用同步手段保證安全。如ArrayList、HashMap等。

      5. 線程對立

      線程對立指無論是否采取同步措施,都無法在多線程環境中安全使用。如Thread類的suspend()和resume()方法。

      三、線程安全的實現方法

      3.1 互斥同步

      互斥同步是最常見的并發保障手段,synchronized是最基本的互斥同步手段。

      synchronized的實現原理

      public class SynchronizedExample {
          // 同步實例方法
          public synchronized void instanceMethod() {
              // 同步代碼
          }
          
          // 同步靜態方法
          public static synchronized void staticMethod() {
              // 同步代碼
          }
          
          public void method() {
              // 同步塊
              synchronized(this) {
                  // 同步代碼
              }
          }
      }
      

      synchronized編譯后會在同步塊前后生成monitorenter和monitorexit字節碼指令。執行monitorenter時:

      1. 如果對象未被鎖定,或當前線程已持有鎖,則鎖計數器+1
      2. 如果獲取鎖失敗,當前線程阻塞直到鎖被釋放

      synchronized的特性:

      • 可重入:同一線程可重復獲取同一把鎖
      • 阻塞性:未獲取鎖的線程會無條件阻塞
      • 重量級:線程阻塞和喚醒需要操作系統介入,成本高

      ReentrantLock:更靈活的互斥同步

      public class ReentrantLockExample {
          private final ReentrantLock lock = new ReentrantLock();
          
          public void method() {
              lock.lock();  // 獲取鎖
              try {
                  // 同步代碼
              } finally {
                  lock.unlock();  // 確保鎖被釋放
              }
          }
      }
      

      ReentrantLock相比synchronized的高級特性:

      1. 等待可中斷:避免長期等待
      public boolean tryLockWithTimeout() throws InterruptedException {
          return lock.tryLock(5, TimeUnit.SECONDS);  // 最多等待5秒
      }
      
      1. 公平鎖:按申請順序獲取鎖
      private final ReentrantLock fairLock = new ReentrantLock(true);  // 公平鎖
      
      1. 綁定多個條件
      public class ConditionExample {
          private final ReentrantLock lock = new ReentrantLock();
          private final Condition condition = lock.newCondition();
          
          public void await() throws InterruptedException {
              lock.lock();
              try {
                  condition.await();  // 釋放鎖并等待
              } finally {
                  lock.unlock();
              }
          }
          
          public void signal() {
              lock.lock();
              try {
                  condition.signal();  // 喚醒等待線程
              } finally {
                  lock.unlock();
              }
          }
      }
      

      synchronized vs ReentrantLock

      • 簡單性:synchronized更簡單清晰
      • 性能:JDK6后兩者性能相近
      • 功能:ReentrantLock更靈活
      • 推薦:優先使用synchronized,需要高級功能時使用ReentrantLock

      3.2 非阻塞同步

      非阻塞同步基于沖突檢測的樂觀并發策略,先操作后檢測沖突。

      CAS(Compare-and-Swap)原理

      CAS操作需要三個參數:內存位置V、舊預期值A和新值B。當且僅當V的值等于A時,才用B更新V的值。

      public class CASExample {
          private AtomicInteger atomicValue = new AtomicInteger(0);
          
          public void increment() {
              int oldValue;
              int newValue;
              do {
                  oldValue = atomicValue.get();  // 獲取當前值
                  newValue = oldValue + 1;       // 計算新值
              } while (!atomicValue.compareAndSet(oldValue, newValue));  // CAS操作
          }
      }
      

      Java中的原子類(如AtomicInteger)使用CAS實現無鎖線程安全:

      public class AtomicExample {
          public static AtomicInteger race = new AtomicInteger(0);
          
          public static void increase() {
              race.incrementAndGet();  // 原子自增
          }
          
          public static void main(String[] args) throws InterruptedException {
              Thread[] threads = new Thread[20];
              
              for (int i = 0; i < threads.length; i++) {
                  threads[i] = new Thread(() -> {
                      for (int j = 0; j < 10000; j++) {
                          increase();
                      }
                  });
                  threads[i].start();
              }
              
              for (Thread thread : threads) {
                  thread.join();
              }
              
              System.out.println(race.get());  // 總是輸出200000
          }
      }
      

      ABA問題

      CAS操作存在ABA問題:如果一個值從A變成B,又變回A,CAS操作會誤以為它沒變化。

      解決方案:使用AtomicStampedReference或AtomicMarkableReference維護版本號。

      public class ABAExample {
          public static void main(String[] args) {
              AtomicStampedReference<Integer> atomicRef = 
                  new AtomicStampedReference<>(100, 0);
              
              int stamp = atomicRef.getStamp();
              Integer reference = atomicRef.getReference();
              
              // 更新值并增加版本號
              atomicRef.compareAndSet(reference, 101, stamp, stamp + 1);
          }
      }
      

      3.3 無同步方案

      可重入代碼(純代碼)

      可重入代碼不依賴共享數據,所有狀態都由參數傳入,不會調用非可重入方法。

      // 可重入代碼示例
      public class MathUtils {
          // 純函數:輸出只依賴于輸入,沒有副作用
          public static int add(int a, int b) {
              return a + b;
          }
          
          // 非純函數:依賴外部狀態
          private int base = 0;
          public int addToBase(int value) {
              return base + value;  // 非可重入,依賴共享狀態
          }
      }
      

      線程本地存儲(ThreadLocal)

      ThreadLocal是Java中實現線程本地存儲的核心類,它為每個線程提供獨立的變量副本,避免了多線程環境下的競爭條件。

      ThreadLocal的核心概念

      ThreadLocal允許你將狀態與線程關聯起來,每個線程都有自己獨立初始化的變量副本。這些變量通常用于保持線程的上下文信息,如用戶會話、事務ID等。

      ThreadLocal的基本使用
      public class ThreadLocalExample {
          // 創建ThreadLocal變量,并提供初始值
          private static ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 0);
          private static ThreadLocal<String> threadLocalUser = new ThreadLocal<>();
          
          public static void increment() {
              threadLocalCounter.set(threadLocalCounter.get() + 1);
          }
          
          public static int getCounter() {
              return threadLocalCounter.get();
          }
          
          public static void setUser(String user) {
              threadLocalUser.set(user);
          }
          
          public static String getUser() {
              return threadLocalUser.get();
          }
          
          public static void clear() {
              // 清理ThreadLocal變量,防止內存泄漏
              threadLocalCounter.remove();
              threadLocalUser.remove();
          }
          
          public static void main(String[] args) throws InterruptedException {
              Runnable task = () -> {
                  // 設置線程用戶
                  setUser(Thread.currentThread().getName());
                  
                  // 每個線程獨立計數
                  for (int i = 0; i < 5; i++) {
                      increment();
                  }
                  
                  System.out.println(Thread.currentThread().getName() + 
                      ": Counter=" + getCounter() + 
                      ", User=" + getUser());
                      
                  // 清理ThreadLocal變量
                  clear();
              };
              
              // 創建多個線程
              Thread[] threads = new Thread[3];
              for (int i = 0; i < threads.length; i++) {
                  threads[i] = new Thread(task, "Thread-" + (i + 1));
                  threads[i].start();
              }
              
              // 等待所有線程完成
              for (Thread thread : threads) {
                  thread.join();
              }
          }
      }
      
      ThreadLocal的實現原理

      ThreadLocal的實現依賴于每個Thread對象內部的ThreadLocalMap數據結構。下面是ThreadLocal的核心實現機制:

      // ThreadLocal的核心方法源碼簡析
      public class ThreadLocal<T> {
          // 獲取當前線程的變量值
          public T get() {
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t); // 獲取線程的ThreadLocalMap
              if (map != null) {
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null) {
                      @SuppressWarnings("unchecked")
                      T result = (T)e.value;
                      return result;
                  }
              }
              return setInitialValue(); // 設置初始值
          }
          
          // 設置當前線程的變量值
          public void set(T value) {
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null) {
                  map.set(this, value);
              } else {
                  createMap(t, value); // 創建ThreadLocalMap
              }
          }
          
          // 獲取與線程關聯的ThreadLocalMap
          ThreadLocalMap getMap(Thread t) {
              return t.threadLocals;
          }
          
          // 創建ThreadLocalMap
          void createMap(Thread t, T firstValue) {
              t.threadLocals = new ThreadLocalMap(this, firstValue);
          }
      }
      
      Thread、ThreadLocal與ThreadLocalMap的關系

      ThreadLocal的實現依賴于Thread類中的兩個重要字段:

      public class Thread implements Runnable {
          // 線程本地變量Map
          ThreadLocal.ThreadLocalMap threadLocals = null;
          
          // 繼承自父線程的線程本地變量Map
          ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
          
          // 其他字段和方法...
      }
      

      ThreadLocalMap是ThreadLocal的靜態內部類,它使用弱引用(WeakReference)作為鍵來存儲線程本地變量,這是為了避免內存泄漏。

      graph TB Thread1[Thread 1] --> ThreadLocalMap1[ThreadLocalMap] Thread2[Thread 2] --> ThreadLocalMap2[ThreadLocalMap] ThreadLocalMap1 --> Entry1_1[Entry: key=ThreadLocalA, value=value1] ThreadLocalMap1 --> Entry1_2[Entry: key=ThreadLocalB, value=value2] ThreadLocalMap2 --> Entry2_1[Entry: key=ThreadLocalA, value=value3] ThreadLocalMap2 --> Entry2_2[Entry: key=ThreadLocalB, value=value4] ThreadLocalA[ThreadLocalA] --> Entry1_1 ThreadLocalA --> Entry2_1 ThreadLocalB[ThreadLocalB] --> Entry1_2 ThreadLocalB --> Entry2_2 style Thread1 fill:#e6f3ff style Thread2 fill:#e6f3ff style ThreadLocalMap1 fill:#fff2e6 style ThreadLocalMap2 fill:#fff2e6 style ThreadLocalA fill:#f9e6ff style ThreadLocalB fill:#f9e6ff

      從上圖可以看出:

      • 每個Thread對象都有一個ThreadLocalMap實例
      • ThreadLocalMap中存儲了多個Entry,每個Entry的鍵是ThreadLocal對象,值是線程本地變量
      • 不同的ThreadLocal對象可以在不同的線程中存儲不同的值
      ThreadLocal的內存泄漏問題

      ThreadLocal可能引起內存泄漏,原因在于ThreadLocalMap中的Entry鍵是弱引用(WeakReference),而值是強引用:

      static class Entry extends WeakReference<ThreadLocal<?>> {
          /** The value associated with this ThreadLocal. */
          Object value;
          
          Entry(ThreadLocal<?> k, Object v) {
              super(k);  // 鍵是弱引用
              value = v; // 值是強引用
          }
      }
      

      當ThreadLocal對象沒有外部強引用時,GC會回收鍵(ThreadLocal對象),但值仍然被Entry強引用,導致值無法被回收,造成內存泄漏。

      解決方案

      1. 使用完ThreadLocal后,及時調用remove()方法清理
      2. 將ThreadLocal變量聲明為static final,避免重復創建
      InheritableThreadLocal:可繼承的線程本地變量

      InheritableThreadLocal是ThreadLocal的子類,它允許子線程繼承父線程的線程本地變量:

      public class InheritableThreadLocalExample {
          private static InheritableThreadLocal<String> inheritableThreadLocal = 
              new InheritableThreadLocal<>();
          
          public static void main(String[] args) {
              inheritableThreadLocal.set("Parent Value");
              
              Thread childThread = new Thread(() -> {
                  System.out.println("Child thread value: " + inheritableThreadLocal.get());
                  inheritableThreadLocal.set("Child Value");
                  System.out.println("Child thread value after set: " + inheritableThreadLocal.get());
              });
              
              childThread.start();
              
              try {
                  childThread.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              
              System.out.println("Parent thread value after child modification: " + 
                  inheritableThreadLocal.get());
          }
      }
      
      ThreadLocal的使用場景
      1. 數據庫連接管理:每個線程使用獨立的數據庫連接
      2. 會話管理:在Web應用中存儲用戶會話信息
      3. 全局參數傳遞:避免在方法參數中傳遞上下文信息
      4. 日期格式化:SimpleDateFormat不是線程安全的,可以使用ThreadLocal為每個線程提供獨立的實例
      public class DateFormatterUtils {
          private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = 
              ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
          
          public static String formatDate(Date date) {
              return DATE_FORMATTER.get().format(date);
          }
          
          public static Date parseDate(String dateString) throws ParseException {
              return DATE_FORMATTER.get().parse(dateString);
          }
      }
      

      四、鎖優化技術

      HotSpot虛擬機實現了多種鎖優化技術,提高并發性能。

      4.1 自旋鎖與自適應自旋

      當線程請求鎖時,如果鎖被占用,線程不立即阻塞,而是執行忙循環(自旋)等待鎖釋放。

      // 自旋鎖偽代碼
      public class SpinLock {
          private AtomicReference<Thread> owner = new AtomicReference<>();
          
          public void lock() {
              Thread currentThread = Thread.currentThread();
              // 自旋等待
              while (!owner.compareAndSet(null, currentThread)) {
                  // 空循環,等待鎖釋放
              }
          }
          
          public void unlock() {
              Thread currentThread = Thread.currentThread();
              owner.compareAndSet(currentThread, null);
          }
      }
      

      自適應自旋:根據前一次的自旋時間和鎖擁有者的狀態動態調整自旋時間。

      4.2 鎖消除

      JVM通過逃逸分析檢測不可能存在共享數據競爭的鎖,并消除這些鎖。

      public String concatString(String s1, String s2, String s3) {
          return s1 + s2 + s3;
      }
      

      上述代碼編譯后相當于:

      public String concatString(String s1, String s2, String s3) {
          StringBuffer sb = new StringBuffer();
          sb.append(s1);  // 同步方法
          sb.append(s2);  // 同步方法
          sb.append(s3);  // 同步方法
          return sb.toString();
      }
      

      JVM通過逃逸分析發現sb不會逃逸出方法,自動消除鎖操作。

      4.3 鎖粗化

      將連續的對同一對象加鎖解鎖操作合并為一次范圍更大的加鎖操作。

      // 多次加鎖解鎖
      public void method() {
          synchronized(lock) {
              // 操作1
          }
          // 一些其他代碼...
          synchronized(lock) {
              // 操作2
          }
      }
      
      // 鎖粗化后
      public void method() {
          synchronized(lock) {
              // 操作1
              // 一些其他代碼...
              // 操作2
          }
      }
      

      4.4 輕量級鎖

      輕量級鎖減少傳統重量級鎖使用操作系統互斥量產生的性能消耗。

      輕量級鎖工作流程:

      sequenceDiagram participant T as 線程 participant O as 對象頭 participant S as 線程棧幀 T->>O: 檢查鎖標志位(01) T->>S: 創建Lock Record空間 T->>S: 復制對象頭Mark Word(Displaced Mark Word) T->>O: CAS嘗試將Mark Word指向Lock Record alt CAS成功 O->>O: 將鎖標志位改為00(輕量級鎖) T->>T: 獲取鎖成功 else CAS失敗 alt 檢查是否當前線程已持有鎖 T->>T: 獲取鎖成功(重入) else 其他線程競爭 O->>O: 膨脹為重量級鎖(10) T->>T: 線程阻塞 end end

      4.5 偏向鎖

      偏向鎖消除無競爭情況下的同步原語,偏向于第一個獲取它的線程。

      graph LR A[對象未鎖定<br>標志位01] -->|第一個線程訪問| B[偏向模式<br>標志位01+偏向模式1] B -->|同一線程再次訪問| C[直接訪問<br>不需要同步] B -->|其他線程訪問| D[檢查偏向線程是否活躍] D -->|已不活躍| E[撤銷偏向模式<br>恢復到未鎖定01或輕量級鎖00] D -->|仍然活躍| F[膨脹為輕量級鎖00] E -->|競爭| G[輕量級鎖競爭] G -->|多線程競爭| H[膨脹為重量級鎖10]

      偏向鎖的撤銷:

      1. 當對象計算過哈希碼后,無法進入偏向狀態
      2. 當偏向鎖收到計算一致性哈希碼請求時,撤銷偏向狀態,膨脹為重量級鎖

      五、實踐建議

      1. 優先使用synchronized:在簡單場景下,synchronized更簡潔且性能足夠好
      2. 需要高級功能時使用ReentrantLock:如定時鎖等待、可中斷鎖等待、公平鎖等
      3. 使用讀多寫少的并發容器:如ConcurrentHashMap、CopyOnWriteArrayList等
      4. 使用原子類替代同步:在簡單原子操作場景下,使用AtomicInteger等原子類
      5. 謹慎使用線程本地存儲:避免內存泄漏,及時調用remove()方法清理
      6. 根據場景選擇合適鎖優化:在競爭激烈場景下,考慮禁用偏向鎖(-XX:-UseBiasedLocking)

      六、總結

      線程安全與鎖優化是Java并發編程的核心內容。理解線程安全的不同級別、掌握各種同步機制的原理和適用場景,能夠幫助我們編寫出更高效、更安全的并發程序。

      從基本的互斥同步到非阻塞同步,從鎖消除到偏向鎖,Java虛擬機提供了豐富的線程安全保障和優化手段。作為開發者,我們應該根據具體場景選擇最合適的同步方式,在保證正確性的前提下追求更高的性能。

      ThreadLocal作為實現線程安全的重要工具,通過為每個線程提供獨立的變量副本,避免了共享數據的競爭條件。然而,使用ThreadLocal時需要注意內存泄漏問題,及時清理不再需要的變量。

      記住,并發編程是一門藝術,而了解底層實現原理是掌握這門藝術的基礎。只有深入理解線程安全與鎖優化的機制,才能寫出真正高效、可靠的并發程序。

      posted @ 2025-11-04 08:51  佛祖讓我來巡山  閱讀(175)  評論(0)    收藏  舉報

      佛祖讓我來巡山博客站 - 創建于 2018-08-15

      開發工程師個人站,內容主要是網站開發方面的技術文章,大部分來自學習或工作,部分來源于網絡,希望對大家有所幫助。

      Bootstrap中文網

      主站蜘蛛池模板: 2020国产欧洲精品网站| 欧美高清freexxxx性| yyyy在线在片| 午夜福利国产精品小视频| 久久精品一区二区东京热| 黄色免费在线网址| 99久久国产宗和精品1上映| 四虎成人精品在永久在线| 午夜成人性爽爽免费视频| 丰满无码人妻热妇无码区| 无遮高潮国产免费观看| 欧美日本国产va高清cabal| 日韩乱码视频一区二区三区| аⅴ天堂中文在线网| 中文字幕久久波多野结衣av| 延津县| 国99久9在线 | 免费| 精品久久人人妻人人做精品| 亚洲免费成人av一区| 无码av天天av天天爽| 国产亚洲精久久久久久无码77777| 国产成人亚洲日韩欧美| 免费观看欧美猛交视频黑人| 国产精品高清中文字幕| 日韩人妻少妇一区二区三区 | 亚洲嫩模喷白浆在线观看| 亚洲中文字幕无码专区| 国产精品无卡毛片视频| 国产免费福利网站| 亚洲综合精品一区二区三区| 国精无码欧精品亚洲一区| 国产精品国产高清国产一区| 福利一区二区在线观看| 日本一区二区在线高清观看| 欧美人与动欧交视频| 国色天香中文字幕在线视频| 国产精品国产亚洲区久久| av在线播放国产一区| 男女动态无遮挡动态图| 久在线精品视频线观看| 亚洲少妇人妻无码视频|