java面試必問(wèn):多線程的實(shí)現(xiàn)和同步機(jī)制,一文幫你搞定多線程編程

希望文章像圖片一樣驚艷

前言

進(jìn)程:一個(gè)計(jì)算機(jī)程序的運(yùn)行實(shí)例,包含了需要執(zhí)行的指令;有自己的獨(dú)立地址空間,包含程序內(nèi)容和數(shù)據(jù);不同進(jìn)程的地址空間是互相隔離的;進(jìn)程擁有各種資源和狀態(tài)信息,包括打開(kāi)的文件、子進(jìn)程和信號(hào)處理。

線程:表示程序的執(zhí)行流程,是CPU調(diào)度執(zhí)行的基本單位;線程有自己的程序計(jì)數(shù)器、寄存器、堆棧和幀。同一進(jìn)程中的線程共用相同的地址空間,同時(shí)共享進(jìn)進(jìn)程鎖擁有的內(nèi)存和其他資源。

多線程的實(shí)現(xiàn)

繼承Thread類

創(chuàng)建一個(gè)類,這個(gè)類需要繼承Thread類

重寫Thread類的run方法(run方法中是業(yè)務(wù)代碼)

實(shí)例化此線程類

調(diào)用實(shí)例化對(duì)象的start方法啟動(dòng)線程

packagecom.test;publicclass Demo1 {publicstaticvoidmain(String[] args){ThreadDemothreadDemo =newThreadDemo();? ? ? ? threadDemo.start();? ? }}class ThreadDemo extends Thread{@Overridepublicvoidrun(){System.out.println("運(yùn)行了run方法");? ? }}

在多線程編程中,代碼的執(zhí)行結(jié)果與代碼的執(zhí)行順序或者調(diào)用順序是無(wú)關(guān)的線程是一個(gè)子任務(wù),CPU以不確定的方式或者是以隨機(jī)的時(shí)間來(lái)調(diào)用線程中的run方法這體現(xiàn)了線程運(yùn)行的隨機(jī)性

packagecom.test;publicclass Demo2 {public static void main(String[] args) {? ? ? ? Demo2Thread demo2Thread =newDemo2Thread();/*

? ? *demo2Thread.start方法才是啟動(dòng)線程

? ? *demo2Thread.run方法只是由main主線程來(lái)調(diào)用run方法

? ? */demo2Thread.start();try{for(inti =0; i <3; i++) {? ? ? ? ? ? ? ? System.out.println("運(yùn)行了main方法");? ? ? ? ? ? ? ? Thread.sleep(100);? ? ? ? ? ? }? ? ? ? }catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }}class Demo2Thread extends Thread{@Overridepublic void run() {try{for(inti =0; i <3; i++) {? ? ? ? ? ? ? ? System.out.println("運(yùn)行了run方法");? ? ? ? ? ? ? ? Thread.sleep(100);? ? ? ? ? ? }? ? ? ? }catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }}

start的執(zhí)行順序和線程的啟動(dòng)順序是不一致的

1,2,3,4,5的輸出順序是隨機(jī)的

packagecom.test;publicclass Demo3 {public static void main(String[] args) {? ? Demo3Thread demo3Thread1 =newDemo3Thread(1);? ? ? ? Demo3Thread demo3Thread2 =newDemo3Thread(2);? ? ? ? Demo3Thread demo3Thread3 =newDemo3Thread(3);? ? ? ? Demo3Thread demo3Thread4 =newDemo3Thread(4);? ? ? ? Demo3Thread demo3Thread5 =newDemo3Thread(5);? ? ? ? demo3Thread1.start();? ? ? ? demo3Thread2.start();? ? ? ? demo3Thread3.start();? ? ? ? demo3Thread4.start();? ? ? ? demo3Thread5.start();? ? }}class Demo3Thread extends Thread{privateinti;public Demo3Thread(int i){this.i = i;? ? }@Overridepublic void run() {? ? ? ? System.out.println("i="+ i);? ? }}

