JAVA多線程內(nèi)容比較多抓韩,今天寫完了第六篇,后邊還有七(肯定最后一篇了)连茧。
- 如何保證多線程讀寫文件的安全核蘸?
(1)讀寫互斥,寫讀互斥梅屉,寫寫互斥值纱,只有讀讀相容(可以異步)
(2)FileInputStream、FileOutputStream坯汤、RandomAccessFile均可得到FileChannel對(duì)象
(3)FileChannel通過(guò)獨(dú)占鎖tryLock()鎖定文件虐唠,用于寫文件。
(4)FileChannel的tryLock(0, Long.MAX_VALUE, true)是非阻塞的惰聂,是共享鎖疆偿,能被多個(gè)線程同時(shí)持有,它能禁止其他線程獲取獨(dú)占鎖(防止寫進(jìn)程進(jìn)來(lái)寫文件)搓幌,可用于讀文件杆故。
(5)FileChannel的lock()是阻塞的,在文件被鎖定的情況下溉愁,會(huì)保持阻塞处铛,直到獲得該鎖為止,實(shí)際上這個(gè)可以用作喝tryLock一樣的情況,用于寫文件撤蟆。
//一般用這種方式奕塑,來(lái)循環(huán)獲取鎖,獲取到鎖之后開(kāi)始寫操作家肯。
File file;
try {
file = FileUtils.createFile(pathFile);
} catch (IOException e) {
e.printStackTrace();
ioListener.onFail("文件創(chuàng)建失敗龄砰,請(qǐng)檢查路徑是否合法以及讀寫權(quán)限");
return;
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel fileChannel = fileOutputStream.getChannel();
//文件鎖
FileLock fileLock = null;
while (true) {
try {
fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, true);//共享鎖
break;
} catch (Exception e) {
System.out.println("有其他線程正在操作該文件,當(dāng)前線程" + Thread.currentThread().getName());
}
}
- Java中的ReadWriteLock是什么讨衣?
(1)ReetrantReadWriteLock管理一組鎖换棚,一個(gè)是只讀的鎖,一個(gè)是寫鎖反镇。
(2)讀鎖使用共享模式固蚤;寫鎖使用獨(dú)占模式;讀鎖可以在沒(méi)有寫鎖的時(shí)候被多個(gè)線程同時(shí)持有愿险,寫鎖是獨(dú)占的
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockTest {
//默認(rèn)是非公平鎖
// static ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
//手工設(shè)置為公平鎖
static ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock(true);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
// new Thread(new RunnableDowngradeLock()).start();
new Thread(new RunnableUpgradeLock()).start();
}
}
//ReentrantReadWriteLock支持鎖降級(jí)颇蜡,但是一定記得顯示的把兩個(gè)鎖都釋放
static class RunnableUpgradeLock implements Runnable{
@Override
public void run() {
try {
//先獲取寫鎖
rtLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get writeLock");
//再獲取讀鎖,這種明顯是降級(jí)辆亏,鎖降級(jí)
rtLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get read lock");
System.out.println(Thread.currentThread().getName() + " isWriteLocked "+ rtLock.isWriteLocked());
System.out.println(Thread.currentThread().getName() + " isFair "+ rtLock.isFair());
}finally {
rtLock.writeLock().unlock();
rtLock.readLock().unlock();
}
}
}
//ReentrantReadWriteLock不支持鎖升級(jí)风秤,下面這個(gè)執(zhí)行會(huì)死鎖
static class RunnableDowngradeLock implements Runnable{
@Override
public void run() {
try {
//先獲取讀鎖
rtLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get read lock");
//再獲取寫鎖,這種明顯是升級(jí)了扮叨,鎖升級(jí)
rtLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get writeLock");
}finally {
rtLock.writeLock().unlock();
rtLock.readLock().unlock();
}
}
}
}
- lock原理
(1)Lock接口就這么幾個(gè)方法
//嘗試獲取鎖缤弦,獲取成功則返回,否則阻塞當(dāng)前線程
void lock()
//嘗試獲取鎖彻磁,線程在成功獲取鎖之前被中斷碍沐,則放棄獲取鎖,拋出異常
void lockInterruptibly()
//嘗試獲取鎖衷蜓,獲取鎖成功則返回true累提,否則返回false
boolean tryLock();
//嘗試獲取鎖,若在規(guī)定時(shí)間內(nèi)獲取到鎖磁浇,則返回true斋陪,否則返回false,未獲取鎖之前被中斷置吓,則拋出異常
boolean tryLock(long time, TimeUnit unit)
//釋放鎖
void unlock()
//返回當(dāng)前鎖的條件變量无虚,通過(guò)條件變量可以實(shí)現(xiàn)類似notify和wait的功能,一個(gè)鎖可以有多個(gè)條件變量
Condition newCondition()
(2)一定有一個(gè)變量是表示(鎖)狀態(tài)的變量(假設(shè)0表示沒(méi)有線程獲取鎖衍锚,1表示已有線程占有鎖),該變量必須聲明為voaltile類型(任何線程過(guò)來(lái)友题,都要直接從內(nèi)存讀取鎖鎖狀態(tài),不可能從緩存却髦省);
(3)鎖操作步驟:1>先獲取鎖狀態(tài)度宦,如果狀態(tài)時(shí)0未鎖定時(shí)候踢匣,修改鎖狀態(tài)為1,返回鎖定成功斗埂;2>如果時(shí)1已鎖定符糊,進(jìn)入等待隊(duì)列,將自身阻塞呛凶,等待鎖變?yōu)?未鎖定的喚醒該線程,然后執(zhí)行第一步行贪;3>修改鎖狀態(tài)時(shí)漾稀,有可能失敗,搶占式的建瘫,只有第一個(gè)修改的能成功崭捍,后邊的線程修改都失敗,同樣進(jìn)入等待隊(duì)列啰脚,將自身阻塞殷蛇,等待鎖變?yōu)?時(shí)喚醒該線程,然后執(zhí)行第一步
(4)解除鎖操作步驟:1>修改鎖狀態(tài)橄浓,將1變?yōu)?粒梦,然后通知等待隊(duì)列的第一個(gè)線程(如果是公平鎖的話,第一個(gè)等待的)荸实,釋放完成匀们;2>被喚醒的鎖,執(zhí)行上面的所操作步驟准给。3>如果鎖是已經(jīng)被釋放了泄朴,還來(lái)解除鎖操作,這個(gè)時(shí)候直接拋異常失敗露氮。
(5)公平鎖實(shí)現(xiàn):鎖操作時(shí)祖灰,判斷隊(duì)列是否存在等待的線程,1>如果有畔规,就把自己加入隊(duì)列局扶,然后將自身阻塞;2>如果沒(méi)有等待的線程,執(zhí)行所操作步驟第一步油讯。3>隊(duì)列按照先入先出操作详民,所以喚醒的一定是先進(jìn)入隊(duì)列的線程
(6)非公平鎖實(shí)現(xiàn):實(shí)際上就是是否可以插入到隊(duì)列前面,第一個(gè)被喚醒一定是隊(duì)列頭的等待線程陌兑,哪個(gè)線程搶占了隊(duì)列頭沈跨,誰(shuí)就是第一個(gè)。
(7)Condition接口兔综,實(shí)際上就是維護(hù)的等待隊(duì)列饿凛,先進(jìn)先出隊(duì)列
ReentrantLock的內(nèi)部實(shí)現(xiàn)
(1)其實(shí)上面Lock已經(jīng)說(shuō)的差不多了
(2)內(nèi)部有抽象類Sync繼承了AbstractQueuedSynchronizer狞玛。Sync有兩個(gè)子類,一個(gè)FairSync另一個(gè)NonfairSync(ReentrantLock默認(rèn)就是非公平鎖)涧窒。
(3)這個(gè)比Lock多了acquire和tryAcquire的方法心肪,還有tryRelease/getOwner/getHoldCount/isLocked等方法。
(4)tryAcquire方法分為公平鎖/非公平鎖的纠吴,公平鎖:如果鎖狀態(tài)為0且等待隊(duì)列沒(méi)有等待的線程硬鞍,則通過(guò)cas方式設(shè)置鎖狀態(tài)為1,設(shè)置擁有鎖的線程為自己戴已;如果鎖狀態(tài)大于1固该,判斷擁有鎖的是不是自己,是自己則將狀態(tài)改為加1糖儡,返回成功伐坏,如果擁有鎖的不是自己,返回失敗握联。非公平鎖:如果鎖狀態(tài)為0桦沉,就通過(guò)cas方式搶占鎖,搶占成功設(shè)置狀態(tài)金闽,設(shè)置擁有鎖的線程為自己纯露,如果鎖狀態(tài)不為0,在判斷擁有鎖的是不是自己呐矾,是則+1苔埋,返回成功,不是則直接返回?fù)屨际 ?br> (5)acquire方法實(shí)際上在AbstractQueuedSynchronizer(AQS)里蜒犯,這個(gè)會(huì)調(diào)用(4)的tryAcquire方法组橄,如果失敗把自己加入到等待隊(duì)列中,成功則直接返回成功罚随。
(6)tryRelease就是tryAcquire的反向玉工,為鎖狀態(tài)減去準(zhǔn)備release的個(gè)數(shù),也就是釋放鎖的個(gè)數(shù)淘菩。但是這個(gè)要判斷解鎖的是否為當(dāng)前擁有鎖的線程遵班,否則直接失敗。多線程有什么要注意的問(wèn)題潮改?
(1)需要?jiǎng)?chuàng)建大量多線程的程序狭郑,建議使用線程池,避免占用大量資源
(2)控制并發(fā)數(shù)量汇在,過(guò)大可能導(dǎo)致服務(wù)器無(wú)法支撐翰萨,cpu就那么多,大量并發(fā)執(zhí)行反而卡死了
(3)通過(guò)一些鎖來(lái)控制并發(fā)導(dǎo)致的共享問(wèn)題
(4)使用鎖的過(guò)程中注意開(kāi)發(fā)時(shí)候盡量別設(shè)計(jì)成循環(huán)等待的糕殉,容易死鎖
(5)另外ReentrantLock一類的鎖亩鬼,lock后殖告,一定要記得unlock,否則就死鎖了
(6)有些線程任務(wù)執(zhí)行完成一部分后雳锋,需要等待其他線程執(zhí)行完成后再執(zhí)行黄绩,這個(gè)時(shí)候可以通過(guò)sleep或者其他方式使其進(jìn)入阻塞狀態(tài)。
(7)有沒(méi)有優(yōu)先級(jí)玷过?