一篇文章帶你入門(mén)Java多線(xiàn)程
進(jìn)程
1、進(jìn)程是指運(yùn)行中的程序,比如我們使用qq,就啟動(dòng)了一個(gè)進(jìn)程,操作系統(tǒng)就會(huì)為該進(jìn)程分配內(nèi)存空間。當(dāng)我們使用迅雷,又啟動(dòng)了一個(gè)進(jìn)程,操作系統(tǒng)將為迅雷分配新的內(nèi)存空間
2、進(jìn)程是程序的一次執(zhí)行過(guò)程,或是正在運(yùn)行的一個(gè)程序。是動(dòng)態(tài)過(guò)程:有它自身的產(chǎn)生、存在和消亡的過(guò)程
其他相關(guān)概念
1、單線(xiàn)程:同一個(gè)時(shí)刻,只允許執(zhí)行一個(gè)線(xiàn)程
2、多線(xiàn)程:同一時(shí)刻,可以執(zhí)行多個(gè)線(xiàn)程,比如:一個(gè)qq進(jìn)程,可以同時(shí)打開(kāi)多個(gè)聊天窗口,一個(gè)迅雷進(jìn)程,可以同時(shí)下載多個(gè)文件
3、并發(fā):同一時(shí)刻,多個(gè)任務(wù)交替執(zhí)行,造成一種“貌似同時(shí)”的錯(cuò)覺(jué),簡(jiǎn)單的說(shuō),單核cpu實(shí)現(xiàn)的多任務(wù)就是并發(fā)
4、并行:同一時(shí)刻,多個(gè)任務(wù)同時(shí)進(jìn)行。多核cpu可以實(shí)現(xiàn)并行。并發(fā)和并行:如果開(kāi)的程序太多,有可能也會(huì)觸發(fā)并發(fā)
創(chuàng)建線(xiàn)程的兩種方式
1、繼承Thread類(lèi),重寫(xiě)run方法
實(shí)例:
//該線(xiàn)程每隔1秒鐘。在控制臺(tái)輸出"喵喵",打印8次后結(jié)束線(xiàn)程
public class Thread01 {
public static void main(String[] args) {
//創(chuàng)建一個(gè)cat對(duì)象,可以當(dāng)作線(xiàn)程使用
Cat cat=new Cat();
cat.start();//啟動(dòng)線(xiàn)程
}
}
//1、當(dāng)一個(gè)類(lèi)繼承了 Thread 類(lèi) ,該類(lèi)就可以當(dāng)作線(xiàn)程使用
//2、我們會(huì)重寫(xiě)run方法,寫(xiě)上自己的業(yè)務(wù)代碼
//3、run Thread 類(lèi) 實(shí)現(xiàn)了 Runnable 接口的run方法
class Cat extends Thread{
int times=0;
@Override
public void run() { //重寫(xiě)run方法,寫(xiě)上自己的業(yè)務(wù)邏輯
while(true) {
System.out.println("喵喵"+ ++times);
//讓該線(xiàn)程休眠1秒鐘
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times==8){//設(shè)置打印次數(shù)
break;
}
}
}
}
為什么使用start()方法而不直接使用run()方法
因?yàn)閞un()方法就是一個(gè)普通的方法,沒(méi)有真正的啟動(dòng)一個(gè)線(xiàn)程,就會(huì)把run方法執(zhí)行完畢,才向下執(zhí)行
start()方法底層
(1)
public synchronized void start() {
start0();
}
//start0();是start中最主要的方法
(2)
//start0(); 是本地方法,是JVM調(diào)用,底層是C/C++實(shí)現(xiàn)
//真正實(shí)現(xiàn)多線(xiàn)程的效果,是start0(),而不是run,也可以說(shuō)在start0()本地方法中去調(diào)用了Run()方法

2、實(shí)現(xiàn)Runnable接口,重寫(xiě)run方法
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1=new Thread(t1);
Thread thread2=new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable{
int count=0;
@Override
public void run() {
while (true) {
//每隔1秒輸出"hello,world",輸出10次
System.out.println("hello,world " + ++count + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count==50){
break;
}
}
}
}
class T2 implements Runnable{
int count=0;
@Override
public void run() {
while (true) {
//每隔1秒輸出"hello,world",輸出10次
System.out.println("hi " + ++count + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count==60){
break;
}
}
}
繼承Thread 和 實(shí)現(xiàn)Rnnable的區(qū)別
1、從Java的設(shè)計(jì)來(lái)看,通過(guò)繼承Thread或者實(shí)現(xiàn)Runnable接口來(lái)創(chuàng)建線(xiàn)程本質(zhì)上沒(méi)有區(qū)別,從jdk幫助文檔我們可以看到Thread類(lèi)本身就實(shí)現(xiàn)了Runnable接口
2、實(shí)現(xiàn)Runnable接口方式更加適合多個(gè)線(xiàn)程共享一個(gè)資源的情況,并且避免了單繼承的限制,建議使用Runnable接口
售票系統(tǒng)
SellTicket01類(lèi)繼承Thread實(shí)現(xiàn)
class SellTicket01 extends Thread{
private static int ticketNum=100; //讓多個(gè)線(xiàn)程共享num
@Override
public void run() {
while(true) {
if (ticketNum <= 0) {
System.out.println("售票結(jié)束");
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一張票" + "剩余票數(shù)=" + --ticketNum);
}
}
}
//====================main方法===========================
public static void main(String[] args) {
//測(cè)試
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
//這里會(huì)出現(xiàn)票數(shù)超賣(mài)現(xiàn)象
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
}
SellTicket02類(lèi)實(shí)現(xiàn)Runnable接口
class SellTicket02 implements Runnable{
private int ticketNum=99;
@Override
public void run() {
while(true) {
if (ticketNum <= 0) {
System.out.println("售票結(jié)束");
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一張票" + "剩余票數(shù)=" + --ticketNum);
}
}
}
//=================main================
public static void main(String[] args) {
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第一個(gè)線(xiàn)程-窗口
new Thread(sellTicket02).start();//第二個(gè)線(xiàn)程-窗口
new Thread(sellTicket02).start();//第三個(gè)線(xiàn)程-窗口
}
兩個(gè)方法都會(huì)有超票的現(xiàn)象,線(xiàn)程安全的問(wèn)題
線(xiàn)程終止
基本說(shuō)明
1、當(dāng)線(xiàn)程完成任務(wù)后,會(huì)自動(dòng)退出
2、還可以通過(guò)使用變量來(lái)控制run方法退出的方式停止線(xiàn)程,即通知方式
通知方式
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
//如果希望主線(xiàn)程去控制t中線(xiàn)程的終止,需要能夠控制loop
//修改loop,讓t退出run方法,從而終止t線(xiàn)程-->通知方式
//讓主線(xiàn)程休眠10秒,在通知t線(xiàn)程退出
Thread.sleep(10000);
t.setLoop(false);//將T線(xiàn)程中的循環(huán)判斷為false
}
}
class T extends Thread{
private int count=0;
private boolean loop=true;
@Override
public void run() {
while (loop){
try {
Thread.sleep(1000); //休眠50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T線(xiàn)程執(zhí)行"+ ++count);
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
線(xiàn)程常用方法
常用第一組
1、setName:設(shè)置線(xiàn)程名稱(chēng),使之與參數(shù)name相同
2、getName:返回該線(xiàn)程的名稱(chēng)
3、start:該線(xiàn)程開(kāi)始執(zhí)行;java虛擬機(jī)底層調(diào)用該線(xiàn)程的start()方法
4、run:調(diào)用線(xiàn)程對(duì)象run方法
5、setPriority:更改線(xiàn)程的優(yōu)先級(jí)
6、getPriority:獲取線(xiàn)程的優(yōu)先級(jí)
7、sleep:在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線(xiàn)程休眠(暫停執(zhí)行)
8、interrupt:中斷線(xiàn)程
注意事項(xiàng)和細(xì)節(jié)
1、start底層會(huì)創(chuàng)建新的線(xiàn)程,調(diào)用run,run就是一個(gè)簡(jiǎn)單的方法調(diào)用,不會(huì)啟動(dòng)新線(xiàn)程
2、線(xiàn)程優(yōu)先級(jí)的范圍
3、interrupt,中斷線(xiàn)程,但沒(méi)有真正的結(jié)束線(xiàn)程,所以一般用于中斷正在休眠線(xiàn)程
4、sleep:線(xiàn)程的靜態(tài)方法,使當(dāng)前線(xiàn)程休眠
常用方法第二組
1、yield:線(xiàn)程的禮讓。讓出cpu,讓其他線(xiàn)程執(zhí)行,但禮讓的時(shí)間不確定,所以也不一定禮讓成功
2、join:線(xiàn)程的插隊(duì)。插隊(duì)的線(xiàn)程一旦插隊(duì)成功,則肯定先執(zhí)行完插入的線(xiàn)程所有的任務(wù)
案例
創(chuàng)建一個(gè)子線(xiàn)程,每隔1s輸出hello,輸出20次,主線(xiàn)程每隔1s,輸出hi,輸出20次。要求:兩個(gè)線(xiàn)程同時(shí)執(zhí)行,當(dāng)主線(xiàn)程輸出5次后,就讓子線(xiàn)程運(yùn)行完畢,主線(xiàn)程再繼續(xù)
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i = 1;i<=20;i++){
Thread.sleep(1000);
System.out.println("主線(xiàn)程(小弟)吃了"+i+"個(gè)包子");
if (i==5){
System.out.println("主線(xiàn)程(小弟)讓子線(xiàn)程(老大)先吃");
//yield 禮讓
t2.yield();
//線(xiàn)程插隊(duì),join
// t2.join();
System.out.println("子線(xiàn)程(老大)吃完,主線(xiàn)程(小弟)再吃");
}
}
}
}
class T2 extends Thread{
@Override
public void run() {
for (int i=1;i<=20;i++){
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線(xiàn)程(老大)吃了"+i+"個(gè)包子");
}
}
}
插隊(duì)的話(huà)是百分百成功的,但是禮讓如果資源過(guò)剩的話(huà),禮讓會(huì)不成功,例如上面資源不是特別缺乏,所以禮讓會(huì)不成功
常用方法第三組
用戶(hù)線(xiàn)程和守護(hù)線(xiàn)程
1、用戶(hù)線(xiàn)程:也叫工作線(xiàn)程,當(dāng)線(xiàn)程的任務(wù)執(zhí)行完或通知方式結(jié)束
2、守護(hù)線(xiàn)程:一般是為工作線(xiàn)程服務(wù)的,當(dāng)所有的用戶(hù)線(xiàn)程結(jié)束,守護(hù)線(xiàn)程自動(dòng)結(jié)束
3、常見(jiàn)的守護(hù)線(xiàn)程:垃圾回收機(jī)制
自定義守護(hù)線(xiàn)程
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我們希望當(dāng)main線(xiàn)程結(jié)束后,子線(xiàn)程自動(dòng)結(jié)束
//只需將子線(xiàn)程設(shè)為守護(hù)線(xiàn)程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i =1;i<=10;i++){
System.out.println("媽媽做飯");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
@Override
public void run() {
for (;;){ //等價(jià)于無(wú)限循環(huán)
try {
Thread.sleep(50); //休眠50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我吃飯。。。");
}
}
}
MyDaemonThread類(lèi)的進(jìn)程會(huì)在主線(xiàn)程進(jìn)程結(jié)束后相繼結(jié)束
線(xiàn)程的生命周期
線(xiàn)程狀態(tài):線(xiàn)程可以處于一下?tīng)顟B(tài)之一:
NEW
尚未啟動(dòng)的線(xiàn)程處于此狀態(tài)
RUNNABLE
在Java虛擬機(jī)中執(zhí)行的線(xiàn)程處于此狀態(tài)
BLOCKED
被阻塞等待監(jiān)視器鎖定的線(xiàn)程處于此狀態(tài)
WAITING
正在等待另一個(gè)線(xiàn)程執(zhí)行特定動(dòng)作的線(xiàn)程處于此狀態(tài)
TIMED_WAITING
正在等待另一個(gè)線(xiàn)程執(zhí)行動(dòng)作達(dá)到指定等待時(shí)間的線(xiàn)程處于此狀態(tài)
TERMINATED
已退出的線(xiàn)程處于此狀態(tài)
RUNNABLE又可分為兩個(gè)狀態(tài):Ready狀態(tài):就緒狀態(tài) 和 Running運(yùn)行狀態(tài)
線(xiàn)程同步機(jī)制
1、在多線(xiàn)程編程,一些敏感數(shù)據(jù)不允許被多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn),此時(shí)就是用同步訪(fǎng)問(wèn)技術(shù),保證數(shù)據(jù)在任何時(shí)刻,最多有一個(gè)線(xiàn)程訪(fǎng)問(wèn),以保證數(shù)據(jù)的完整性。
2、也可以這樣理解:線(xiàn)程同步,即當(dāng)有一個(gè)線(xiàn)程在對(duì)內(nèi)存進(jìn)行操作時(shí),其他線(xiàn)程都不可以對(duì)這個(gè)內(nèi)存地址進(jìn)行操作,直到線(xiàn)程完成操作,其他線(xiàn)程才能對(duì)該內(nèi)存地址進(jìn)行操作。
利用同步解決買(mǎi)票超賣(mài)問(wèn)題
public class SellTicket {
public static void main(String[] args) {
//測(cè)試同步解決超賣(mài)現(xiàn)象
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第一個(gè)線(xiàn)程-窗口
new Thread(sellTicket03).start();//第二個(gè)線(xiàn)程-窗口
new Thread(sellTicket03).start();//第三個(gè)線(xiàn)程-窗口
}
}
//實(shí)現(xiàn)接口的方式,使用synchronized實(shí)現(xiàn)線(xiàn)程同步
class SellTicket03 implements Runnable{
private boolean loop=true;
private int ticketNum=99;
public synchronized void sell(){
if (ticketNum <= 0) {
System.out.println("售票結(jié)束");
loop=false;
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一張票" + "剩余票數(shù)=" + --ticketNum);
}
@Override
public void run() { //在同一時(shí)刻只能有一個(gè)線(xiàn)程來(lái)執(zhí)行sell方法
while(loop) {
sell(); //sell方法是一個(gè)同步方法
}
}
}
synchronized關(guān)鍵字為鎖的意思,如果有線(xiàn)程去調(diào)用了synchronized關(guān)鍵字修飾的方法,則不會(huì)去再有線(xiàn)程調(diào)用
synchronized的使用方法
- 修飾一個(gè)代碼塊,被修飾的代碼塊稱(chēng)為同步代碼塊,作用范圍是大括號(hào){}括起來(lái)的代碼;
- 修飾一個(gè)方法,被修飾的方法稱(chēng)為同步方法,其作用范圍是整個(gè)方法;
- 修改一個(gè)靜態(tài)方法,作用范圍是整個(gè)靜態(tài)方法;
- 修改一個(gè)類(lèi),作用范圍是synchronized后面括號(hào)括起來(lái)的部分。
互斥鎖
基本介紹
1、Java語(yǔ)言中,引入了對(duì)象互斥鎖的概念,來(lái)保證共享數(shù)據(jù)操作的完整性。
2、每個(gè)對(duì)象都對(duì)應(yīng)一個(gè)可稱(chēng)為互斥鎖的標(biāo)記,這個(gè)標(biāo)記用來(lái)保證在任一時(shí)刻,只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn)該對(duì)象
3、關(guān)鍵字synchronized來(lái)與對(duì)象的互斥鎖聯(lián)系。當(dāng)某個(gè)對(duì)象用synchronized修飾時(shí),表明該對(duì)象在任一時(shí)刻只能由一個(gè)線(xiàn)程訪(fǎng)問(wèn)
4、同步的局限性:導(dǎo)致程序的執(zhí)行效率要降低
5、同步方法(非靜態(tài)的)的鎖可以是this,也可以是其他對(duì)象(要求是同一對(duì)象)
6、同步方法(靜態(tài)的)的鎖為當(dāng)前類(lèi)本身
同步方法靜態(tài)與非靜態(tài)實(shí)例
//實(shí)現(xiàn)接口的方式,使用synchronized實(shí)現(xiàn)線(xiàn)程同步
class SellTicket03 implements Runnable{
private boolean loop=true;
private int ticketNum=99;
Object object=new Object();
//同步方法(靜態(tài)的)的鎖為當(dāng)前類(lèi)本身
//1.public synchronized static void m1(){} 鎖是加在SellTicket03.class 類(lèi)本身
//2.如果要在靜態(tài)方法中,實(shí)現(xiàn)一個(gè)同步代碼塊
//3.synchronized中的參數(shù)不能為this,要為類(lèi)的class 類(lèi)如:
/*public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}*/
public synchronized static void m1(){
}
public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}
//1、 public synchronized void sell(){}這是一個(gè)同步方法
//2、這時(shí)鎖在this對(duì)象
//3、也可以在代碼塊上寫(xiě)synchronize , 同步代碼塊
public /*synchronized*/ void sell() {
synchronized (object) {
if (ticketNum <= 0) {
System.out.println("售票結(jié)束");
loop = false;
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一張票" + "剩余票數(shù)=" + --ticketNum);
}
}
@Override
public void run() { //在同一時(shí)刻只能有一個(gè)線(xiàn)程來(lái)執(zhí)行sell方法
while(loop) {
sell(); //sell方法是一個(gè)同步方法
}
}
}
注意事項(xiàng)和細(xì)節(jié)
1、同步方法如果沒(méi)有使用static修飾:默認(rèn)鎖對(duì)象為this
2、如果方法使用static修飾,默認(rèn)鎖對(duì)象:當(dāng)前類(lèi).class
3、實(shí)現(xiàn)的落地步驟
- 需要先分析上鎖的代碼
- 選擇同步代碼塊或同步方法
- 要求多個(gè)線(xiàn)程的鎖對(duì)象為同一個(gè)即可!
線(xiàn)程死鎖
public class DeadLock_ {
public static void main(String[] args) {
//模擬一個(gè)死鎖現(xiàn)象
DeadLockDemo A=new DeadLockDemo(true);
DeadLockDemo B=new DeadLockDemo(false);
deadLockDemo1.start();
deadLockDemo2.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object(); //保證多線(xiàn)程,共享一個(gè)對(duì)象,這里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag){ //構(gòu)造器
this.flag = flag;
}
@Override
public void run() {
//下面的業(yè)務(wù)邏輯的分析
//1.如果flag為T(mén) , 線(xiàn)程就會(huì)先得到/持有 o1 對(duì)象鎖 , 然后嘗試去獲得 o2對(duì)象鎖
//2.如果線(xiàn)程A 得不到o2對(duì)象鎖,就會(huì)Blocked
//3.如果flag為F,線(xiàn)程B就會(huì)先得到/持有 o2 對(duì)象鎖,然后嘗試去獲取 o1 對(duì)象鎖
//4.如果線(xiàn)程B 得不到 o1 對(duì)象鎖,就會(huì)Blocked
if (flag){
synchronized (o1){ //對(duì)象互斥鎖,下面就是我們同步代碼
System.out.println(Thread.currentThread().getName() + "進(jìn)入1");
synchronized (o2){ //這里獲得li對(duì)象的監(jiān)視權(quán)
System.out.println(Thread.currentThread().getName()+"進(jìn)入2");
}
}
}else {
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"進(jìn)入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"進(jìn)入4");
}
}
}
}
}
因?yàn)榫€(xiàn)程A會(huì)去搶線(xiàn)程B占著的對(duì)象,線(xiàn)程B也會(huì)去搶線(xiàn)程A占著的對(duì)象,所以會(huì)出現(xiàn)線(xiàn)程鎖死的現(xiàn)象,寫(xiě)代碼的時(shí)候要避免這個(gè)錯(cuò)誤
釋放鎖
下面操作會(huì)釋放鎖
1、當(dāng)前線(xiàn)程的同步方法、同步代碼塊執(zhí)行結(jié)束
案例:上廁所,完事出來(lái)
2、當(dāng)前線(xiàn)程在同步代碼塊、同步方法中遇到break、return
案例:沒(méi)有正常的完事,經(jīng)理叫你去修改bug,不得已出來(lái)
3、當(dāng)前線(xiàn)程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或Exception,導(dǎo)致異常結(jié)束
案例:沒(méi)有正常的完事,發(fā)現(xiàn)忘記帶紙,不得已出來(lái)
4、當(dāng)前線(xiàn)程在同步代碼塊、同步方法中執(zhí)行了線(xiàn)程對(duì)象的wait()方法,當(dāng)前線(xiàn)程暫停,并釋放鎖。
案例:沒(méi)有正常完事,覺(jué)得需要醞釀下,所以出來(lái)等會(huì)在進(jìn)去
下面操作不會(huì)釋放鎖
1、線(xiàn)程執(zhí)行同步代碼塊或同步方法時(shí),程序調(diào)用了Thread.sleep()、Thread.yield()方法暫停當(dāng)前線(xiàn)程的執(zhí)行,不會(huì)釋放鎖
案例:上廁所,太困了,在坑位上瞇了一會(huì)
2、線(xiàn)程執(zhí)行同步代碼塊時(shí),其他線(xiàn)程調(diào)用了該線(xiàn)程的suspend()方法將該線(xiàn)程掛起,該線(xiàn)程不會(huì)釋放鎖
提示:應(yīng)盡量避免使用suspend()和resume()來(lái)控制線(xiàn)程,方法不再推薦使用
練習(xí)題
一、
(1)在main方法中啟動(dòng)兩個(gè)線(xiàn)程
(2)第一個(gè)線(xiàn)程循環(huán)隨機(jī)打印100以?xún)?nèi)的整數(shù)
(3)直到第二個(gè)線(xiàn)程從鍵盤(pán)上讀取了"Q"命令
通過(guò)線(xiàn)程守護(hù)解決
public class homeWork01 {
public static void main(String[] args) {
//創(chuàng)建線(xiàn)程B,并運(yùn)行
B b=new B();
b.start();
}
}
class A extends Thread{
@Override
public void run() {
while (true){
try {
//休眠1秒運(yùn)行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印隨機(jī)數(shù)
int num = (int)(Math.random()*100);
System.out.println(num);
}
}
}
class B extends Thread{
@Override
public void run() {
//創(chuàng)建A線(xiàn)程對(duì)象,并創(chuàng)建守護(hù)線(xiàn)程
A a=new A();
a.setDaemon(true);
a.start();
while (true) {
//當(dāng)輸入Q的時(shí)候B線(xiàn)程結(jié)束,因?yàn)槭鞘刈o(hù)線(xiàn)程,所以線(xiàn)程A也會(huì)跟著結(jié)束
System.out.println("請(qǐng)輸入你的指令");
Scanner sc = new Scanner(System.in);
String Z = sc.next();
System.out.println(Z);
if (Z.equals("Q")) {
System.out.println("B線(xiàn)程結(jié)束");
break;
}
}
}
}
通過(guò)通知方式解決
public class homeWork01 {
public static void main(String[] args) {
//創(chuàng)建線(xiàn)程A、B,并且執(zhí)行線(xiàn)程A、B
A a= new A();
B b=new B(a);
a.start();
b.start();
}
}
class A extends Thread{
private boolean loop=true;
//創(chuàng)建setLoop用來(lái)通知
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
while (loop){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int num = (int)(Math.random()*100);
System.out.println(num);
}
}
}
class B extends Thread{
A a;
//通過(guò)B的構(gòu)造器,傳入main中的線(xiàn)程A
public B(A a){
this.a=a;
}
@Override
public void run() {
while (true) {
System.out.println("請(qǐng)輸入你的指令");
Scanner sc = new Scanner(System.in);
String Z = sc.next();
System.out.println(Z);
if (Z.equals("Q")) {
//通過(guò)setLoop提醒線(xiàn)程A結(jié)束
a.setLoop(false);
break;
}
}
}
}
二、
(1)有兩個(gè)用戶(hù)分別從同一個(gè)卡上取錢(qián)(總額:10000)
(2)每次都取1000,當(dāng)余額不足時(shí),就不能取款了
(3)不能出現(xiàn)超取現(xiàn)象 —>線(xiàn)程同步問(wèn)題
同步方法
public class homeWork02 {
public static void main(String[] args) {
C c=new C();
//將兩個(gè)線(xiàn)程運(yùn)行
new Thread(c).start();
new Thread(c).start();
}
}
class C implements Runnable{
private static boolean loop=true;
private static int money=10000;
@Override
public void run() {
while (loop){
//讓兩個(gè)線(xiàn)程去搶同步方法
quMoney();
}
}
public synchronized void quMoney(){
if (money<=0){
System.out.println("余額不足,線(xiàn)程退出"+Thread.currentThread().getName());
loop=false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
money=money-1000;
System.out.println(Thread.currentThread().getName()+"從余額中取到了1000元還剩"+money+"元");
}
}
通過(guò)創(chuàng)建同步方法,避免超取現(xiàn)象
同步代碼塊
public class homeWork03 {
public static void main(String[] args) {
T t=new T();
Thread thread=new Thread(t);
Thread thread1=new Thread(t);
thread.setName("t1");
thread1.setName("t2");
thread.start();
thread1.start();
}
}
//編寫(xiě)取款的線(xiàn)程
//因?yàn)檫@里涉及到多個(gè)程序共享資源,所以我們使用實(shí)現(xiàn)Runnable方式
class T implements Runnable{
private int money=10000;
@Override
public void run() {
while (true){
//解讀
//1.這里使用 synchronized 實(shí)現(xiàn)了線(xiàn)程同步
//2.當(dāng)多個(gè)線(xiàn)程執(zhí)行到這里時(shí),就會(huì)去爭(zhēng)奪 this 對(duì)象鎖
//3.那個(gè)線(xiàn)程獲取到了this鎖,就執(zhí)行 synchronized 代碼塊,執(zhí)行完后,會(huì)釋放this對(duì)象鎖
//4.獲取不到this對(duì)象鎖,就會(huì)blocked(阻塞),準(zhǔn)備繼續(xù)爭(zhēng)奪
synchronized (this) {
if (money < 1000) {
System.out.println("余額不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + "取出了1000 當(dāng)前余額" + money);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ss T implements Runnable{
private int money=10000;
@Override
public void run() {
while (true){
//解讀
//1.這里使用 synchronized 實(shí)現(xiàn)了線(xiàn)程同步
//2.當(dāng)多個(gè)線(xiàn)程執(zhí)行到這里時(shí),就會(huì)去爭(zhēng)奪 this 對(duì)象鎖
//3.那個(gè)線(xiàn)程獲取到了this鎖,就執(zhí)行 synchronized 代碼塊,執(zhí)行完后,會(huì)釋放this對(duì)象鎖
//4.獲取不到this對(duì)象鎖,就會(huì)blocked(阻塞),準(zhǔn)備繼續(xù)爭(zhēng)奪
synchronized (this) {
if (money < 1000) {
System.out.println("余額不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + "取出了1000 當(dāng)前余額" + money);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
HttpClient實(shí)現(xiàn)表單提交上傳文件
這篇文章主要為大家詳細(xì)介紹了HttpClient實(shí)現(xiàn)表單提交上傳文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(41)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07
logback?OutputStreamAppender高效日志輸出源碼解析
這篇文章主要介紹了為大家logback?OutputStreamAppender日志輸出效率提升示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
SpringBoot+Redisson自定義注解一次解決重復(fù)提交問(wèn)題
項(xiàng)目中經(jīng)常會(huì)出現(xiàn)重復(fù)提交的問(wèn)題,本文主要介紹了SpringBoot+Redisson自定義注解一次解決重復(fù)提交問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
java中json和對(duì)象之間相互轉(zhuǎn)換的運(yùn)用
本文主要介紹了java中json和對(duì)象之間相互轉(zhuǎn)換的運(yùn)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
MyBatisPlus?大數(shù)據(jù)量查詢(xún)慢的問(wèn)題解決
本文主要介紹了MyBatis?Plus?解決大數(shù)據(jù)量查詢(xún)慢問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
阿里Sentinel支持Spring Cloud Gateway的實(shí)現(xiàn)
這篇文章主要介紹了阿里Sentinel支持Spring Cloud Gateway的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04
Java技巧函數(shù)方法實(shí)現(xiàn)二維數(shù)組遍歷
這篇文章主要介紹了Java技巧函數(shù)方法實(shí)現(xiàn)二維數(shù)組遍歷,二維數(shù)組遍歷,每個(gè)元素判斷下是否為偶數(shù),相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-08-08