實(shí)現(xiàn)Runnable接口

1)創(chuàng)建一個(gè)類,整個(gè)類需要實(shí)現(xiàn)Runnable接口

2)重寫Runnable接口的run方法

3)實(shí)例化創(chuàng)建的這個(gè)類

4)實(shí)例化一個(gè)Thread類,把第3步實(shí)例化創(chuàng)建的對(duì)象通過(guò)Thread類的構(gòu)造方法傳遞給Thread類

5)調(diào)用Thread類的run方法

packagecom.test;publicclass Demo4 {public static void main(String[] args) {? ? ? ? Demo4Thread thread =newDemo4Thread();? ? ? ? Thread t =newThread(thread);? ? ? ? t.start();? ? ? ? System.out.println("運(yùn)行了main方法");? ? }}class Demo4Thread implements Runnable{@Overridepublic void run() {? ? ? ? ? ? System.out.println("運(yùn)行了run方法");? ? }}

使用繼承Thread類的方式開(kāi)發(fā)多線程應(yīng)用程序是有局限的,因?yàn)镴ava是單繼承,繼承了Thread類就無(wú)法繼承其他類,所以為了改變這種局限,用實(shí)現(xiàn)Runnable接口的方式來(lái)實(shí)現(xiàn)多線程

成員變量與線程安全

自定義線程類中的成員變量對(duì)于其他線程可以是共享或者不共享的,這對(duì)于多線程的交互很重要

不共享數(shù)據(jù)時(shí)

packagecom.test;publicclass Demo5 {public static void main(String[] args) {? ? ? ? Thread t1 =newDemo5Thread();? ? ? ? Thread t2 =newDemo5Thread();? ? ? ? Thread t3 =newDemo5Thread();? ? ? ? t1.start();? ? ? ? t2.start();? ? ? ? t3.start();? ? ? ? }}class Demo5Thread extends Thread{privateinti =5;@Overridepublic void run() {while(i >0){? ? ? ? ? ? i--;? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +" i = "+ i);? ? ? ? }? ? }}

每個(gè)線程都有各自的i變量,i變量的值相互之間不影響

共享數(shù)據(jù)時(shí)

packagecom.test;publicclass Demo6 {public static void main(String[] args) {? ? ? ? Thread t =newDemo6Thread();/*

? 為什么能將Thread類的對(duì)象傳遞給Thread類?

? ? 因?yàn)門hread類本身就實(shí)現(xiàn)了Runnable接口

? ? ? ? */Thread thread1 =newThread(t);? ? ? ? Thread thread2 =newThread(t);? ? ? ? Thread thread3 =newThread(t);? ? ? ? Thread thread4 =newThread(t);? ? ? ? Thread thread5 =newThread(t);thread1.start();? ? ? ? thread2.start();? ? ? ? thread3.start();? ? ? ? thread4.start();? ? ? ? thread5.start();? }}class Demo6Thread extends Thread{privateinti =5;@Overridepublic void run() {? ? ? ? ? ? i--;? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +" i = "+ i);? ? ? ? ? ? }}

共享數(shù)據(jù)時(shí),將數(shù)據(jù)所在類的對(duì)象傳遞給多個(gè)Thread類即可

共享數(shù)據(jù)有概率出現(xiàn)不同線程產(chǎn)生相同的i的值,這就是非線程安全

線程常用API

currentThread方法

返回代碼被哪個(gè)線程調(diào)用的詳細(xì)信息

packagecom.test;publicclass Demo7 {public static void main(String[] args) {//main線程調(diào)用Demo7Thread的構(gòu)造方法Thread thread =newDemo7Thread();//Thread-0線程調(diào)用run方法thread.start();? ? ? ? System.out.println("main方法"+ Thread.currentThread().getName());? ? }}class Demo7Thread extends Thread{public Demo7Thread(){? ? ? ? System.out.println(Thread.currentThread().getName() +"的構(gòu)造方法");? ? }@Overridepublic void run() {? ? ? ? System.out.println(Thread.currentThread().getName() +"的run方法");? ? }}

輸出結(jié)果為:

main的構(gòu)造方法

main方法main

Thread-0的run方法

main方法會(huì)被名稱為main的線程調(diào)用,在新建線程類的對(duì)象時(shí),線程類的構(gòu)造方法會(huì)被main線程調(diào)用;

線程類對(duì)象的start方法會(huì)調(diào)用run方法,此時(shí)線程類默認(rèn)的名稱為Thread-0

isAlive方法

判斷當(dāng)前的線程是否處于活動(dòng)的狀態(tài),活動(dòng)狀態(tài)就是線程已經(jīng)啟動(dòng)并且沒(méi)有結(jié)束運(yùn)行的狀態(tài)

packagecom.test;publicclass Demo8 {publicstaticvoidmain(String[] args){Threadt =newDemo8Thread();System.out.println("線程啟動(dòng)前:"+ t.isAlive());? ? ? ? t.start();System.out.println("線程啟動(dòng)后:"+ t.isAlive());? ? }}class Demo8Thread extends Thread{@Overridepublicvoidrun(){System.out.println("run方法的運(yùn)行狀態(tài)"+this.isAlive());? ? }}

輸出結(jié)果為:

線程啟動(dòng)前:false

線程啟動(dòng)后:true

run方法的運(yùn)行狀態(tài)true

true表示線程正處于活動(dòng)狀態(tài),false則表示線程正處于非活動(dòng)狀態(tài)

sleep方法

使當(dāng)前正在執(zhí)行的線程在指定的毫秒數(shù)內(nèi)暫停執(zhí)行

packagecom.test;publicclass Demo8 {publicstaticvoidmain(String[] args){Threadt =newDemo8Thread();System.out.println("線程啟動(dòng)前時(shí)間:"+System.currentTimeMillis());? ? ? ? t.start();System.out.println("線程啟動(dòng)后時(shí)間:"+System.currentTimeMillis());? ? }}class Demo8Thread extends Thread{@Overridepublicvoidrun(){System.out.println("線程sleep前的時(shí)間:"+System.currentTimeMillis());try{Thread.sleep(300);? ? ? ? }catch(Exceptione){? ? ? ? ? ? e.printStackTrace();? ? ? ? }System.out.println("線程sleep后的時(shí)間:"+System.currentTimeMillis());? ? }}

getId方法

獲取當(dāng)前線程的唯一標(biāo)識(shí)

package com.test;publicclassDemo9{public static void main(String[] args){? ? ? ? Thread t = Thread.currentThread();? ? ? ? System.out.println(t.getName() +", "+ t.getId());? ? ? ? Thread thread =newThread();? ? ? ? System.out.println(thread.getName() +", "+ thread.getId());? ? }}

停止線程

停止一個(gè)線程,即線程在完成任務(wù)之前,就結(jié)束當(dāng)前正在執(zhí)行的操作

1)使用退出標(biāo)志,使線程正常停止,即run方法運(yùn)行完后線程終止

packagecom.test;publicclass Demo10 {public static void main(String[] args) {? ? ? ? Demo10Thread thread =newDemo10Thread();? ? ? ? thread.start();try{? ? ? ? ? ? Thread.sleep(2000);? ? ? ? }catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }/*

? ? ? ? stopThread方法,將flag變?yōu)閒alse,為什么能夠傳遞到當(dāng)前線程中?

? ? ? ? 我覺(jué)得是因?yàn)楫?dāng)前線程是一直在運(yùn)行的,while()中的條件一直成立

? ? ? ? 所以當(dāng)調(diào)用了stopThread方法,將flag變?yōu)閒alse,while循環(huán)就結(jié)束了,run方法中的代碼也結(jié)束了

? ? ? ? 所以線程停止了

? ? ? ? */thread.stopThread();? ? }}class Demo10Thread extends Thread{privateBoolean flag =true;@Overridepublic void run() {try{while(flag){? ? ? ? ? ? ? ? System.out.println("線程正在運(yùn)行");? ? ? ? ? ? ? ? Thread.sleep(1000);? ? ? ? ? ? }? ? ? ? ? ? System.out.println("線程結(jié)束運(yùn)行");? ? ? ? }catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }public void stopThread(){? ? ? ? flag =false;? ? }}

2)stop方法強(qiáng)制結(jié)束線程

packagecom.test;publicclass Demo11 {publicstaticvoidmain(String[] args){Demo11Threadthread =newDemo11Thread();? ? ? ? thread.start();try{Thread.sleep(2000);? ? ? ? }catch(Exceptione){? ? ? ? ? ? e.printStackTrace();? ? ? ? }//stop方法中的斜杠表示方法已經(jīng)被作廢,不建議使用此方法thread.stop();? ? }}class Demo11Thread extends Thread{privateBooleanflag =true;@Overridepublicvoidrun(){try{while(flag){System.out.println("線程正在運(yùn)行~~~");Thread.sleep(1000);? ? ? ? ? ? }System.out.println("線程結(jié)束運(yùn)行~~~");? ? ? ? }catch(Exceptione){? ? ? ? ? ? e.printStackTrace();? ? ? ? }catch(ThreadDeathe){//捕獲線程終止的異常System.out.println("進(jìn)入catch塊");? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }}

stop強(qiáng)制停止線程可能使一些清理性的工作得不到完成;還會(huì)對(duì)鎖定的對(duì)象進(jìn)行解鎖,使數(shù)據(jù)得不到同步的處理,導(dǎo)致數(shù)據(jù)不一致

3)interrupt方法中斷線程

packagecom.test;publicclass Demo12 {public static void main(String[] args) {? ? ? ? Demo12Thread thread =newDemo12Thread();? ? ? ? thread.start();? ? ? ? thread.interrupt();? ? ? ? System.out.println("thread線程是否已經(jīng)停止?"+ thread.isInterrupted() +", "+ thread.getName());? ? ? ? System.out.println("當(dāng)前線程是否已經(jīng)停止?"+ Thread.interrupted() +", "+ Thread.currentThread().getName());? ? }}class Demo12Thread extends Thread{@Overridepublic void run() {for(inti =0; i <5; i++){? ? ? ? ? ? System.out.println(i);? ? ? ? }? ? }}

調(diào)用interrupt方法不會(huì)真正的結(jié)束線程,而是給當(dāng)前線程打上一個(gè)停止的標(biāo)記

Thread類提供了interrupt方法測(cè)試當(dāng)前線程是否已經(jīng)中斷,isInterrupted方法測(cè)試線程是否已經(jīng)中斷

執(zhí)行結(jié)果為:

thread線程是否已經(jīng)停止?true,Thread-001234當(dāng)前線程是否已經(jīng)停止?false,main

thread.isInterrupted方法檢查線程類是否被打上停止的標(biāo)記,Thread.interrupted方法檢查主線程是否被打上停止的標(biāo)記

暫停線程

暫停線程使用suspend方法,重啟暫停線程使用resume方法

suspend方法暫停線程后,i的值就不會(huì)繼續(xù)增加。兩次"第一次suspend"輸出的結(jié)果一致

resume方法重啟暫停線程后,i的值會(huì)繼續(xù)增加,再使用suspend方法暫停線程,兩次"resume后第二次suspend:"輸出的結(jié)果一致

packagecom.test;publicclass Demo13 {public static void main(String[] args) throws InterruptedException {Demo13Thread thread =newDemo13Thread();thread.start();Thread.sleep(100);thread.suspend();System.out.println("第一次suspend:"+ thread.getI());? ? ? ? Thread.sleep(100);? ? ? ? System.out.println("第一次suspend:"+ thread.getI());? ? ? ? thread.resume();? ? ? ? Thread.sleep(100);? ? ? ? thread.suspend();? ? ? ? System.out.println("resume后第二次suspend:"+ thread.getI());? ? ? ? Thread.sleep(100);? ? ? ? System.out.println("resume后第二次suspend:"+ thread.getI());? ? }}class Demo13Thread extends Thread{privatelongi;public long getI() {returni;? ? }public void setI(long i) {this.i = i;? ? }@Overridepublic void run() {while(true){? ? ? ? ? ? i++;? ? ? ? }? ? }}

suspend方法會(huì)使線程獨(dú)占公共的同步對(duì)象,使其他線程無(wú)法訪問(wèn)公共的同步對(duì)象

suspend方法還可能會(huì)造成共享對(duì)象的數(shù)據(jù)不同步

yield方法

yield方法是使當(dāng)前線程放棄CPU資源,將資源讓給其他的線程,但是放棄的時(shí)間不確定,可能剛剛放棄,馬上又獲取CPU時(shí)間片

packagecom.test;publicclass Demo15 {public static void main(String[] args) {? ? Demo15Thread thread =newDemo15Thread();? ? thread.start();? ? }}class Demo15Thread extends Thread{@Overridepublic void run() {longstart = System.currentTimeMillis();intcount =0;for(inti =0; i <50000; i++){? ? ? ? ? ? Thread.yield();//使當(dāng)前線程放棄CPU資源,但是放棄的時(shí)間不確定count = count + i;? ? ? ? }longend = System.currentTimeMillis();? ? ? ? System.out.println("花費(fèi)時(shí)間:"+ (end - start));? ? }}

線程的優(yōu)先級(jí)

在操作系統(tǒng)中,線程是可以劃分優(yōu)先級(jí)的,優(yōu)先級(jí)較高的線程能夠得到更多的CPU資源,即CPU會(huì)優(yōu)先執(zhí)行優(yōu)先級(jí)較高的線程對(duì)象中的任務(wù)。設(shè)置線程優(yōu)先級(jí)有助于幫助"線程調(diào)度器"確定下一次選擇哪個(gè)線程優(yōu)先執(zhí)行

設(shè)置線程的優(yōu)先級(jí)使用setPriority方法,優(yōu)先級(jí)分為1~10級(jí),如果設(shè)置的優(yōu)先級(jí)小于1或者大于10,JVM會(huì)拋出IllegalArgumentException異常,JDK默認(rèn)設(shè)置了3個(gè)優(yōu)先級(jí)常量,MIN_PRIORITY=1(最小值),NORM_PRIORITY=5(中間值,也是默認(rèn)值),MAX_PRIORITY=10(最大值)

獲取線程的優(yōu)先級(jí)使用getPriority方法

packagecom.test;publicclass Demo16 {publicstaticvoidmain(String[] args){System.out.println("主線程的運(yùn)行優(yōu)先級(jí)是:"+Thread.currentThread().getPriority());System.out.println("設(shè)置主線程的運(yùn)行優(yōu)先級(jí)");Thread.currentThread().setPriority(8);System.out.println("主線程的運(yùn)行優(yōu)先級(jí)是:"+Thread.currentThread().getPriority());/*

? ? ? ? 線程的優(yōu)先級(jí)具有繼承性,本來(lái)默認(rèn)的線程的優(yōu)先級(jí)為5

? ? ? ? 但是將主線程的優(yōu)先級(jí)設(shè)置為8,此子線程也會(huì)繼承主線程的優(yōu)先級(jí)8

? ? ? ? */Threadt =newDemo16Thread();? ? ? ? t.start();? ? }}class Demo16Thread extends Thread{@Overridepublicvoidrun(){System.out.println("線程的優(yōu)先級(jí)是:"+this.getPriority());? ? }}

優(yōu)先級(jí)較高的線程,先執(zhí)行的概率較大

線程的同步機(jī)制

Java多線程中的同步,指的是如何在Java語(yǔ)言中開(kāi)發(fā)出線程安全的程序,或者如何在Java語(yǔ)言中解決線程不安全時(shí)所帶來(lái)的問(wèn)題

"線程安全"與"非線程安全"是多線程技術(shù)中的經(jīng)典問(wèn)題。"非線程安全"就是當(dāng)多個(gè)線程訪問(wèn)同一個(gè)對(duì)象的成員變量時(shí),讀取到的數(shù)據(jù)可能是被其他線程修改過(guò)的(臟讀)。"線程安全"就是獲取的成員變量的值是經(jīng)過(guò)同步處理的,不會(huì)有臟讀的現(xiàn)象

synchronized同步方法

局部變量是線程安全的

局部變量不存在線程安全的問(wèn)題,永遠(yuǎn)都是線程安全的,這是由局部變量是私有的特性造成的

packagecom.test.chap2;publicclass Demo1 {public static void main(String[] args) {? ? ? ? Service service =newService();? ? ? ? ThreadDemo1 t1 =newThreadDemo1(service);? ? ? ? t1.start();? ? ? ? ThreadDemo2 t2 =newThreadDemo2(service);? ? ? ? t2.start();? ? }}class Service {public void add(String name){intnumber =0;//number是方法內(nèi)的局部變量 if("a".equals(name)){? ? ? ? ? ? number =100;? ? ? ? ? ? System.out.println("傳入的參數(shù)為a,修改number的值為:"+ number);try{//這里使線程休眠是為了等待其他線程修改number的值Thread.sleep(1000);? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }? ? ? ? }else{? ? ? ? ? ? number =200;? ? ? ? ? ? System.out.println("傳入的參數(shù)不為a,修改number的值為:"+ number);? ? ? ? }? ? ? }}class ThreadDemo1 extends Thread{privateService service;public ThreadDemo1(Service service){this.service = service;? ? }@Overridepublic void run() {? ? ? ? service.add("a");? ? }}class ThreadDemo2 extends Thread{privateService service;public ThreadDemo2(Service service){this.service = service;? ? }@Overridepublic void run() {? ? ? ? service.add("b");? ? }}

成員變量不是線程安全的

如果有兩個(gè)線程,都要操作業(yè)務(wù)對(duì)象中的成員變量,可能會(huì)產(chǎn)生"非線程安全"的問(wèn)題,此時(shí)需要在方法前使用synchronized關(guān)鍵字進(jìn)行修飾

number是Demo2Service類的成員變量,Demo2Service類的add方法中,當(dāng)傳入的參數(shù)為a時(shí),會(huì)進(jìn)入if條件,休眠1s,并將number的值改為100,當(dāng)傳入的參數(shù)不為a時(shí),不會(huì)休眠,將number的值改為200

t3線程,傳入的參數(shù)為a;t4線程,傳入的參數(shù)為b,所以在線程start之后,t3線程會(huì)休眠1s,t4線程不會(huì)休眠,所以t4線程會(huì)先將number的值改為200并輸出,但是當(dāng)t3線程結(jié)束休眠后,輸出的number的值也是200,這就產(chǎn)生了線程安全的問(wèn)題

為了解決此線程不安全的問(wèn)題,可以在方法前,加上synchronized關(guān)鍵字進(jìn)行修飾,此時(shí)調(diào)用此方法的線程需要執(zhí)行完,方法才會(huì)被另一個(gè)線程所調(diào)用

packagecom.test.chap2;publicclass Demo2 {public static void main(String[] args) {? ? ? ? Demo2Service service =newDemo2Service();? ? ? ? ThreadDemo3 t3 =newThreadDemo3(service);? ? ? ? t3.start();? ? ? ? ThreadDemo4 t4 =newThreadDemo4(service);? ? ? ? t4.start();? ? }}class Demo2Service{privateintnumber =0;public void add(String name){if("a".equals(name)){? ? ? ? ? ? number =100;try{//這里使線程休眠是為了等待其他線程修改number的值Thread.sleep(1000);? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }? ? ? ? ? ? System.out.println("傳入的參數(shù)為a,修改number的值為:"+ number);? ? ? ? }else{? ? ? ? ? ? number =200;? ? ? ? ? ? System.out.println("傳入的參數(shù)不為a,修改number的值為:"+ number);? ? ? ? }? }}class ThreadDemo3 extends Thread{privateDemo2Service service =newDemo2Service();public ThreadDemo3(Demo2Service service){this.service = service;? ? }@Overridepublic void run() {? ? ? ? service.add("a");? ? }}class ThreadDemo4 extends Thread{privateDemo2Service service;public ThreadDemo4(Demo2Service service){this.service = service;? ? }@Overridepublic void run() {? ? ? ? service.add("b");? ? }}

多個(gè)對(duì)象使用多個(gè)對(duì)象鎖

synchronized設(shè)置的鎖都是對(duì)象鎖,而不是將代碼或者方法作為鎖

當(dāng)多個(gè)線程訪問(wèn)同一個(gè)對(duì)象時(shí),哪個(gè)線程先執(zhí)行此對(duì)象帶有synchronized關(guān)鍵字修飾的方法,其他線程就只能處于等待狀態(tài),直到此線程執(zhí)行完畢,釋放了對(duì)象鎖,其他線程才能繼續(xù)執(zhí)行

如果多個(gè)線程分別訪問(wèn)多個(gè)對(duì)象,JVM會(huì)創(chuàng)建出多個(gè)對(duì)象鎖,此時(shí)每個(gè)線程之間都不會(huì)互相干擾

鎖的自動(dòng)釋放

當(dāng)一個(gè)線程執(zhí)行的代碼出現(xiàn)了異常,其持有的鎖會(huì)自動(dòng)釋放

synchronized同步語(yǔ)句塊

synchronized關(guān)鍵字修飾的方法的不足之處

假如線程A和線程B都訪問(wèn)被synchronized關(guān)鍵字修飾的get方法,線程B就必須等線程A執(zhí)行完后,才能執(zhí)行,這樣運(yùn)行的效率低

synchronized同步代碼塊的使用

同步代碼塊的作用與在方法上添加synchronized關(guān)鍵字修飾的作用是一樣的

t1和t2兩個(gè)線程同時(shí)訪問(wèn)Demo10Service的synTest方法,synTest方法中部分代碼加上了同步代碼塊,從輸出結(jié)果可以發(fā)現(xiàn),t1和t2線程會(huì)同時(shí)訪問(wèn)synTest方法并同時(shí)執(zhí)行非同步代碼塊的邏輯,但是同步代碼塊的部分,t1線程先訪問(wèn)的話,t2線程就必須等到t1線程執(zhí)行完畢后,才能繼續(xù)執(zhí)行

假如在synTest方法上加上synchronized關(guān)鍵字修飾,t1線程先訪問(wèn)synTest方法的話,t2線程就必須等到t1線程執(zhí)行完畢后,才會(huì)訪問(wèn)synTest方法并執(zhí)行,總的來(lái)說(shuō),同步代碼塊可以鎖住部分需要同步執(zhí)行的代碼,而方法中沒(méi)有鎖住的其他代碼可以異步執(zhí)行

packagecom.test.chap2;publicclass Demo10 {public static void main(String[] args) throws InterruptedException {? ? ? ? Demo10Service service =newDemo10Service();? ? ? ? Thread t1 =newDemo10Thread(service);? ? ? ? t1.setName("A");? ? ? ? Thread t2 =newDemo10Thread(service);? ? ? ? t2.setName("B");? ? ? ? t1.start();? ? ? ? t2.start();? ? }}class Demo10Service{public void synTest(){? ? ? ? System.out.println(Thread.currentThread().getName() +"線程訪問(wèn)synTest方法");try{synchronized(this) {? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"線程開(kāi)始~~~");? ? ? ? ? ? ? ? Thread.sleep(2000);? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"線程結(jié)束~~~");? ? ? ? ? ? }? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }}class Demo10Thread extends Thread{? ? Demo10Service service;public Demo10Thread(Demo10Service service){this.service = service;? ? }@Overridepublic void run() {? ? ? ? service.synTest();? ? }}

volatile關(guān)鍵字

volatile關(guān)鍵字的主要作用是使變量在多個(gè)線程之間可見(jiàn)

當(dāng)線程啟動(dòng)后,如果flag變量前沒(méi)有volatile關(guān)鍵字修飾,線程會(huì)一直卡在run方法中的while循環(huán)中,修改flag的值不會(huì)生效,而加了volatile關(guān)鍵字修飾后,修改flag的值會(huì)生效,線程會(huì)退出while循環(huán)

在啟動(dòng)線程時(shí),flag變量存在于公共堆棧及線程的私有堆棧中。JVM為了線程的運(yùn)行效率,一直從私有堆棧中取flag的值,當(dāng)執(zhí)行service.flag = false語(yǔ)句時(shí),雖然修改了flag的值,但是修改的卻是公共堆棧的flag值,線程還是從私有堆棧中取flag的值,所以并不會(huì)退出while循環(huán)。使用volatile關(guān)鍵字修飾成員變量后,會(huì)強(qiáng)制JVM從公共堆棧中獲取變量的值,所以能夠退出while循環(huán)

packagecom.test.chap2;publicclass Demo {public static void main(String[] args) throws InterruptedException {? ? ? ? DemoService service =newDemoService();? ? ? ? Thread t1 =newThread(service);? ? ? ? t1.start();? ? ? ? Thread.sleep(100);? ? ? ? System.out.println("準(zhǔn)備修改flag的值");? ? ? ? service.flag =false;? ? ? ? System.out.println(service.flag);? ? }}class DemoService extends Thread{//沒(méi)有volatile關(guān)鍵字的話,線程會(huì)一致處于while循環(huán)中volatilepublicbooleanflag =true;@Overridepublic void run() {? ? ? ? System.out.println("開(kāi)始運(yùn)行run方法");while(flag){? ? ? ? }? ? ? ? System.out.println("結(jié)束運(yùn)行run方法");? ? }}

synchronized和volatile的區(qū)別:

1、volatile是線程同步的輕量級(jí)實(shí)現(xiàn),所以volatile的性能要比synchronized要好,但是volatile只能修飾變量。而synchronized可以修飾方法以及代碼塊。隨著JDK的版本更新,synchronized在執(zhí)行效率上也有很大的提升,使用率還是較高

2、多線程訪問(wèn)volatile不會(huì)阻塞,而訪問(wèn)synchronized會(huì)出現(xiàn)阻塞

3、volatile能保證數(shù)據(jù)的可見(jiàn)性,但是不能保證原子性,可能會(huì)出現(xiàn)臟讀;而synchronized能夠保證原子性,也能間接保證可見(jiàn)性,因?yàn)槠淠軐⑺接袃?nèi)存和公共內(nèi)存中的數(shù)據(jù)做同步

4、volatile解決的是變量在多個(gè)線程之間的可見(jiàn)性,而synchronized解決的是多個(gè)線程之間訪問(wèn)資源的同步性

最后

大家看完有什么不懂的可以在下方留言討論,也可以關(guān)注我私信問(wèn)我,我看到后都會(huì)回答的。。謝謝你的觀看,覺(jué)得文章對(duì)你有幫助的話記得關(guān)注我點(diǎn)個(gè)贊支持一下!


來(lái)自:https://mp.weixin.qq.com/s/le8My8lmRMV_8rn7BTmKPA

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,241評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,939評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評(píng)論 6 409
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,182評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,406評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,638評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,866評(píng)論 1 285
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評(píng)論 2 373