java多線程及線程安全詳解


為什么要使用多線程:

單線程只能干一件事  而多線程可以同時干好多事(將任務放到線程里執行  效率高)

而所謂同時干並不是真正意義上的同時   只是(這里就叫CPU)cpu在每個線程中隨機切換來執行 線程中要干的活

多線程編寫:

1)第一種:(線程類)

class Stu1 extends Thread{

  //重寫 run方法 

}

調用:Stu1 su = new Stu1();

su.start()//內部會自動調用run方法    把run方法放到線程上調用

2)第二種:普通任務類(由於第一種類只能是單繼承 就沒法實現繼承其他父類並且繼承線程類 所以第一種方法擴展性比較差)

直接創建線程對象    線程內干的事  放到一個自定義對象里面實現   (用到修飾設計模式)

底層代碼

class Thread{

private Runnable r

public void Thread(Runnable r){ //利用有參構造將自定義對象傳進來

this.r = r;

}

public void start(){

  r.run();

}

}

class Stu implements Runnable{}  通過實現接口   線程類底層start調用的run方法 實際就是我們自定義類中的run方法

Thread th = new  Thread(new Stu())  //直接創建線程對象

th.start();

我們用第二種方法創建多線程如下:

public static void main(String[] args) {
//		不同線程干同一件事
		SaleWindow sw = new SaleWindow();
		Thread t1 = new Thread(sw);
		Thread t2 = new Thread(sw);
		t1.setName("窗口A");
		t2.setName("窗口B");
		t1.start();
		t2.start();
	}

 

public class SaleWindow implements Runnable {
	private int ticketCount = 10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
//		多個窗口賣票
		for(int i = 0;i<10;i++){
			if(ticketCount>0){
				//字符串拼接信息   變量+""  就可以拼接成字符串
				System.out.println(Thread.currentThread().getName()+"賣出"+ticketCount+"張票");
				ticketCount--;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
//					e.printStackTrace();
				}
			}
		}
	}
	
}

 最后結果圖:

從運行結果來看:同一張票賣給了二個人  這是在現實生活中不允許的 這樣就會產生線程安全問題。

而產生線程安全問題的有三個要素必須同時滿足才會產生線程安全:

1、必須有共享資源
2、必須是多線程環境
3、每個線程都適用了共享資源

而上面的例子:票是共享資源、又是多線程環境、線程執行任務的時候又使用了共享資源 所以會產生線程安全

怎么解決線程安全?

解決線程安全其核心:就是將某一個線程中的任務給鎖(同步鎖)起來,這個時候JVM就不得不等到本任務里的代碼執行完以后在去執行另外一個線程里的任務。

(ps:在沒加同步鎖時jvm可以隨時在線程之間隨機切換,很有可能本線程的任務沒有執行完就切換到別的線程上去了)

二種方法:

1、同步代碼塊:

public class SaleWindow implements Runnable {
	private int ticketCount = 10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		// 多個窗口賣票
		for (int i = 0; i < 10; i++) {//可以隨意設置鎖
			synchronized (this) {
				if (ticketCount > 0) {
					// 字符串拼接信息 變量+"" 就可以拼接成字符串
					System.out.println(Thread.currentThread().getName() + "賣出"
							+ ticketCount + "張票");
					ticketCount--;
					try {
						Thread.sleep(500);//每隔500毫秒 線程休眠 隨后自己自動會喚醒(目的是為了調節線程速度)
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						// e.printStackTrace();
					}
				}
			}
		}
	}

}

 2、同步方法:

public class SaleWindow implements Runnable {
	private int ticketCount = 10;
     //默認固定的鎖對象this
//將產生線程安全的代碼封裝到方法里並設置成同步方法 public synchronized void syncB() { if (ticketCount > 0) { // 字符串拼接信息 變量+"" 就可以拼接成字符串 System.out.println(Thread.currentThread().getName() + "賣出" + ticketCount + "張票"); ticketCount--; try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block // e.printStackTrace(); } } } @Override public void run() { // TODO Auto-generated method stub // 多個窗口賣票 for (int i = 0; i < 10; i++) { syncB(); } } }

 結果圖:

最后結果每個窗口不能賣同樣的票解決了線程安全問題

線程之間的通信

(ps:線程之間的通信和線程安全無關聯二者不是一回事)

怎樣才能讓二個線程之間進行通信?

線程之間是相互獨立的互不聯系 而真正意義上的通信是通過中間件(同步鎖 必須是同一把鎖)來達到線程之間通信的目的

案例:二個線程來回交替輸出一條數據(意思就是必須按照我說一句你說一句的這個規則走)

class BingB extends Thread {
	public void run(){
		for(int i = 0;i<5;i++){
			System.out.println("冰冰美,如花丑");
		}
	}
}

class RuH extends Thread{
	public void run(){
		for(int i = 0;i<5;i++){
			System.out.println("如花美,冰冰丑");
		}
	}
}

public class Test {
	public static void main(String[] args) {
		BingB bb = new BingB();
		RuH rh = new RuH();
		bb.start();
		rh.start();
	}
}

 結果圖:此結果不是交替出現的

要想達到交替的目的代碼如下:

class MyLock{
	static Object o = new Object();
}

class BingB extends Thread {
	public void run(){
		for(int i = 0;i<5;i++){
			//加鎖(意思是必須等到本線程的任務代碼執行完以后才去執行別的線程的代碼)
			synchronized (MyLock.o) {
				System.out.println("冰冰美,如花丑");
				MyLock.o.notify();//喚醒另一個線程(這里表示如花線程)
				try {
					MyLock.o.wait();//暫時徹底休眠本線程(不會自動喚醒 需要手動喚醒)   同時解鎖  阻塞線程 代碼就不會往下繼續走  jvm會切換到另外一個線程中去
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
				}
			}
		}
	}
}

class RuH extends Thread{
	public void run(){
		for(int i = 0;i<5;i++){
			synchronized (MyLock.o) {
				System.out.println("如花美,冰冰丑");
				MyLock.o.notify();//喚醒另一個線程(這里表示如冰冰程)
				try {
					MyLock.o.wait();//暫時徹底休眠本線程   同時解鎖  阻塞線程
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

public class Test {
	public static void main(String[] args) {
		BingB bb = new BingB();
		RuH rh = new RuH();
		bb.start();
		rh.start();
	}
}

 

從以上例子可以看出產生線程安全並解決線程安全 到實現線程間的通信 都用到了 同步鎖  所以 同步鎖既可以解決線程安全問題  又可以解決線程之間的通信

多線程狀態流程圖:

寫到這里有關線程的基本講解完畢,如果內容有我自己理解錯誤的地方還請各位大神指教,小弟不吝求教!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM