高併發Java(5):JDK併發包1

原文地址:http://www.importnew.com/21288.html

高併發Java(2):多線程基礎中,我們已經初步提到了基本的線程同步操作。這次要提到的是在併發包中的同步控制工具。

1. 各種同步控制工具的使用

1.1 ReentrantLock

ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的性能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。

相比於synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。

首先我們通過一個例子來說明ReentrantLock最初步的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package test;
 
import java.util.concurrent.locks.ReentrantLock;
 
public class Test implements Runnable
{
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
 
    @Override
    public void run()
    {
        for (int j = 0; j < 10000000; j++)
        {
            lock.lock();
            try
            {
                i++;
            }
            finally
            {
                lock.unlock();
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
 
}

有兩個線程都對i進行++操作,爲了保證線程安全,使用了 ReentrantLock,從用法上可以看出,與 synchronized相比,ReentrantLock就稍微複雜一點。因爲必須在finally中進行解鎖操作,如果不在 finally解鎖,有可能代碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。

那麼ReentrantLock到底有哪些優秀的特點呢?

1.1.1 可重入

單線程可以重複進入,但要重複退出

1
2
3
4
5
6
7
8
9
10
11
12
lock.lock();
lock.lock();
try
{
    i++;
 
}          
finally
{
    lock.unlock();
    lock.unlock();
}

由於ReentrantLock是重入鎖,所以可以反覆得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized 塊時,才釋放鎖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Child extends Father implements Runnable{
    final static Child child = new Child();//爲了保證鎖唯一
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(child).start();
        }
    }
 
    public synchronized void doSomething() {
        System.out.println("1child.doSomething()");
        doAnotherThing(); // 調用自己類中其他的synchronized方法
    }
 
    private synchronized void doAnotherThing() {
        super.doSomething(); // 調用父類的synchronized方法
        System.out.println("3child.doAnotherThing()");
    }
 
    @Override
    public void run() {
        child.doSomething();
    }
}
class Father {
    public synchronized void doSomething() {
        System.out.println("2father.doSomething()");
    }
}

我們可以看到一個線程進入不同的 synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖

輸出:

1
2
3
4
5
6
7
8
9
10
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...

1.1.2.可中斷

與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識查看高併發Java(2):多線程基礎

普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。

我們模擬出一個死鎖現場,然後用中斷來處理死鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package test;
 
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.ReentrantLock;
 
public class Test implements Runnable
{
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
 
    int lock;
 
    public Test(int lock)
    {
        this.lock = lock;
    }
 
    @Override
    public void run()
    {
        try
        {
            if (lock == 1)
            {
                lock1.lockInterruptibly();
                try
                {
                    Thread.sleep(500);
                }
                catch (Exception e)
                {
                    // TODO: handle exception
                }
                lock2.lockInterruptibly();
            }
            else
            {
                lock2.lockInterruptibly();
                try
                {
                    Thread.sleep(500);
                }
                catch (Exception e)
                {
                    // TODO: handle exception
                }
                lock1.lockInterruptibly();
            }
        }
        catch (Exception e)
        {
            // TODO: handle exception
        }
        finally
        {
            if (lock1.isHeldByCurrentThread())
            {
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread())
            {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":線程退出");
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        Test t1 = new Test(1);
        Test t2 = new Test(2);
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        //DeadlockChecker.check();
    }
 
    static class DeadlockChecker
    {
        private final static ThreadMXBean mbean = ManagementFactory
                .getThreadMXBean();
        final static Runnable deadlockChecker = new Runnable()
        {
            @Override
            public void run()
            {
                // TODO Auto-generated method stub
                while (true)
                {
                    long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
                    if (deadlockedThreadIds != null)
                    {
                        ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
                        for (Thread t : Thread.getAllStackTraces().keySet())
                        {
                            for (int i = 0; i < threadInfos.length; i++)
                            {
                                if(t.getId() == threadInfos[i].getThreadId())
                                {
                                    t.interrupt();
                                }
                            }
                        }
                    }
                    try
                    {
                        Thread.sleep(5000);
                    }
                    catch (Exception e)
                    {
                        // TODO: handle exception
                    }
                }
 
            }
        };
 
        public static void check()
        {
            Thread t = new Thread(deadlockChecker);
            t.setDaemon(true);
            t.start();
        }
    }
 
}

上述代碼有可能會發生死鎖,線程1得到lock1,線程2得到lock2,然後彼此又想獲得對方的鎖。

我們用jstack查看運行上述代碼後的情況

的確發現了一個死鎖。

DeadlockChecker.check();方法用來檢測死鎖,然後把死鎖的線程中斷。中斷後,線程正常退出。

1.1.3.可限時

超時不能獲得鎖,就返回false,不會永久等待構成死鎖

使用lock.tryLock(long timeout, TimeUnit unit)來實現可限時鎖,參數爲時間和單位。

舉個例子來說明下可限時:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package test;
 
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
 
public class Test implements Runnable
{
    public static ReentrantLock lock = new ReentrantLock();
 
    @Override
    public void run()
    {
        try
        {
            if (lock.tryLock(5, TimeUnit.SECONDS))
            {
                Thread.sleep(6000);
            }
            else
            {
                System.out.println("get lock failed");
            }
        }
        catch (Exception e)
        {
        }
        finally
        {
            if (lock.isHeldByCurrentThread())
            {
                lock.unlock();
            }
        }
    }
 
    public static void main(String[] args)
    {
        Test t = new Test();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t1.start();
        t2.start();
    }
 
}

使用兩個線程來爭奪一把鎖,當某個線程獲得鎖後,sleep6秒,每個線程都只嘗試5秒去獲得鎖。

所以必定有一個線程無法獲得鎖。無法獲得後就直接退出了。

輸出:

1
get lock failed

1.1.4.公平鎖

使用方式:

1
2
3
public ReentrantLock(boolean fair)
 
public static ReentrantLock fairLock = new ReentrantLock(true);

一般意義上的鎖是不公平的,不一定先來的線程能先得到鎖,後來的線程就後得到鎖。不公平的鎖可能會產生飢餓現象。

公平鎖的意思就是,這個鎖能保證線程是先來的先得到鎖。雖然公平鎖不會產生飢餓現象,但是公平鎖的性能會比非公平鎖差很多。

1.2 Condition

Condition與ReentrantLock的關係就類似於synchronized與Object.wait()/signal()

await()方法會使當前線程等待,同時釋放當前鎖,當其他線程中使用signal()時或者signalAll()方法時,線 程會重新獲得鎖並繼續執行。或者當線程被中斷時,也能跳出等待。這和Object.wait()方法很相似。

awaitUninterruptibly()方法與await()方法基本相同,但是它並不會再等待過程中響應中斷。 singal()方法用於喚醒一個在等待中的線程。相對的singalAll()方法會喚醒所有在等待中的線程。這和Obejct.notify()方法很類似。

這裏就不再詳細介紹了。舉個例子來說明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package test;
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
 
public class Test implements Runnable
{
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
 
    @Override
    public void run()
    {
        try
        {
            lock.lock();
            condition.await();
            System.out.println("Thread is going on");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        Test t = new Test();
        Thread thread = new Thread(t);
        thread.start();
        Thread.sleep(2000);
 
        lock.lock();
        condition.signal();
        lock.unlock();
    }
 
}

上述例子很簡單,讓一個線程await住,讓主線程去喚醒它。condition.await()/signal只能在得到鎖以後使用。

1.3.Semaphore

對於鎖來說,它是互斥的排他的。意思就是,只要我獲得了鎖,沒人能再獲得了。

而對於Semaphore來說,它允許多個線程同時進入臨界區。可以認爲它是一個共享鎖,但是共享的額度是有限制的,額度用完了,其他沒有拿到額度的線程還是要阻塞在臨界區外。當額度爲1時,就相等於lock

下面舉個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package test;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
 
public class Test implements Runnable
{
    final Semaphore semaphore = new Semaphore(5);
    @Override
    public void run()
    {
        try
        {
            semaphore.acquire();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId() + " done");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        final Test t = new Test();
        for (int i = 0; i < 20; i++)
        {
            executorService.submit(t);
        }
    }
 
}

有一個20個線程的線程池,每個線程都去 Semaphore的許可,Semaphore的許可只有5個,運行後可以看到,5個一批,一批一批地輸出。

當然一個線程也可以一次申請多個許可

1
public void acquire(int permits) throws InterruptedException

1.4 ReadWriteLock

ReadWriteLock是區分功能的鎖。讀和寫是兩種不同的功能,讀-讀不互斥,讀-寫互斥,寫-寫互斥。

這樣的設計是併發量提高了,又保證了數據安全。

使用方式:

1
2
3
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();

詳細例子可以查看 Java實現生產者消費者問題與讀者寫者問題,這裏就不展開了。

1.5 CountDownLatch

倒數計時器
一種典型的場景就是火箭發射。在火箭發射前,爲了保證萬無一失,往往還要進行各項設備、儀器的檢查。 只有等所有檢查完畢後,引擎才能點火。這種場景就非常適合使用CountDownLatch。它可以使得點火線程
,等待所有檢查線程全部完工後,再執行

使用方式:

1
2
3
static final CountDownLatch end = new CountDownLatch(10);
end.countDown();
end.await();

示意圖:

一個簡單的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package test;
 
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Test implements Runnable
{
    static final CountDownLatch countDownLatch = new CountDownLatch(10);
    static final Test t = new Test();
    @Override
    public void run()
    {
        try
        {
            Thread.sleep(2000);
            System.out.println("complete");
            countDownLatch.countDown();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++)
        {
            executorService.execute(t);
        }
        countDownLatch.await();
        System.out.println("end");
        executorService.shutdown();
    }
 
}

主線程必須等待10個線程全部執行完纔會輸出”end”。

1.6 CyclicBarrier

和CountDownLatch相似,也是等待某些線程都做完以後再執行。與CountDownLatch區別在於這個計數器可以反覆使用。比如,假設我們將計數器設置爲10。那麼湊齊第一批1 0個線程後,計數器就會歸零,然後接着湊齊下一批10個線程

使用方式:

1
2
3
4
5
public CyclicBarrier(int parties, Runnable barrierAction)
 
barrierAction就是當計數器一次計數完成後,系統會執行的動作
 
await()

示意圖:

下面舉個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package test;
 
import java.util.concurrent.CyclicBarrier;
 
public class Test implements Runnable
{
    private String soldier;
    private final CyclicBarrier cyclic;
 
    public Test(String soldier, CyclicBarrier cyclic)
    {
        this.soldier = soldier;
        this.cyclic = cyclic;
    }
 
    @Override
    public void run()
    {
        try
        {
            //等待所有士兵到齊
            cyclic.await();
            dowork();
            //等待所有士兵完成工作
            cyclic.await();
        }
        catch (Exception e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
 
    private void dowork()
    {
        // TODO Auto-generated method stub
        try
        {
            Thread.sleep(3000);
        }
        catch (Exception e)
        {
            // TODO: handle exception
        }
        System.out.println(soldier + ": done");
    }
 
    public static class BarrierRun implements Runnable
    {
 
        boolean flag;
        int n;
 
        public BarrierRun(boolean flag, int n)
        {
            super();
            this.flag = flag;
            this.n = n;
        }
 
        @Override
        public void run()
        {
            if (flag)
            {
                System.out.println(n + "個任務完成");
            }
            else
            {
                System.out.println(n + "個集合完成");
                flag = true;
            }
 
        }
 
    }
 
    public static void main(String[] args)
    {
        final int n = 10;
        Thread[] threads = new Thread[n];
        boolean flag = false;
        CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
        System.out.println("集合");
        for (int i = 0; i < n; i++)
        {
            System.out.println(i + "報道");
            threads[i] = new Thread(new Test("士兵" + i, barrier));
            threads[i].start();
        }
    }
 
}

打印結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
集合
0報道
1報道
2報道
3報道
4報道
5報道
6報道
7報道
8報道
9報道
10個集合完成
士兵5: done
士兵7: done
士兵8: done
士兵3: done
士兵4: done
士兵1: done
士兵6: done
士兵2: done
士兵0: done
士兵9: done
10個任務完成

1.7 LockSupport

提供線程阻塞原語

和suspend類似

1
2
LockSupport.park();
LockSupport.unpark(t1);

與suspend相比 不容易引起線程凍結

LockSupport的思想呢,和 Semaphore有點相似,內部有一個許可,park的時候拿掉這個許可,unpark的時候申請這個許可。所以如果unpark在park之前,是不會發生線程凍結的。

下面的代碼是高併發Java(2):多線程基礎中suspend示例代碼,在使用suspend時會發生死鎖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package test;
 
import java.util.concurrent.locks.LockSupport;
 
public class Test
{
    static Object u = new Object();
    static TestSuspendThread t1 = new TestSuspendThread("t1");
    static TestSuspendThread t2 = new TestSuspendThread("t2");
 
    public static class TestSuspendThread extends Thread
    {
        public TestSuspendThread(String name)
        {
            setName(name);
        }
 
        @Override
        public void run()
        {
            synchronized (u)
            {
                System.out.println("in " + getName());
                //Thread.currentThread().suspend();
                LockSupport.park();
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        t1.start();
        Thread.sleep(100);
        t2.start();
//        t1.resume();
//        t2.resume();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

而使用 LockSupport則不會發生死鎖。

另外

park()能夠響應中斷,但不拋出異常。中斷響應的結果是,park()函數的返回,可以從Thread.interrupted()得到中斷標誌。

在JDK當中有大量地方使用到了park,當然LockSupport的實現也是使用unsafe.park()來實現的。

1
2
3
public static void park() {
        unsafe.park(false, 0L);
    }

1.8 ReentrantLock 的實現

下面來介紹下ReentrantLock的實現,ReentrantLock的實現主要由3部分組成:

  • CAS狀態
  • 等待隊列
  • park()

ReentrantLock的父類中會有一個state變量來表示同步的狀態

1
2
3
4
/**
     * The synchronization state.
     */
    private volatile int state;

通過CAS操作來設置state來獲取鎖,如果設置成了1,則將鎖的持有者給當前線程

1
2
3
4
5
6
final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

如果拿鎖不成功,則會做一個申請

1
2
3
4
5
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

首先,再去申請下試試看tryAcquire,因爲此時可能另一個線程已經釋放了鎖。

如果還是沒有申請到鎖,就addWaiter,意思是把自己加到等待隊列中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

其間還會有多次嘗試去申請鎖,如果還是申請不到,就會被掛起

1
2
3
4
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

同理,如果在unlock操作中,就是釋放了鎖,然後unpark,這裏就不具體講了。

2. 併發容器及典型源碼分析

2.1 ConcurrentHashMap

我們知道HashMap不是一個線程安全的容器,最簡單的方式使HashMap變成線程安全就是使用Collections.synchronizedMap,它是對HashMap的一個包裝

1
public static Map m=Collections.synchronizedMap(new HashMap());

同理對於List,Set也提供了相似方法。

但是這種方式只適合於併發量比較小的情況。

我們來看下synchronizedMap的實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize
 
        SynchronizedMap(Map<K,V> m) {
            if (m==null)
                throw new NullPointerException();
            this.m = m;
            mutex = this;
        }
 
        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }
 
        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }
 
        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

它會將HashMap包裝在裏面,然後將HashMap的每個操作都加上synchronized。

由於每個方法都是獲取同一把鎖(mutex),這就意味着,put和remove等操作是互斥的,大大減少了併發量。

下面來看下ConcurrentHashMap是如何實現的

1
2
3
4
5
6
7
8
9
10
11
public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }

在 ConcurrentHashMap內部有一個Segment段,它將大的HashMap切分成若干個段(小的HashMap),然後讓數據在每一段上Hash,這樣多個線程在不同段上的Hash操作一定是線程安全的,所以只需要同步同一個段上的線程就可以了,這樣實現了鎖的分離,大大增加了併發量。

在使用ConcurrentHashMap.size時會比較麻煩,因爲它要統計每個段的數據和,在這個時候,要把每一個段都加上鎖,然後再做數據統計。這個就是把鎖分離後的小小弊端,但是size方法應該是不會被高頻率調用的方法。

在實現上,不使用synchronized和lock.lock而是儘量使用trylock,同時在HashMap的實現上,也做了一點優化。這裏就不提了。

2.2 BlockingQueue

BlockingQueue不是一個高性能的容器。但是它是一個非常好的共享數據的容器。是典型的生產者和消費者的實現。

示意圖:

具體可以查看Java實現生產者消費者問題與讀者寫者問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章