前言
進(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