JavaRush /Blog Jawa /Random-JV /Deadlock ing Jawa lan cara kanggo nglawan
articles
tingkat

Deadlock ing Jawa lan cara kanggo nglawan

Diterbitake ing grup
Nalika ngembangake aplikasi multi-threaded, dilema asring muncul: sing luwih penting yaiku keandalan utawa kinerja aplikasi kasebut. Contone, kita nggunakake sinkronisasi kanggo safety thread, lan ing kasus ngendi urutan sinkronisasi ora bener, kita bisa nimbulaké deadlocks. Kita uga nggunakake pools thread lan semaphore kanggo matesi konsumsi sumber, lan kesalahan ing desain iki bisa mimpin kanggo deadlock amarga lack saka sumber daya. Ing artikel iki kita bakal pirembagan bab carane supaya deadlock, uga masalah liyane ing kinerja aplikasi. Kita uga bakal ndeleng carane aplikasi bisa ditulis kanthi cara supaya bisa pulih ing kasus deadlock. Deadlock ing Jawa lan cara kanggo nglawan - 1Deadlock minangka kahanan ing ngendi loro utawa luwih proses sing manggoni sawetara sumber daya nyoba ndarbeni sawetara sumber daya liyane sing dikuwasani dening proses liyane lan ora ana proses sing bisa ngisi sumber daya sing dibutuhake lan, mula, ngeculake sing dikuwasani. Dhéfinisi iki umum banget lan mula angel dimangerteni; kanggo pangerten sing luwih apik, kita bakal ndeleng jinis deadlock nggunakake conto.

Sinkronisasi Order Mutual Locking

