徹底搞懂Java多線程(二)
Java中的鎖
Java中的加鎖操作有兩種:
1.synchronized鎖(jvm層的解決方案,也叫監(jiān)視器鎖)
在操作系統(tǒng)的層面使用的是互斥鎖(mutex lock)
在Java中放在了對(duì)象頭中。
2.手動(dòng)鎖Lock
操作鎖的流程
- 1.嘗試獲取鎖
- 2.使用鎖
- 3.釋放鎖
synchronized鎖
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:12;
*/
class Counter2 {
private static volatile int count = 0;
public void increase() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease() {
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount() {
return count;
}
}
public class ThreadDemo19 {
public static void main(String[] args) throws InterruptedException {
//聲明鎖對(duì)象,任何的對(duì)象都可以作為鎖
Object lock = new Object();
Counter2 counter2 = new Counter2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//使用鎖
synchronized (lock) {
counter2.decrease();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
counter2.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter2.getCount());
}
}
結(jié)果是:

synchronized使用場(chǎng)景
1.使用synchronized來(lái)修飾代碼塊(可以給任意的對(duì)象進(jìn)行加鎖操作)
public class ThreadDemo19 {
public static void main(String[] args) throws InterruptedException {
//聲明鎖對(duì)象,任何的對(duì)象都可以作為鎖
Object lock = new Object();
Counter2 counter2 = new Counter2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//使用鎖
synchronized (lock) {
counter2.decrease();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
counter2.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter2.getCount());
}
}

2.使用synchronized來(lái)修飾靜態(tài)方法(對(duì)當(dāng)前的類進(jìn)行加鎖的操作)
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:02;
*/
class Counter1 {
private static volatile int count = 0;
public void increase() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease() {
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount() {
return count;
}
}
public class ThreadDemo18 {
public static void main(String[] args) throws InterruptedException {
Counter1 counter1 = new Counter1();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
counter1.decrease();
}
});
Thread thread2 = new Thread(() -> {
counter1.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter1.getCount());
}
}

3.使用synchronized來(lái)修飾普通的方法(對(duì)當(dāng)前類的實(shí)例來(lái)進(jìn)行加鎖)
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:12;
*/
public class ThreadDemo20 {
private static int num = 0;
private static final int maxSize = 100000;
public static void main(String[] args) throws InterruptedException {
ThreadDemo20 threadDemo20 = new ThreadDemo20();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadDemo20.increase();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadDemo20. decrease();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(num);
}
//給靜態(tài)的方法進(jìn)行加鎖,被加的鎖是當(dāng)前的對(duì)象。
// public synchronized static void increase(){
//給普通的方法進(jìn)行加鎖的操作
public synchronized void increase() {
for (int i = 0; i < maxSize; i++) {
num++;
}
}
// public synchronized static void decrease(){
public synchronized void decrease() {
for (int i = 0; i < maxSize; i++) {
num--;
}
}
}

synchronized注意事項(xiàng)
1.加鎖的時(shí)候一定要使用同一把鎖對(duì)象
Lock類的使用
也叫手動(dòng)鎖
package ThreadDeom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* user:ypc;
* date:2021-06-12;
* time: 18:32;
*/
public class ThreadDemo22 {
private static int number = 0;
private static final int maxSize = 100000;
public static void main(String[] args) {
//創(chuàng)建lock鎖對(duì)象,lock是接口,不能實(shí)列化
Lock lock = new ReentrantLock();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < maxSize; i++) {
lock.lock();
try {
number++;
} finally {
lock.unlock();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < maxSize; i++) {
lock.lock();
try {
number--;
} finally {
lock.unlock();
}
}
});
System.out.println(number);
}
}

Lock鎖使用的注意事項(xiàng)
lock()操作一定要放在try外面
如果放在try的里面:
1.try中拋出了異常,還沒(méi)有加鎖就釋放了finally中的鎖的操作了
2.如果放在了try,沒(méi)加鎖就釋放了鎖,就會(huì)拋出異常,就會(huì)將業(yè)務(wù)代碼中的異常吞噬掉👇如果一定要放的話,將lock()放在try的第一行。
package ThreadDeom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* user:ypc;
* date:2021-06-12;
* time: 18:49;
*/
public class ThreadDemo23 {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
try{
System.out.println(1/0);
lock.lock();
} finally {
lock.unlock();
}
}
}

