線程安全與不安全
線程安全:當(dāng)多線程訪問時(shí)棒假,采用了加鎖的機(jī)制歇盼;即當(dāng)一個(gè)線程訪問該類的某一個(gè)數(shù)據(jù)時(shí)道宅,會(huì)對(duì)這個(gè)數(shù)據(jù)進(jìn)行保護(hù)萧豆,其他線程不能對(duì)其訪問臼朗,直到該線程讀取結(jié)束之后邻寿,其他線程才可以使用。防止出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)被污染的情況视哑。
線程不安全:多個(gè)線程同時(shí)操作某個(gè)數(shù)據(jù)绣否,出現(xiàn)數(shù)據(jù)不一致或者被污染的情況。
代碼示例:
package thread_5_10;
public class Demo26 {
static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100_0000; i++) {
a++;
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100_0000; i++) {
a--;
}
}
});
//開啟線程
t1.start();
t2.start();
//等待線程完成
//t1.join();
//t2.join();
while(t1.isAlive() || t2.isAlive()){
}
System.out.println(a);
}
}
運(yùn)行結(jié)果:
493612
結(jié)果分析:
個(gè)人整理了一些資料挡毅,有需要的朋友可以直接點(diǎn)擊領(lǐng)取蒜撮。
[1000+道2021年最新面試題](https://jq.qq.com/?_wv=1027&k=6SfDAjTT
線程不安全的因素:
CPU是搶占式執(zhí)行的(搶占資源)
多個(gè)線程操作的是同一個(gè)變量
可見性
非原子性
編譯期優(yōu)化(指令重排)
volatile
volatile是指令關(guān)鍵字,作用是確保本指令不會(huì)因編譯期優(yōu)化而省略跪呈,且每次要求直接讀值段磨。可以解決內(nèi)存不可見和指令重排序的問題耗绿,但是不能解決原子性問題
解決線程不安全
有兩種加鎖方式:
synchronized(jvm層的解決方案)
Lock手動(dòng)鎖
synchronized
操作鎖的流程
嘗試獲取鎖a
使用鎖(這一步驟是具體的業(yè)務(wù)代碼)
釋放鎖
synchronized是JVM層面鎖的解決方案苹支,它幫我們實(shí)現(xiàn)了加鎖和釋放鎖的過程
代碼示例
package thread_5_10;
public class Demo31 {
//循環(huán)的最大次數(shù)
private final static int maxSize = 100_0000;
//定義全局變量
private static int number = 0;
public static void main(String[] args) throws InterruptedException {
//聲明鎖對(duì)象
Object obj = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < maxSize; i++) {
//實(shí)現(xiàn)加鎖
synchronized (obj){
number++;
}
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < maxSize; i++) {
synchronized (obj){
number--;
}
}
}
});
t2.start();
//等待兩個(gè)線程執(zhí)行完成
t1.join();
t2.join();
System.out.println(number);
}
}
運(yùn)行結(jié)果:
0
解析:
注意
synchronized實(shí)現(xiàn)分為:
操作系統(tǒng)層面,它是依靠互斥鎖mutex
針對(duì)JVM误阻,monitor實(shí)現(xiàn)
針對(duì)Java語(yǔ)言來說债蜜,是將鎖信息存放在對(duì)象頭中
三種使用場(chǎng)景
使用synchronized修飾代碼塊晴埂,(可以對(duì)任意對(duì)象加鎖)
使用synchronized修飾靜態(tài)方法(對(duì)當(dāng)前類進(jìn)行加鎖)
使用synchronized修飾普通方法(對(duì)當(dāng)前類實(shí)例進(jìn)行加鎖)
修飾靜態(tài)方法:
package thread_5_10;
public class Demo32 {
private static int number = 0;
private static final int maxSize = 100_0000;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
increment();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
decrement();
}
});
t2.start();
t1.join();
t2.join();
System.out.println("最終結(jié)果為:"+number);
}
public synchronized static void increment(){
for (int i = 0; i < maxSize; i++) {
number++;
}
}
public synchronized static void decrement(){
for (int i = 0; i < maxSize; i++) {
number--;
}
}
}
修飾實(shí)例方法:
package thread_5_10;
public class Demo33 {
private static int number = 0;
private static final int maxSize = 100_0000;
public static void main(String[] args) throws InterruptedException {
Demo33 demo = new Demo33();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
demo.increment();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
demo.decrement();
}
});
t2.start();
t1.join();
t2.join();
System.out.println("最終結(jié)果:"+number);
}
public synchronized void increment(){
for (int i = 0; i < maxSize; i++) {
number++;
}
}
public synchronized void decrement(){
for (int i = 0; i < maxSize; i++) {
number--;
}
}
}
Lock手動(dòng)鎖
代碼示例:
package thread_5_10;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo34 {
private static int number = 0;
private static final int maxSize = 100_0000;
public static void main(String[] args) throws InterruptedException {
//創(chuàng)建lock實(shí)例
Lock lock = new ReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < maxSize; i++) {
lock.lock();
try{
number++;
}finally {
lock.unlock();
}
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < maxSize; i++) {
lock.lock();
try{
number--;
}finally {
lock.unlock();
}
}
}
});
t2.start();
t1.join();
t2.join();
System.out.println("最終結(jié)果為--> "+number);
}
}
運(yùn)行結(jié)果:
最終結(jié)果為--> 0
注意事項(xiàng):
lock()一定要放在try外面
如果放在try里面,如果try里面出現(xiàn)異常寻定,還沒有加鎖成功就執(zhí)行finally里面的釋放鎖的代碼儒洛,就會(huì)出現(xiàn)異常
如果放在try里面,如果沒有鎖的情況下釋放鎖特姐,這個(gè)時(shí)候產(chǎn)生的異常就會(huì)把業(yè)務(wù)代碼里面的異常給吞噬掉晶丘,增加代碼調(diào)試的難度
公平鎖與非公平鎖
公平鎖:當(dāng)一個(gè)線程釋放鎖之后黍氮,需要主動(dòng)喚醒“需要得到鎖”的隊(duì)列來得到鎖
非公平鎖:當(dāng)一個(gè)線程釋放鎖之后唐含,另一個(gè)線程剛好執(zhí)行到獲取鎖的代碼就可以直接獲取鎖
java語(yǔ)言中,所有鎖的默認(rèn)實(shí)現(xiàn)方式都是非公平鎖
1.synchronized是非公平鎖
2.reentrantLock默認(rèn)是非公平鎖沫浆,但也可以顯示地聲明為公平鎖
顯示聲明公平鎖格式:
ReentrantLock源碼:
示例一:
package thread_5_10;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo36 {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock(true);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try{
System.out.println("線程1");
}finally {
lock.unlock();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try{
System.out.println("線程2");
}finally {
lock.unlock();
}
}
}
});
Thread.sleep(1000);
t1.start();
t2.start();
}
}
運(yùn)行結(jié)果:
示例二:
package test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test08 {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock(true);
Runnable r = new Runnable() {
@Override
public void run() {
for(char ch: "ABCD".toCharArray()){
lock.lock();
try{
System.out.print(ch);
}finally {
lock.unlock();
}
}
}
};
Thread.sleep(100);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
運(yùn)行結(jié)果:
AABBCCDD
兩種鎖區(qū)別
synchronized和lock的區(qū)別
關(guān)鍵字不同
synchronized自動(dòng)進(jìn)行加鎖和釋放鎖捷枯,而Lock需要手動(dòng)加鎖和釋放鎖
synchronized是JVM層面上的實(shí)現(xiàn),而Lock是Java層面鎖的實(shí)現(xiàn)
修飾范圍不同专执,synchronized可以修飾代碼塊淮捆,靜態(tài)方法,實(shí)例方法本股,而Lock只能修飾代碼塊
synchronized鎖的模式是非公平鎖攀痊,而lock鎖的模式是公平鎖和非公平鎖
Lock的靈活性更高
死鎖
死鎖定義
在兩個(gè)或兩個(gè)以上的線程運(yùn)行中,因?yàn)橘Y源搶占而造成線程一直等待的問題
當(dāng)線程1擁有資源并1且試圖獲取資源2和線程2擁有了資源2拄显,并且試圖獲取資源1的時(shí)候苟径,就發(fā)了死鎖
死鎖示例
package thread_5_11;
public class Demo36 {
public static void main(String[] args) {
//聲明加鎖的資源
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//獲取線程名稱
String threadName = Thread.currentThread().getName();
//1.獲取資源1
synchronized (lock1){
System.out.println(threadName+" 獲取到了lock1");
try {
//2.等待1ms,讓線程t1和線程t2都獲取到相應(yīng)的資源
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName+" waiting lock2");
//3.獲取資源2
synchronized (lock2){
System.out.println(threadName+" 獲取到了lock2");
}
}
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
synchronized (lock2){
System.out.println(threadName+" 獲取到了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName+" waiting lock1");
synchronized (lock1){
System.out.println(threadName+" 獲取到了lock1");
}
}
}
},"t2");
t2.start();
}
}
運(yùn)行結(jié)果:
通過工具來查看死鎖:
(1)jdk–>bin–>jconsole.exe
(2)jdk–>bin–>jvisualvm.exe
(3)jdk–>bin–>jmc.exe
死鎖的4個(gè)必要條件
1.互斥條件:當(dāng)資源被一個(gè)線程擁有之后躬审,就不能被其他的線程擁有了
2.占有且等待:當(dāng)一個(gè)線程擁有了一個(gè)資源之后又試圖請(qǐng)求另一個(gè)資源
3.不可搶占:當(dāng)一個(gè)資源被一個(gè)線程被擁有之后棘街,如果不是這個(gè)線程主動(dòng)釋放此資源的情況下,其他線程不能擁有此資源
4.循環(huán)等待:兩個(gè)或兩個(gè)以上的線程在擁有了資源之后承边,試圖獲取對(duì)方資源的時(shí)候形成了一個(gè)環(huán)路
線程通訊
所謂的線程通訊就是在一個(gè)線程中的操作可以影響另一個(gè)線程遭殉,wait(休眠線程),notify(喚醒一個(gè)線程)博助,notifyall(喚醒所有線程)
wait方法
注意事項(xiàng):
1.wait方法在執(zhí)行之前必須先加鎖险污。也就是wait方法必須配合synchronized配合使用
2.wait和notify在配合synchronized使用時(shí),一定要使用同一把鎖
運(yùn)行結(jié)果:
wait之前
主線程喚醒t1
wait之后
多線程
package thread_5_13;
public class demo40 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//調(diào)用wait方法之前必須先加鎖
synchronized (lock){
try {
System.out.println("t1 wait之前");
lock.wait();
System.out.println("t1 wait之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//調(diào)用wait方法之前必須先加鎖
synchronized (lock){
try {
System.out.println("t2 wait之前");
lock.wait();
System.out.println("t2 wait之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//調(diào)用wait方法之前必須先加鎖
synchronized (lock){
try {
System.out.println("t3 wait之前");
lock.wait();
System.out.println("t3 wait之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"t3");
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.out.println("主線程調(diào)用喚醒操作");
//在主線程中喚醒
synchronized (lock){
lock.notify();
}
}
}
運(yùn)行結(jié)果:
t1 wait之前
t2 wait之前
t3 wait之前
主線程調(diào)用喚醒操作
t1 wait之后
注意事項(xiàng):
將lock.notify()修改為lock.notifyAll()富岳,則三個(gè)線程都能被喚醒
wait在不傳遞任何參數(shù)的情況下會(huì)進(jìn)入waiting狀態(tài)(參數(shù)為0也是waiting狀態(tài))蛔糯;當(dāng)wait里面有一個(gè)大于0的整數(shù)時(shí),它就會(huì)進(jìn)入timed_waiting狀態(tài)
關(guān)于wait和sleep釋放鎖的代碼:
wait在等待的時(shí)候可以釋放鎖城瞎,sleep在等待的時(shí)候不會(huì)釋放鎖
wait方法與sleep方法對(duì)比
相同點(diǎn):
(1)wait和sleep都可以使線程休眠
(2)wait和sleep在執(zhí)行的過程中都可以接收到終止線程執(zhí)行的通知
不同點(diǎn):
(1)wait必須synchronized一起使用渤闷,而sleep不用
(2)wait會(huì)釋放鎖,sleep不會(huì)釋放鎖
(3)wait是Object的方法脖镀,而sleep是Thread的方法
(4)默認(rèn)情況下飒箭,wait不傳遞參數(shù)或者參數(shù)為0的情況下狼电,它會(huì)進(jìn)入waiting狀態(tài),而sleep會(huì)進(jìn)入timed_waiting狀態(tài)
(5)使用wait可以主動(dòng)喚醒線程弦蹂,而使用sleep不能主動(dòng)喚醒線程
面試題
1.問:sleep(0)和wait(0)有什么區(qū)別
答:(1)sleep(0)表示過0毫秒后繼續(xù)執(zhí)行肩碟,而wait(0)會(huì)一直等待
(2)sleep(0)表示重新觸發(fā)一次CPU競(jìng)爭(zhēng)
2.為什么wait會(huì)釋放鎖,而sleep不會(huì)釋放鎖
答:sleep必須要傳遞一個(gè)最大等待時(shí)間的凸椿,也就是說sleep是可控的(對(duì)于時(shí)間層面來講)削祈,而wait是可以不傳遞時(shí)間,從設(shè)計(jì)層面來講脑漫,如果讓wait這個(gè)沒有超時(shí)等待時(shí)間的機(jī)制下釋放鎖的話髓抑,那么線程可能會(huì)一直阻塞,而sleep不會(huì)存在這個(gè)問題
3.為什么wait是Object的方法优幸,而sleep是Thread的方法
答:wait需要操作鎖吨拍,而鎖是對(duì)象級(jí)別(所有的鎖都在對(duì)象頭當(dāng)中),它不是線程級(jí)別网杆,一個(gè)線程可以有多把鎖羹饰,為了靈活起見,所有把wait放在Object當(dāng)中
4.解決wait/notify隨機(jī)喚醒的問題
答:可以使用LockSupport中的park碳却,unpark方法队秩,注意:locksupport雖然不會(huì)報(bào)interrupted的異常,但是可以監(jiān)聽到線程終止的指令
最后
都看到這里了昼浦,記得點(diǎn)個(gè)贊哦馍资!