Coba tugas ing ngisor iki: sampeyan kudu nulis cara sing nindakake transaksi kanggo nransfer dhuwit tartamtu saka siji akun menyang akun liyane. Solusi kasebut bisa uga katon kaya iki:
public void transferMoney(Account fromAccount, Account toAccount, Amount amount) throws InsufficientFundsException {
	synchronized (fromAccount) {
		synchronized (toAccount) {
			if (fromAccount.getBalance().compareTo(amount) < 0)
				throw new InsufficientFundsException();
			else {
				fromAccount.debit(amount);
				toAccount.credit(amount);
			}
		}
	}
}
Sepisanan, kode iki disinkronake kanthi normal; kita duwe operasi atom kanggo mriksa lan ngganti status akun sumber lan ngganti akun tujuan. Nanging, kanthi strategi sinkronisasi iki, kahanan buntu bisa kedadeyan. Ayo goleki conto carane iki kedadeyan. Sampeyan kudu nggawe rong transaksi: transfer x dhuwit saka akun A menyang akun B, lan transfer dhuwit y saka akun B menyang akun A. Asring kahanan iki ora bakal nimbulaké deadlock, Nanging, ing pesawat apes saka kahanan, transaksi 1 bakal Occupy akun monitor A, transaksi 2 Occupy akun monitor B. Asil iku deadlock: transaksi 1 ngenteni transaksi 2 kanggo release akun monitor. B, nanging transaksi 2 kudu ngakses monitor A, kang dikuwasani dening transaksi 1. Salah masalah amba karo deadlocks iku padha ora gampang kanggo nggoleki ing testing. Malah ing kahanan sing diterangake ing conto kasebut, benang bisa uga ora diblokir, yaiku, kahanan iki ora bakal ditindakake terus-terusan, sing nyebabake komplikasi diagnostik. Umumé, masalah sing diterangake non-determinisme khas kanggo multithreading (sanajan iki ora nggawe luwih gampang). Mulane, review kode nduweni peran penting kanggo ningkatake kualitas aplikasi multi-threaded, amarga ngidini sampeyan ngenali kesalahan sing angel ditindakake sajrone tes. Iki, mesthi, ora ateges aplikasi kasebut ora perlu dites; kita kudu ora lali babagan review kode. Apa sing kudu dilakoni kanggo nyegah kode iki nyebabake deadlock? Pamblokiran iki disebabake amarga sinkronisasi akun bisa kedadeyan ing urutan sing beda. Dadi, yen sampeyan ngenalake sawetara pesenan ing akun kasebut (iki sawetara aturan sing ngidini sampeyan ujar manawa akun A kurang saka akun B), mula masalah kasebut bakal diilangi. Carane nindakake? Kaping pisanan, yen akun duwe sawetara jinis pengenal unik (umpamane, nomer akun) numerik, huruf cilik, utawa sawetara liyane kanthi konsep urutan alami (string bisa dibandhingake kanthi urutan leksikografis, mula kita bisa nganggep awake dhewe begja, lan kita bakal bisa nindakake kanthi bener. tansah Kita bisa pisanan Occupy monitor akun cilik, lan banjur luwih gedhe (utawa kosok balene).
private void doTransfer(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException {
	if (fromAcct.getBalance().compareTo(amount) < 0)
		throw new InsufficientFundsException();
	else {
		fromAcct.debit(amount);
		toAcct.credit(amount);
	}
}
public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException {
	int fromId= fromAcct.getId();
	int toId = fromAcct.getId();
	if (fromId < toId) {
		synchronized (fromAcct) {
			synchronized (toAcct) {
				doTransfer(fromAcct, toAcct, amount)}
			}
		}
	} else  {
		synchronized (toAcct) {
			synchronized (fromAcct) {
				doTransfer(fromAcct, toAcct, amount)}
			}
		}
	}
}
Opsi kapindho, yen kita ora duwe pengenal kasebut, kita kudu nggawe dhewe. Kita bisa, kanggo perkiraan pisanan, mbandhingake obyek kanthi kode hash. Paling kamungkinan bakal beda. Nanging apa yen padha dadi padha? Banjur sampeyan kudu nambah obyek liyane kanggo sinkronisasi. Iki bisa uga katon rada canggih, nanging apa sing bisa ditindakake? Kajaba iku, obyek katelu bakal digunakake arang banget. Asil bakal katon kaya iki:
private static final Object tieLock = new Object();
private void doTransfer(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException {
	if (fromAcct.getBalance().compareTo(amount) < 0)
		throw new InsufficientFundsException();
	else {
		fromAcct.debit(amount);
		toAcct.credit(amount);
	}
}
public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException {
	int fromHash = System.identityHashCode(fromAcct);
	int toHash = System.identityHashCode(toAcct);
	if (fromHash < toHash) {
		synchronized (fromAcct) {
			synchronized (toAcct) {
				doTransfer(fromAcct, toAcct, amount);
			}
		}
	} else if (fromHash > toHash) {
		synchronized (toAcct) {
			synchronized (fromAcct) {
				doTransfer(fromAcct, toAcct, amount);
			}
		}
	} else {
		synchronized (tieLock) {
			synchronized (fromAcct) {
				synchronized (toAcct) {
					doTransfer(fromAcct, toAcct, amount)
				}
			}
		}
	}
}

Deadlock antarane obyek

Kondisi pamblokiran sing diterangake minangka kasus deadlock sing paling gampang kanggo diagnosa. Asring ing aplikasi multi-threaded, obyek sing beda nyoba ngakses blok sing disinkronake sing padha. Iki bisa nyebabake deadlock. Coba conto ing ngisor iki: aplikasi dispatcher penerbangan. Pesawat ngandhani pengontrol nalika wis tekan panggonan sing dituju lan njaluk ijin kanggo ndharat. Kontroler nyimpen kabeh informasi babagan pesawat sing mabur menyang arahe lan bisa ngrancang posisine ing peta.
class Plane {
	private Point location, destination;
	private final Dispatcher dispatcher;

	public Plane(Dispatcher dispatcher) {
		this.dispatcher = dispatcher;
	}
	public synchronized Point getLocation() {
		return location;
	}
	public synchronized void setLocation(Point location) {
		this.location = location;
		if (location.equals(destination))
		dispatcher.requestLanding(this);
	}
}

class Dispatcher {
	private final Set<Plane> planes;
	private final Set<Plane> planesPendingLanding;

	public Dispatcher() {
		planes = new HashSet<Plane>();
		planesPendingLanding = new HashSet<Plane>();
	}
	public synchronized void requestLanding(Plane plane) {
		planesPendingLanding.add(plane);
	}
	public synchronized Image getMap() {
		Image image = new Image();
		for (Plane plane : planes)
			image.drawMarker(plane.getLocation());
		return image;
	}
}
Ngerteni manawa ana bug ing kode iki sing bisa nyebabake deadlock luwih angel tinimbang sadurunge. Sepisanan, ora ana sinkronisasi maneh, nanging ora kaya ngono. Sampeyan mbokmenawa wis ngeweruhi sing setLocationkelas Planelan getMapkelas cara Dispatchersing disinkronake lan nelpon diselarasake cara saka kelas liyane ing dhewe. Iki umume laku ala. Kepiye carane bisa didandani bakal dibahas ing bagean sabanjure. Akibaté, yen pesawat teka ing lokasi, wayahe wong mutusaké kanggo njaluk kertu, deadlock bisa kelakon. Yaiku, metode getMaplan bakal diarani, setLocationsing bakal ngenggoni monitor Dispatcherlan Planemasing-masing. Cara kasebut banjur bakal getMapnelpon plane.getLocation(khusus ing conto Planesing lagi sibuk) sing bakal ngenteni monitor dadi gratis kanggo saben kedadeyan Plane. Ing wektu sing padha, cara kasebut setLocationbakal diarani dispatcher.requestLanding, nalika monitor conto Dispatchertetep sibuk nggambar peta. Akibaté buntu.

Bukak telpon

Kanggo ngindhari kahanan kaya sing diterangake ing bagean sadurunge, disaranake nggunakake telpon umum kanggo metode obyek liyane. Yaiku, cara nelpon obyek liyane ing njaba blok sing disinkronake. Yen cara ditulis maneh nggunakake prinsip telpon mbukak setLocation, getMapkemungkinan deadlock bakal diilangi. Iku bakal katon, contone, kaya iki:
public void setLocation(Point location) {
	boolean reachedDestination;
	synchronized(this){
		this.location = location;
		reachedDestination = location.equals(destination);
	}
	if (reachedDestination)
		dispatcher.requestLanding(this);
}
………………………………………………………………………………
public Image getMap() {
	Set<Plane> copy;
	synchronized(this){
		copy = new HashSet<Plane>( planes);
	}
	Image image = new Image();
	for (Plane plane : copy)
		image.drawMarker(plane.getLocation());
	return image;
}

Kebuntuan sumber daya

Deadlocks uga bisa kedadeyan nalika nyoba ngakses sawetara sumber daya sing mung bisa digunakake siji utas. Conto bakal dadi blumbang sambungan database. Yen sawetara utas kudu ngakses loro sambungan bebarengan lan padha ngakses ing pesenan beda, iki bisa mimpin kanggo deadlock. Sejatine, ngunci jinis iki ora beda karo ngunci urutan sinkronisasi, kajaba kedadeyan kasebut ora nalika nyoba nglakokake sawetara kode, nanging nalika nyoba ngakses sumber daya.

Carane supaya deadlocks?

Mesthi wae, yen kode kasebut ditulis tanpa kesalahan (conto sing kita deleng ing bagean sadurunge), mula ora bakal ana deadlocks. Nanging sapa sing bisa njamin yen kode kasebut ditulis tanpa kesalahan? Mesthi, testing mbantu kanggo ngenali bagean pinunjul saka kasalahan, nanging kita wis katon sadurungé, kesalahan ing kode multi-threaded ora gampang kanggo diagnosa lan malah sawise testing sampeyan ora bisa mesthekake yen ora ana kahanan deadlock. Apa kita bisa nglindhungi awake dhewe saka pamblokiran? Jawabane ya. Teknik sing padha digunakake ing mesin database, sing asring kudu pulih saka deadlocks (digandhengake karo mekanisme transaksi ing database). Antarmuka Locklan implementasine kasedhiya ing paket java.util.concurrent.locksngijini sampeyan kanggo nyoba kanggo Occupy monitor digandhengake karo Kayata saka kelas iki nggunakake cara tryLock(ngasilake bener yen iku bisa kanggo Occupy monitor). Upaminipun kita duwe sepasang obyek sing ngleksanakake antarmuka Locklan kita kudu Occupy monitor ing kuwi cara kanggo ngindhari pamblokiran bebarengan. Sampeyan bisa ngleksanakake kaya iki:
public void twoLocks(Lock A,  Lock B){
	while(true){
		if(A.tryLock()){
			if(B.tryLock())
			{
				try{
					//do something
				} finally{
					B.unlock();
					A.unlock();
				}
			} else{
				A.unlock();
			}
		}
	}
}
Kaya sing sampeyan ngerteni ing program iki, kita duwe rong monitor, nalika ngilangi kemungkinan pamblokiran bebarengan. Wigati dicathet yen pamblokiran kasebut try- finallyperlu amarga kelas ing paket kasebut java.util.concurrent.locksora ngeculake monitor kanthi otomatis, lan yen ana sawetara pangecualian nalika nindakake tugas sampeyan, monitor bakal macet ing kahanan sing dikunci. Carane diagnosa deadlocks? JVM ngijini sampeyan kanggo diagnosa deadlocks kanthi nampilake ing dumps thread. Dumps kuwi kalebu informasi babagan apa negara thread ing. Yen diblokir, mbucal ngemot informasi babagan monitor sing thread nunggu kanggo dirilis. Sadurunge mbuwang benang, JVM ndeleng grafik monitor nunggu (sibuk), lan yen nemokake siklus, nambah informasi deadlock, nuduhake monitor lan benang sing melu. Dump thread deadlocked katon kaya iki:
Found one Java-level deadlock:
=============================
"ApplicationServerThread":
waiting to lock monitor 0x0f0d80cc (a MyDBConnection),
which is held by "ApplicationServerThread"
"ApplicationServerThread":
waiting to lock monitor 0x0f0d8fed (a MyDBCallableStatement),
which is held by "ApplicationServerThread"
Java stack information for the threads listed above:
"ApplicationServerThread":
at MyDBConnection.remove_statement
- waiting to lock <0x6f50f730> (a MyDBConnection)
at MyDBStatement.close
- locked <0x604ffbb0> (a MyDBCallableStatement)
...
"ApplicationServerThread":
at MyDBCallableStatement.sendBatch
- waiting to lock <0x604ffbb0> (a MyDBCallableStatement)
at MyDBConnection.commit
- locked <0x6f50f730> (a MyDBConnection)
Dump ing ndhuwur kanthi jelas nuduhake yen rong utas sing nggarap database wis diblokir saben liyane. Supaya diagnosa deadlocks nggunakake fitur JVM iki, iku perlu kanggo nelpon kanggo operasi thread dump ing macem-macem panggonan ing program lan nyoba aplikasi. Sabanjure, sampeyan kudu nganalisa log sing diasilake. Yen padha nuduhake yen deadlock wis kedaden, informasi saka mbucal bakal bantuan kanggo ndeteksi kahanan ing ngendi iku kedaden. Umumé, sampeyan kudu ngindhari kahanan kaya sing ana ing conto deadlock. Ing kasus kaya mengkono, aplikasi kasebut bakal bisa digunakake kanthi stabil. Nanging aja lali babagan testing lan review kode. Iki bakal mbantu ngenali masalah yen kedadeyan. Ing kasus ngendi sampeyan ngembangaken sistem kang Recovery saka lapangan deadlock kritis, sampeyan bisa nggunakake cara diterangake ing bagean "Carane supaya deadlocks?". Ing kasus iki, cara lockInterruptiblyantarmuka Locksaka java.util.concurrent.locks. Iku ngijini sampeyan kanggo ngganggu thread sing wis dikuwasani monitor nggunakake cara iki (lan mbebasake monitor).
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION