1,爲何須要線程?java
做用:提高cpu的利用率,如,早期的dos系統,執行2個命令時( command 1, command 2 ),若是command1【假如是磁盤遍歷文件的IO操做】執行的時間比較長,那麼command 2必須等待,這種方式就是同步阻塞,編程
cpu就閒置了,爲了提升cpu的利用率,咱們就要使用多線程,若是一個任務時間比較長,cpu就暫時掛起他,去執行另外的線程,因此線程通常是異步的。安全
2,每個進程至少會有一個線程在運行多線程
public class Test { public static void main(String[] args) { //打印線程的名稱 System.out.println( Thread.currentThread().getName() ); } }
輸出結果爲 "main" ,注意這個main是線程的名字,跟main函數的名字相同而已。dom
3,在java中實現多線程有2種方式異步
>繼承Thread類函數
>實現Runnable接口this
在run方法中寫線程要執行的任務spa
class MyThread extends Thread{ public void run(){ System.out.println( "MyThread::run" ); } } public class ThreadUse1 { public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); System.out.println( "運行結束" ); } }
從運行結果可知,run方法是在以後執行的,雖然start開啓線程比 【System.out.println( "運行結束" );】 他早,這說明,CPU在調用線程的時候,是隨機的線程
4,再次驗證cpu調用線程的隨機性
class MyThreadRand extends Thread{ public void run(){ try{ for ( int i = 0; i < 10; i++ ) { int time = ( int )( Math.random() * 1000 ); Thread.sleep( time ); System.out.println( "MyThread:" + Thread.currentThread().getName() ); } }catch( InterruptedException e ){ e.printStackTrace(); } } } public class RandThread { public static void main(String[] args) { try{ MyThreadRand mt = new MyThreadRand(); mt.setName( "自定義線程" ); mt.start(); for ( int i = 0; i < 10; i++ ) { int time = ( int )( Math.random() * 1000 ); Thread.sleep( time ); System.out.println( "MainThread:" + Thread.currentThread().getName() ); } }catch( InterruptedException e ){ e.printStackTrace(); } } }
從執行結果可知,線程的調度沒有什麼規律,是隨機的, 這裏補充一點,start方法做用是通知 「線程規劃器」,這個線程已經準備好了,等待調用線程的run方法,就是讓系統安排一個時間來調用run方法。若是直接調用run方法,線程就變成同步方式了,必須等待MyThreadRand的run方法執行完成以後,纔會執行main函數中的線程
5,start方法的順序,不表明線程的啓動順序
class MyThreadStart extends Thread{ private int i; public MyThreadStart( int i ) { this.i = i; } public void run(){ System.out.println( i ); } } public class RandThread2 { public static void main(String[] args) { MyThreadStart s1 = new MyThreadStart( 1 ); MyThreadStart s2 = new MyThreadStart( 2 ); MyThreadStart s3 = new MyThreadStart( 3 ); MyThreadStart s4 = new MyThreadStart( 4 ); MyThreadStart s5 = new MyThreadStart( 5 ); MyThreadStart s6 = new MyThreadStart( 6 ); MyThreadStart s7 = new MyThreadStart( 7 ); MyThreadStart s8 = new MyThreadStart( 8 ); MyThreadStart s9 = new MyThreadStart( 9 ); MyThreadStart s10 = new MyThreadStart( 10 ); s1.start(); s2.start(); s3.start(); s4.start(); s5.start(); s6.start(); s7.start(); s8.start(); s9.start(); s10.start(); } }
6,實現Runnable接口
class MyThreadRunnable implements Runnable { public void run(){ System.out.println( Thread.currentThread().getName() ); } } public class ThreadRunnable { public static void main(String[] args) { MyThreadRunnable mt = new MyThreadRunnable(); Thread t = new Thread( mt ); t.setName( "自定義線程1" ); t.start(); } }
那麼兩種多線程的實現方式,有什麼不一樣呢?
>繼承Thread類
>實現Runnable接口
1,使用繼承Thread類的方式,多線程之間的數據不共享
class MyThreadShare extends Thread{ private int count = 5; public MyThreadShare( String name ){ this.setName( name ); } public void run(){ while( count-- > 0 ){ System.out.println( Thread.currentThread().getName() + "->" + count ); } } } public class ThreadShare { public static void main(String[] args) { MyThreadShare mt1 = new MyThreadShare( "A" ); MyThreadShare mt2 = new MyThreadShare( "B" ); MyThreadShare mt3 = new MyThreadShare( "C" ); mt1.start(); mt2.start(); mt3.start(); } }
2,而要想實現線程之間的數據共享,咱們能夠改一下
備註:線程數據共享與不共享,都有對應的場景,好比火車站4個窗口賣票,很顯然須要線程共享數據。如:總共用10張票,若是窗口賣了1張,其餘窗口就指剩下9張,這纔是比較貼近實際的,若是用第一種方式,至關於有40張餘票了。
class MyThreadShare2 extends Thread{ private int count = 5; public void run(){ while( count-- > 0 ){ System.out.println( Thread.currentThread().getName() + "->" + count ); } } } public class ThreadShare2 { public static void main(String[] args) { MyThreadShare2 mt = new MyThreadShare2(); Thread ta = new Thread( mt, "A" ); Thread tb = new Thread( mt, "B" ); Thread tc = new Thread( mt, "C" ); ta.start(); tb.start(); tc.start(); } }
從結果上看,好像實現了,數據共享,可是有點異常,B->3 很明顯不對,這種現象,在多線程編程裏面,叫「線程非安全」。現實生活中也有相似場景,好比4S店賣車,兩個客戶同時預訂了這輛車。那估計少不了一番辯論。怎麼解決這個問題呢?通常來講,在客戶訂車以前,銷售員要先查看庫存,若是客戶下單,要把庫存佔用。代表有人預訂,其餘銷售員看見了,就知道車被預訂了。程序中也是相似。若是要訪問這個變量,咱們就給他加鎖,相似於銷售員佔用庫存。在方法前加上synchronized關鍵字。那麼其餘線程訪問的時候,必須拿到這把鎖,才能訪問。synchronized能夠在任意對象或者方法上加鎖。
class MyThreadShare2 extends Thread{ private int count = 5; // public void run(){ //產生線程非安全問題 synchronized public void run(){ while( count-- > 0 ){ System.out.println( Thread.currentThread().getName() + "->" + count ); } } } public class ThreadShare2 { public static void main(String[] args) { MyThreadShare2 mt = new MyThreadShare2(); Thread ta = new Thread( mt, "A" ); Thread tb = new Thread( mt, "B" ); Thread tc = new Thread( mt, "C" ); ta.start(); tb.start(); tc.start(); } }
3,模擬用戶登陸場景,若是有兩個用戶登陸,咱們讓其中一個用戶線程佔時掛起。看下會出現什麼狀況
class Login { private static String userName; private static String userPwd; public static void doPost( String _userName, String _userPwd ){ try { userName = _userName; if( userName.equals( "ghostwu" ) ) { Thread.sleep( 3000 ); } userPwd = _userPwd; System.out.println( userName + "---->" + userPwd ); }catch( InterruptedException e ){ e.printStackTrace(); } } } class ThreadA extends Thread{ public void run(){ Login.doPost( "ghostwu", "abc123" ); } } class ThreadB extends Thread{ public void run(){ Login.doPost( "ghostwuB", "abc1234" ); } } public class UserLogin { public static void main(String[] args) { ThreadA ta = new ThreadA(); ThreadB tb = new ThreadB(); ta.start(); tb.start(); } }
在A線程掛起的時候,他以前的賦值已經被B線程改變了,因此結果與預想的ghostwu abc123不一樣。很明顯,咱們要上鎖。
synchronized public static void doPost( String _userName, String _userPwd ){ try { userName = _userName; if( userName.equals( "ghostwu" ) ) { Thread.sleep( 3000 ); } userPwd = _userPwd; System.out.println( userName + "---->" + userPwd ); }catch( InterruptedException e ){ e.printStackTrace(); } }