多線程是多個(gè)線程同時(shí)對同一個(gè)數(shù)據(jù)執(zhí)行相同或不同的任務(wù)。要注意的是囤躁,這樣的操作很大可能會出現(xiàn)線程安全問題冀痕,和線程死鎖問題。
現(xiàn)介紹一下為什么要用同步線程:
public class MyThread implements Runnable {
private int ticket=10;
@Override
public void run() {
for(int i=0;i<10;i++){
if(this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售倒數(shù)第:"+this.ticket--+"張票");
}
}
}
}
單多個(gè)線程訪問同一個(gè)對象是狸演,會出現(xiàn)其中一條線程執(zhí)行業(yè)務(wù)代碼的時(shí)候言蛇,CPU的執(zhí)行權(quán)被另一條線程搶走了,通過上面代碼可以明顯看出輸出了負(fù)數(shù)宵距,這和我們現(xiàn)實(shí)生活是完全不存在的腊尚。這就是線程安全問題
這使得我們需要通過靜態(tài)化ticket,或者是用同步線程來實(shí)現(xiàn)我們要的功能满哪。
下面先介紹一下怎么解決線程安全問題的實(shí)現(xiàn)婿斥;
方式一:可以使用同步代碼塊來實(shí)現(xiàn)。
示例:
public class MyThread implements Runnable {
private int ticket=10;
@Override
public synchronized void run() {
for(int i=0;i<10;i++){
if(this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售倒數(shù)第:"+this.ticket--+"張票");
}
}
}
}
示例二:使用同步代碼塊來實(shí)現(xiàn)
public class MyThread implements Runnable {
private int ticket=10;
@Override
public void run() {
for(int i=0;i<10;i++){
synchronized ("鎖"){
if(this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售倒數(shù)第:"+this.ticket--+"張票");
}
}
}
}
}
上面兩種方式都能實(shí)現(xiàn)代碼的同步翩瓜,
線程之所以能同步受扳,是因?yàn)榧恿艘粋€(gè)鎖對象,在使用同步函數(shù)的時(shí)候兔跌,鎖對象是對象本身this勘高,而在使用同步代碼塊的時(shí)候,鎖對象是自己定義的坟桅,可以是任何對象华望。
建議:要是用同步線程的時(shí)候,建議優(yōu)先使用同步代碼塊仅乓,因?yàn)橥酱a塊鎖對象是自己定義的赖舟,方便對鎖進(jìn)行操作。這個(gè)原因下面將要說到夸楣。
通過上面的代碼會發(fā)現(xiàn)宾抓,我在方法中使用了sleep方法子漩。sleep()方法是Thread類中的一個(gè)方法,為什么沒有把它放到上一節(jié)將石洗,是因?yàn)檫@個(gè)方法有點(diǎn)特殊幢泼。這個(gè)涉及到線程的各種狀態(tài):
線程開始的時(shí)候是創(chuàng)建狀態(tài):(new 對象的時(shí)候.)
當(dāng)調(diào)用了start方法的時(shí)候,線程處于可以運(yùn)行狀態(tài)讲衫,在這個(gè)狀態(tài)下的線程具備CPU的等待權(quán)缕棵,但是不具備CPU的執(zhí)行權(quán)。
當(dāng)線程執(zhí)行的時(shí)候他具備了CPU的執(zhí)行權(quán)涉兽。這個(gè)時(shí)候稱為運(yùn)行狀態(tài)招驴。
當(dāng)完成任務(wù)代碼塊的時(shí)候,線程死亡枷畏。
在可運(yùn)行狀態(tài)和運(yùn)行狀態(tài)之間還有一個(gè)狀態(tài)别厘,這個(gè)狀態(tài)稱為臨時(shí)阻塞狀態(tài)。進(jìn)入這個(gè)狀態(tài)需要調(diào)用sleep()或wait()方法矿辽。這個(gè)時(shí)候線程不具備CPU的等待資格丹允,和CPU的執(zhí)行權(quán)。
sleep()方法是自己設(shè)置阻塞時(shí)間袋倔。當(dāng)時(shí)間一到雕蔽,線程會自動轉(zhuǎn)換成可運(yùn)行狀態(tài)。他是Thread中的一個(gè)方法宾娜。當(dāng)一個(gè)線程在同步方法中調(diào)用了sleep方法批狐,其它線程不能調(diào)用它的其它同步方法。
swit()是Object中的一個(gè)方法前塔,當(dāng)線程調(diào)用了這個(gè)方法嚣艇。需要通過notify或者notifyAll方法來喚醒線程。線程阻塞是存放在線程池中的华弓。一個(gè)對象對應(yīng)一個(gè)線程池食零。
當(dāng)線程阻塞是,線程會自動釋放CPU的等待權(quán)和CPU的執(zhí)行權(quán)寂屏。但是如果使用的sleep方法贰谣,線程是不會釋放鎖對象的。而wait方法是會釋放鎖對象迁霎。
下面說下經(jīng)典的線程通訊:
//產(chǎn)品類
class Product{
String name; //名字
double price; //價(jià)格
boolean flag = false; //產(chǎn)品是否生產(chǎn)完畢的標(biāo)識吱抚,默認(rèn)情況是沒有生產(chǎn)完成。
}
//生產(chǎn)者
class Producer extends Thread{
Product p ; //產(chǎn)品
public Producer(Product p) {
this.p = p ;
}
@Override
public void run() {
int i = 0 ;
while(true){
synchronized (p) {
if(p.flag==false){
if(i%2==0){
p.name = "蘋果";
p.price = 6.5;
}else{
p.name="香蕉";
p.price = 2.0;
}
System.out.println("生產(chǎn)者生產(chǎn)出了:"+ p.name+" 價(jià)格是:"+ p.price);
p.flag = true;
i++;
p.notifyAll(); //喚醒消費(fèi)者去消費(fèi)
}else{
//已經(jīng)生產(chǎn) 完畢考廉,等待消費(fèi)者先去消費(fèi)
try {
p.wait(); //生產(chǎn)者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消費(fèi)者
class Customer extends Thread{
Product p;
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
while(true){
synchronized (p) {
if(p.flag==true){ //產(chǎn)品已經(jīng)生產(chǎn)完畢
System.out.println("消費(fèi)者消費(fèi)了"+p.name+" 價(jià)格:"+ p.price);
p.flag = false;
p.notifyAll(); // 喚醒生產(chǎn)者去生產(chǎn)
}else{
//產(chǎn)品還沒有生產(chǎn),應(yīng)該 等待生產(chǎn)者先生產(chǎn)秘豹。
try {
p.wait(); //消費(fèi)者也等待了...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Demo5 {
public static void main(String[] args) {
Product p = new Product(); //產(chǎn)品
//創(chuàng)建生產(chǎn)對象
Producer producer = new Producer(p);
//創(chuàng)建消費(fèi)者
Customer customer = new Customer(p);
//調(diào)用start方法開啟線程
producer.start();
customer.start();
}
}
線程死鎖的體現(xiàn):
lass DeadLock extends Thread{
public DeadLock(String name){
super(name);
}
public void run() {
if("張三".equals(Thread.currentThread().getName())){
synchronized ("遙控器") {
System.out.println("張三拿到了遙控器,準(zhǔn)備 去拿電池!!");
synchronized ("電池") {
System.out.println("張三拿到了遙控器與電池了昌粤,開著空調(diào)爽歪歪的吹著...");
}
}
}else if("狗娃".equals(Thread.currentThread().getName())){
synchronized ("電池") {
System.out.println("狗娃拿到了電池既绕,準(zhǔn)備去拿遙控器!!");
synchronized ("遙控器") {
System.out.println("狗娃拿到了遙控器與電池了啄刹,開著空調(diào)爽歪歪的吹著...");
}
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
DeadLock thread1 = new DeadLock("張三");
DeadLock thread2 = new DeadLock("狗娃");
//開啟線程
thread1.start();
thread2.start();
}
}
死鎖現(xiàn)象出現(xiàn) 的根本原因:
- 存在兩個(gè)或者兩個(gè)以上的線程。
- 存在兩個(gè)或者兩個(gè)以上的共享資源岸更。
【注】講解可能有不足的地方鸵膏,希望各位小伙伴能幫我補(bǔ)充不足之處膊升,謝謝怎炊!
復(fù)制得來終覺淺,要想知道自己敲廓译!