公平鎖、非公平鎖
公平鎖的調(diào)度:
一個(gè)線程釋放鎖。
主動(dòng)喚醒“需要得到鎖”的隊(duì)列來(lái)得到鎖。
非公平鎖
當(dāng)一個(gè)線程釋放鎖之后,另一個(gè)線程剛好執(zhí)行到獲取鎖的代碼就可以直接獲取鎖。
Java中的所有鎖默認(rèn)都是非公平鎖。
非公平鎖的性能更高。
ReentrantLock可以設(shè)置非公平鎖。
公平鎖
package ThreadDeom;
import java.util.concurrent.locks.ReentrantLock;
/**
* user:ypc;
* date:2021-06-12;
* time: 19:22;
*/
public class ThreadDemo24 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
reentrantLock.lock();
try {
System.out.println("thread1");
} finally {
reentrantLock.unlock();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
reentrantLock.lock();
try {
System.out.println("thread2");
} finally {
reentrantLock.unlock();
}
}
});
Thread.sleep(100);
thread1.start();
thread2.start();
}
}
打印的結(jié)果是無(wú)序的

如果設(shè)置為公平鎖:👇


thread1和thread2 交替輸出
synchronzied 和 Lock 的區(qū)別
1.synchronzied可以自動(dòng)的進(jìn)行加鎖和釋放鎖,而Lock需要手動(dòng)的加鎖、釋放鎖。
2.Lock是Java層面的鎖實(shí)現(xiàn),而synchronzied 是JVM層面鎖的實(shí)現(xiàn)
3.synchronzed 即可以修飾代碼塊,又可以修飾普通方法和靜態(tài)的方法,而Lock 只能修飾代碼塊
4.synchronized 實(shí)現(xiàn)的是 非公平的鎖,而Lock 可以實(shí)現(xiàn)公平鎖。
5.lock的靈活性更高
死鎖
在兩個(gè)或兩個(gè)以上的線程運(yùn)行中,因?yàn)橘Y源的搶占而造成線程一直等待的問(wèn)題???#128071;:
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 19:48;
*/
public class ThreadDemo25 {
public static void main(String[] args) throws InterruptedException {
Object lockA = new Object();
Object lockB = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
//讓線程2獲取lockB
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//線程2獲取資源B
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
//讓線程1先獲取到鎖lockA
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
}
}
}
});
thread1.start();
thread2.start();
}
}
這就造成了死鎖

造成死鎖的四個(gè)條件
1.互斥條件:
當(dāng)資源被一個(gè)線程擁有之后,就不能被其它的線程擁有了
2.擁有請(qǐng)求條件:
當(dāng)一個(gè)線程擁有了一個(gè)資源之后,又試圖請(qǐng)求另一個(gè)資源。
3.不可剝奪條件:
當(dāng)一個(gè)線程擁有了一個(gè)資源之后,如果不是這個(gè)線程主動(dòng)的釋放資源,其他線程就不能擁有這個(gè)線程。
4.環(huán)路等待條件:
兩個(gè)或兩個(gè)以上的線程擁有了資源之后,試圖獲取對(duì)方的資源的時(shí)候形成了一個(gè)環(huán)路。
死鎖的解決方案
解決請(qǐng)求擁有和環(huán)路等待。
最有效的解決方案就是控制加鎖的順序。
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 20:25;
*/
public class ThreadDemo26 {
public static void main(String[] args) throws InterruptedException {
Object lockA = new Object();
Object lockB = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
//讓線程2獲取lockB
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
}
}
}
});
thread1.start();
thread2.start();
}
}

線程間通信
線程之間的通訊是指在一個(gè)線程中的操作可以影響另一個(gè)線程。
wait/notify機(jī)制的原理
擁有相同鎖的線程之間才能使用wait/notify機(jī)制。
wait()是Object()的方法,它的作用是是當(dāng)前執(zhí)行wait()方法的線程等待,在wati()所在的代碼出停止執(zhí)行,并釋放鎖,直到接到通知或者被中斷為止。即在調(diào)用wait()的方法之前,線程必需先獲取到對(duì)象級(jí)別的鎖,也就是只能在同步方法或者同步塊中使用wait()方法。
如果在使用wait()方法之前線程沒(méi)有獲得相應(yīng)的鎖,那么程序在執(zhí)行時(shí)就會(huì)拋出異常。
notify()方法要在同步方法或者同步塊中執(zhí)行,即在調(diào)用notify()方法之前,線程必需要先獲取到鎖對(duì)象。如果線程沒(méi)有持有鎖對(duì)象的話,那么也會(huì)拋出異常。該方法用來(lái)通知可能在等待該鎖的其它線程,如果有多個(gè)線程,那么則按照?qǐng)?zhí)行wait()方法的順序來(lái)對(duì)處于wait()方法的線程發(fā)出通知,并使該線程重新獲取鎖。執(zhí)行notify()方法之后,當(dāng)前線程不會(huì)馬上釋放鎖,處于wait()狀態(tài)的線程也不會(huì)立馬得到這個(gè)對(duì)象鎖。而是要等notify的synchronized同步區(qū)域執(zhí)行完成之后才會(huì)釋放鎖,處于wait()狀態(tài)的線程才會(huì)得到鎖對(duì)象。
總結(jié):wait()方法用于讓線程停止運(yùn)行,而notify()方法用于通知暫停的線程繼續(xù)運(yùn)行。
在使用wait()或者notify()方法之前沒(méi)有對(duì)象鎖,就會(huì)報(bào)異常👇:
lock.notify();

正確的使用之后
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 21:11;
*/
public class ThreadDemo27 {
//設(shè)置鎖對(duì)象
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被notify()喚醒之后");
}
}
});
thread.start();
Thread.sleep(1000);
synchronized (lock) {
lock.notify();
}
}
}

注意:使用wait()方法的時(shí)候一定要和線程的鎖對(duì)象是一個(gè)鎖。
notifyAll
在多線程的情況下使用notify()方法只可以喚醒一個(gè)線程👇

package ThreadDeom;
/**
* user:ypc;
* date:2021-06-13;
* time: 8:06;
*/
public class ThreadDemo28 {
//設(shè)置鎖對(duì)象
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("thread1在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1被notify()喚醒之后");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("thread2在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2被notify()喚醒之后");
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("thread3在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread3被notify()喚醒之后");
}
}
});
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(1000);
synchronized (lock) {
System.out.println("主線程調(diào)用notify()之后");
lock.notify();
}
}
}
那么如果使用notifyAll()方法呢?

可以看到所有的線程都被喚醒了

那么使用notify()喚醒的線程有沒(méi)有什么順序呢?
使用notify()喚醒線程的順序是正序、倒序、還是隨機(jī)的,這取決與JVM的具體實(shí)現(xiàn),并不是所有的JVM在執(zhí)行notify()時(shí)都是按照wait()的執(zhí)行順序進(jìn)行喚醒的,也不是所有的notidyAll()都是按照wait()方法的倒序進(jìn)行喚醒的,這取決于JVM的具體實(shí)現(xiàn)。
wait()和notify()不能喚醒指定的線程。
wait()和sleep()的區(qū)別
也可以讓wait()等待指定的時(shí)間,如果超過(guò)給定的時(shí)間,wait()不會(huì)無(wú)限期的等待下去.

沒(méi)有被notify()喚醒,過(guò)了1000毫秒之后會(huì)自動(dòng)停止。

wait()在不傳入任何參數(shù)的時(shí)候,線程會(huì)進(jìn)入waiting 的狀態(tài),而在wait()中加入一個(gè)大于0的參數(shù)的時(shí)候,線程會(huì)進(jìn)入time_wating的狀態(tài)。
sleep()和wait()的區(qū)別 : 線程在sleep()的時(shí)候是不會(huì)釋放鎖的,而執(zhí)行wait()的時(shí)候它就會(huì)釋放鎖。👇:
package ThreadDeom;
import jdk.nashorn.internal.ir.Block;
/**
* user:ypc;
* date:2021-06-13;
* time: 8:45;
*/
public class ThreadDemo29 {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("thread獲取到了鎖");
//如果sleep釋放鎖的話,會(huì)在thread獲取到了鎖和thread釋放了鎖之間打印
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread釋放了鎖");
}
});
thread.start();
//讓thread 先獲取到鎖
Thread.sleep(1000);
synchronized (lock) {
System.out.println("主線程獲取到了鎖");
}
}
}

可以看到線程在sleep()的時(shí)候,線程是不會(huì)釋放鎖的。再來(lái)看看wait()方法👇:


線程使用wait()的時(shí)候它就會(huì)釋放掉鎖。
1.wait()和sleep()都是讓線程進(jìn)行休眠的
2.wait()和sleep()方法都有可能在執(zhí)行的過(guò)程接收到線程終止的通知
3.wait()必須和synchronzied一起使用,而sleep()不用。
4.wait()會(huì)釋放鎖,而sleep()不會(huì)釋放鎖。
5.wait()時(shí)Object的方法,而sleep()時(shí)Thread的方法。
6.默認(rèn)情況下,wait()不傳任何的參數(shù)的情況下,wait()會(huì)進(jìn)入waiting的狀態(tài),如果傳遞了參數(shù),wait()會(huì)進(jìn)入time_waiting的狀態(tài)。而sleep()進(jìn)入的是time_waiting的狀態(tài)。
sleep(0) 和wait(0)的區(qū)別:
1.sleep(0)表示0毫秒之后繼續(xù)執(zhí)行,而wait(0)表示線程會(huì)一直休眠下去wait(0)和wait()是一樣的,wait()的源碼就是調(diào)用了wait(0)方法。
2.sleep(0)表示重新出發(fā)一次CPU的競(jìng)爭(zhēng)。
為什么wait()會(huì)釋放鎖,而sleep()不會(huì)釋放鎖?
sleep()需要傳遞一個(gè)最大的等待時(shí)間,也就是說(shuō)sleep()是可控的,而wait()是不可以傳遞參數(shù)的,從設(shè)計(jì)的層面來(lái)說(shuō),如果讓wait()一直持有所得話,那么線程就可能一直阻塞。
為什么wait()是Object的方法,而sleep()是線程的方法?
wait()需要操作鎖,而鎖是屬于對(duì)象級(jí)別的,所有的鎖都是放在對(duì)象頭中的,它不是線程級(jí)別的,一個(gè)線程可以有多把的鎖,為了靈活,就將wait()放在Object中了。
LockSupport park()/unpark()
使用LockSupport可以解決wait()/notify()隨機(jī)喚醒的問(wèn)題。
package ThreadDeom;
import java.util.concurrent.locks.LockSupport;
/**
* user:ypc;
* date:2021-06-13;
* time: 9:36;
*/
public class ThreadDemo30 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//讓線程休眠
LockSupport.park();
System.out.println("unPark()了thread1");
}
});
Thread thread2 = new Thread(() -> {
LockSupport.park();
System.out.println("unPark()了thread2");
});
Thread thread3 = new Thread() {
@Override
public void run() {
LockSupport.park();
System.out.println("unPark()了thread3");
}
};
thread1.start();
thread2.start();
thread3.start();
LockSupport.unpark(thread1);
LockSupport.unpark(thread2);
}
}

總結(jié)
本篇文章就到這里了,希望可以幫助到你,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java連接postgresql數(shù)據(jù)庫(kù)的示例代碼
本篇文章主要介紹了Java連接postgresql數(shù)據(jù)庫(kù)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
SpringCloud分布式事務(wù)Seata部署和集成過(guò)程
這篇文章主要介紹了SpringCloud分布式事務(wù)Seata部署和集成過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-10-10
Springboot基于maven打包分離lib及resource
這篇文章主要介紹了Springboot基于maven打包分離lib及resource,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Java Math類的三個(gè)方法ceil,floor,round用法
這篇文章主要介紹了Java Math類的三個(gè)方法ceil,floor,round用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java中Comparable和Comparator兩種比較器的區(qū)別詳解
這篇文章主要介紹了Java中Comparable和Comparator兩種比較器的區(qū)別詳解,Comparable接口將比較代碼嵌入自身類中,像Integer、String等這些基本類型的JAVA封裝類都已經(jīng)實(shí)現(xiàn)了Comparable接口,這些類對(duì)象本身就支持和自己比較,需要的朋友可以參考下2023-09-09
java隨機(jī)抽取指定范圍內(nèi)不重復(fù)的n個(gè)數(shù)
這篇文章主要為大家詳細(xì)介紹了java隨機(jī)抽取指定范圍內(nèi)不重復(fù)的n個(gè)數(shù),感興趣的小伙伴們可以參考一下2016-02-02
mybatis之調(diào)用帶輸出參數(shù)的存儲(chǔ)過(guò)程(Oracle)
這篇文章主要介紹了mybatis調(diào)用帶輸出參數(shù)的存儲(chǔ)過(guò)程(Oracle),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11

