Java多線程下解決資源競爭的7種方法詳解
前言
一般情況下,只要涉及到多線程編程,程序的復(fù)雜性就會(huì)顯著上升,性能顯著下降,BUG出現(xiàn)的概率大大提升。
多線程編程本意是將一段程序并行運(yùn)行,提升數(shù)據(jù)處理能力,但是由于大部分情況下都涉及到共有資源的競爭,所以修改資源
對(duì)象時(shí)必須加鎖處理。但是鎖的實(shí)現(xiàn)有很多種方法,下面就來一起了解一下在C#語言中幾種鎖的實(shí)現(xiàn)與其性能表現(xiàn)。
一、c#下的幾種鎖的運(yùn)用方式
1、臨界區(qū),通過對(duì)多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數(shù)據(jù)訪問。
private static object obj = new object();
private static int lockInt;
private static void LockIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
lock (obj)
{
lockInt++;
}
}
}
你沒看錯(cuò),c#中的lock語法就是臨界區(qū)(Monitor)的一個(gè)語法糖,這大概是90%以上的.net程序員首先想到的鎖,不過大部分人都只是知道
有這么個(gè)語法,不知道其實(shí)是以臨界區(qū)的方式處理資源競爭。
2、互斥量,為協(xié)調(diào)共同對(duì)一個(gè)共享資源的單獨(dú)訪問而設(shè)計(jì)的。
c#中有一個(gè)Mutex類,就在System.Threading命名空間下,Mutex其實(shí)就是互斥量,互斥量不單單能處理多線程之間的資源競爭,還能處理
進(jìn)程之間的資源競爭,功能是比較強(qiáng)大的,但是開銷也很大,性能比較低。
private static Mutex mutex = new Mutex();
private static int mutexInt;
private static void MutexIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
mutex.WaitOne();
mutexInt++;
mutex.ReleaseMutex();
}
}
3、信號(hào)量,為控制一個(gè)具有有限數(shù)量用戶資源而設(shè)計(jì)。
private static Semaphore sema = new Semaphore(1, 1);
private static int semaphoreInt;
private static void SemaphoreIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
sema.WaitOne();
semaphoreInt++;
sema.Release();
}
}
4、事 件:用來通知線程有一些事件已發(fā)生,從而啟動(dòng)后繼任務(wù)的開始。
public static AutoResetEvent autoResetEvent = new AutoResetEvent(true);
private static int autoResetEventInt;
private static void AutoResetEventIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
if (autoResetEvent.WaitOne())
{
autoResetEventInt++;
autoResetEvent.Set();
}
}
}
5、讀寫鎖,這種鎖允許在有其他程序正在寫的情況下讀取資源,所以如果資源允許臟讀,用這個(gè)比較合適
private static ReaderWriterLockSlim LockSlim = new ReaderWriterLockSlim();
private static int lockSlimInt;
private static void LockSlimIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
LockSlim.EnterWriteLock();
lockSlimInt++;
LockSlim.ExitWriteLock();
}
}
6、原子鎖,通過原子操作Interlocked.CompareExchange實(shí)現(xiàn)“無鎖”競爭
private static int isLock;
private static int ceInt;
private static void CEIntAdd()
{
//long tmp = 0;
for (var i = 0; i < runTimes; i++)
{
while (Interlocked.CompareExchange(ref isLock, 1, 0) == 1) { Thread.Sleep(1); }
ceInt++;
Interlocked.Exchange(ref isLock, 0);
}
}
7、原子性操作,這是一種特例,野外原子性操作本身天生線程安全,所以無需加鎖
private static int atomicInt;
private static void AtomicIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
Interlocked.Increment(ref atomicInt);
}
}
8、不加鎖,如果不加鎖,那多線程下運(yùn)行結(jié)果肯定是錯(cuò)的,這里貼上來比較一下性能
private static int noLockInt;
private static void NoLockIntAdd()
{
for (var i = 0; i < runTimes; i++)
{
noLockInt++;
}
}
二、性能測試
1、測試代碼,執(zhí)行1000,10000,100000,1000000次
private static void Run()
{
var stopwatch = new Stopwatch();
var taskList = new Task[loopTimes];
// 多線程
Console.WriteLine();
Console.WriteLine($" 線程數(shù):{loopTimes}");
Console.WriteLine($" 執(zhí)行次數(shù):{runTimes}");
Console.WriteLine($" 校驗(yàn)值應(yīng)等于:{runTimes * loopTimes}");
// AtomicIntAdd
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { AtomicIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("AtomicIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{atomicInt}");
// CEIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { CEIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("CEIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{ceInt}");
// LockIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { LockIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("LockIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{lockInt}");
// MutexIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { MutexIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("MutexIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{mutexInt}");
// LockSlimIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { LockSlimIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("LockSlimIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{lockSlimInt}");
// SemaphoreIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { SemaphoreIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("SemaphoreIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{semaphoreInt}");
// AutoResetEventIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { AutoResetEventIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("AutoResetEventIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{autoResetEventInt}");
// NoLockIntAdd
taskList = new Task[loopTimes];
stopwatch.Restart();
for (var i = 0; i < loopTimes; i++)
{
taskList[i] = Task.Factory.StartNew(() => { NoLockIntAdd(); });
}
Task.WaitAll(taskList);
Console.WriteLine($"{GetFormat("NoLockIntAdd")}, 總耗時(shí):{stopwatch.ElapsedMilliseconds}毫秒, 校驗(yàn)值:{noLockInt}");
Console.WriteLine();
}
2、線程:10




3、線程:50


三、總結(jié)


1)在各種測試中,不加鎖肯定是最快的,所以盡量避免資源競爭導(dǎo)致加鎖運(yùn)行
2)在多線程中Interlocked.CompareExchange始終表現(xiàn)出優(yōu)越的性能,排在第二位
3)第三位lock,臨界區(qū)也表現(xiàn)出很好的性能,所以在別人說lock性能低的時(shí)候請(qǐng)反駁他
4)第四位是原子性變量(Atomic)操作,不過目前只支持變量的自增自減,適用性不強(qiáng)
5)第五位讀寫鎖(ReaderWriterLockSlim)表現(xiàn)也還可以,并且支持無所讀,實(shí)用性還是比較好的
6)剩下的信號(hào)量、事件、互斥量,這三種性能最差,當(dāng)然他們有各自的適用范圍,只是在處理資源競爭這方面表現(xiàn)不好
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
IntelliJ IDEA 的 Spring 項(xiàng)目如何查看 @Value 的配置和值(方法詳解)
這篇文章主要介紹了IntelliJ IDEA 的 Spring 項(xiàng)目如何查看 @Value 的配置和值,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
java POI解析Excel 之?dāng)?shù)據(jù)轉(zhuǎn)換公用方法(推薦)
下面小編就為大家?guī)硪黄猨ava POI解析Excel 之?dāng)?shù)據(jù)轉(zhuǎn)換公用方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08
Javaweb EL自定義函數(shù)開發(fā)及代碼實(shí)例
這篇文章主要介紹了Javaweb EL自定義函數(shù)開發(fā)及代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06

