1、繼承Thread
package First; public class MyThread extends Thread { public void run() { super.run(); System.out.println("mythread"); } }
package First; public class Test { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); System.out.println("main"); } }
在使用多線程技術時,代碼的運行結果與代碼執行順序或調用順序是無關的。
package First; public class Test { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); myThread.start(); System.out.println("main"); } }
多次執行start(),會出現java.lang.IllegalThreadStateException異常
package First; public class Test { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.run(); System.out.println("main"); } }
start()通知“”線程規划器“”此線程已經准備就緒,等待調用線程對象的run(),具有異步效果。如果直接調用run(),則是同步,從上到下順序依次執行
執行start()順序不代表線程啟動的順序。
2、實現runnable接口
package First; public class MyRunnable implements Runnable { @Override public void run() { System.out.println("運行中"); } }
package First; public class Run { public static void main(String[] args) { Runnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); System.out.println("運行結束"); } }
Thread構造函數:
構造函數Thread(Runnable target)意味着不光可以傳入runnable接口的對象,還可以傳入Thread類的對象,這樣做完全可以將一個Thread對象的run()方法交給其他線程進行調用
實例變量與線程安全
(1)不共享數據
package First; public class MyThread extends Thread { private int count = 5; public MyThread(String name) { super(); //設置線程名稱 this.setName(name); } public void run() { super.run(); while(count>0) { count--; System.out.println("由"+this.currentThread().getName()+"計算,count="+count); } } }
package First; public class Test { public static void main(String[] args) { MyThread a = new MyThread("A"); MyThread b = new MyThread("B"); MyThread c = new MyThread("C"); a.start(); b.start(); c.start(); System.out.println("main"); } }
(2)共享數據
package First; public class MyThread extends Thread { private int count = 5; public void run() { super.run(); //此實例不要用for語句,因為使用同步后其他線程就得不到運行的機會了 count--; System.out.println("由"+this.currentThread().getName()+"計算,count="+count); } }
package First; public class Test { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread a = new Thread(myThread,"A"); Thread b = new Thread(myThread,"B"); Thread c = new Thread(myThread,"C"); Thread d = new Thread(myThread,"D"); Thread e = new Thread(myThread,"E"); a.start(); b.start(); c.start(); d.start(); e.start(); System.out.println("main"); } }
非線程安全問題(隨機):主要是指多個線程對同一個對象中的同一個實例變量進行操作時會出現值被更改、值不同步的情況,進而影響程序的執行流程
某些JVM中,i--的操作分成三步:
1)取得原有的i值
2)計算i-1
3)對i進行賦值
在這三個步驟中,如果有多個線程同時訪問,那么一定會出現非線程安全問題
package First; public class MyThread extends Thread { private int count = 5; synchronized public void run() { super.run(); count--; System.out.println("由"+this.currentThread().getName()+"計算,count="+count); } }
程序改成上述,就不會出現問題了
一個非線程安全的例子:
package First; public class LoginServlet { private static String usernameRef; private static String passwordRef; public static void doPost(String username,String password) { try { usernameRef = username; if(username.equals("a")) { Thread.sleep(5000); } passwordRef = password; System.out.println("username="+usernameRef+" password="+password); } catch (Exception e) { // TODO: handle exception } } }
package First; public class ALogin extends Thread{ public void run() { LoginServlet.doPost("a", "aa"); } }
package First; public class BLogin extends Thread{ public void run() { LoginServlet.doPost("b", "bb"); } }
package First; public class Run { public static void main(String[] args) { ALogin a = new ALogin(); a.start(); BLogin b = new BLogin(); b.start(); } }
解決非線程安全問題使用synchronized
package First; public class LoginServlet { private static String usernameRef; private static String passwordRef; synchronized public static void doPost(String username,String password) { try { usernameRef = username; if(username.equals("a")) { Thread.sleep(5000); } passwordRef = password; System.out.println("username="+usernameRef+" password="+password); } catch (Exception e) { // TODO: handle exception } } }
留意i--與System.out.println()
println()與i++聯合使用時有可能出現另外一種異常情況
package First; public class MyThread extends Thread { private int i = 5; public void run() { //代碼i--由前面項目中單獨一行運行改成在當前項目中在println()方法中直接進行打印 System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName()); } }
package First; public class Run { public static void main(String[] args) { MyThread run = new MyThread(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); Thread t4 = new Thread(run); Thread t5 = new Thread(run); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }
雖然println()內部是同步的,但i--的操作卻是在進入println()之前發生的。所以為了防止非線程安全問題,還是應該繼續使用同步方法
currentTread()方法
package First; public class MyThread extends Thread { public MyThread() { System.out.println("構造方法的打印: "+Thread.currentThread().getName()); } public void run() { System.out.println("run方法打印:"+Thread.currentThread().getName()); } }
package First; public class Run { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } }
package First; public class Run { public static void main(String[] args) { MyThread myThread = new MyThread(); //myThread.start(); myThread.run(); } }
由第一個實驗可以看出:Mythread的構造函數是被main函數調用的,而run()方法是被名稱為Thread-0調用的
由第二個實驗可以看出:直接調用run()的話,則調用者名稱為main
package First; public class CountOperate extends Thread { public CountOperate() { System.out.println("CountOperation---begin"); System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName()); System.out.println("this.getName()"+this.getName()); System.out.println("CountOperation---end"); } public void run() { System.out.println("CountOperation---begin"); System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName()); System.out.println("this.getName()"+this.getName()); System.out.println("CountOperation---end"); } }
package First; public class Run { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); CountOperate countOperate = new CountOperate(); Thread thread = new Thread(countOperate); thread.setName("A"); thread.start(); } }
isLive()
判斷當前線程是否處於活躍狀態
package First; public class MyThread extends Thread { public void run() { System.out.println("run="+this.isAlive()); } }
package First; public class Run { public static void main(String[] args) { MyThread myThread = new MyThread(); System.out.println("begin="+myThread.isAlive()); myThread.start(); System.out.println("end="+myThread.isAlive()); } }
注意最后一個println()的值是不確定的
package First; public class CountOperate extends Thread { public CountOperate() { System.out.println("CountOperation---begin"); System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()="+Thread.currentThread().isAlive()); System.out.println("this.getName()"+this.getName()); System.out.println("this.isAlive()"+this.isAlive()); System.out.println("CountOperation---end"); } public void run() { System.out.println("run---begin"); System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()="+Thread.currentThread().isAlive()); System.out.println("this.getName()"+this.getName()); System.out.println("this.isAlive()"+this.isAlive()); System.out.println("run---end"); } }
package First; public class Run { public static void main(String[] args) { CountOperate countOperate = new CountOperate(); Thread thread = new Thread(countOperate); System.out.println("main begin t1 isAlive="+thread.isAlive()); thread.setName("A"); thread.start(); System.out.println("main end t1 isAlive="+thread.isAlive()); } }
如果將線程對象以構造參數的方式傳遞給Thread對象進行start()啟動時,運行的結果和前面實例的結果有些差異。造成這樣的差異的原因還是來自於Thread.currentThread()和this之間的差異(具體解釋參考:http://blog.csdn.net/yezis/article/details/57513130)
sleep()方法
是在指定的毫秒數內讓當前“正在執行的線程”休眠(暫停執行)這個“正在執行的線程”是指this.currentThread()返回的線程
package First; public class MyThread extends Thread { public void run() { try { System.out.println("run threadName="+this.currentThread().getName()+" begin"); Thread.sleep(2000); System.out.println("run threadName="+this.currentThread().getName()+" end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package First; public class Run { public static void main(String[] args) { MyThread myThread = new MyThread(); System.out.println("begin = " +System.currentTimeMillis()); myThread.run(); //myThread.start(); System.out.println("end = " +System.currentTimeMillis()); } }
package First; public class Run { public static void main(String[] args) { MyThread myThread = new MyThread(); System.out.println("begin = " +System.currentTimeMillis()); //myThread.run(); myThread.start(); System.out.println("end = " +System.currentTimeMillis()); } }
getId()方法
作用是獲取線程的唯一標識
package First; public class Run { public static void main(String[] args) { Thread runThrad = Thread.currentThread(); System.out.println(runThrad.getId()); } }
停止線程
Thread.stop()是不安全的,已經被棄用
大多數使用Thread.interrupt(),但這個方法不會終止一個正在運行的線程,還需要加入一個判斷才可以完成線程的停止
停不了的線程
調用interrupt僅僅是在當前線程中打了一個停止的標記,並不是真的停止線程
package First; public class MyThread extends Thread { public void run() { super.run(); for(int i = 0;i <50000;i++) { System.out.println("i"+(i+1)); } } }
package First; public class Run { public static void main(String[] args) { try { Thread myThread= new MyThread(); myThread.start(); Thread.sleep(2000); Thread.interrupted(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } } }
說明調用interrupt並沒有停止線程
判斷線程是否是停止狀態
this.interrupted();是static方法,測試當前線程是否已經中斷,當前線程是指運行this.interrupted()方法的線程
package First; public class Run { public static void main(String[] args) { try { Thread myThread= new MyThread(); myThread.start(); Thread.sleep(2000); Thread.interrupted(); System.out.println("是否停止1? = "+Thread.interrupted()); System.out.println("是否停止1? = "+Thread.interrupted()); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end"); } }
線程並未停止,這也說明了interrupted()方法的解釋:測試當前線程是否已經中斷。這個“當前線程”是main,它從未中斷過,所以打印的是兩false
package First; public class Run { public static void main(String[] args) { Thread.currentThread().interrupt(); System.out.println("是否停止1? = " + Thread.interrupted()); System.out.println("是否停止1? = " + Thread.interrupted()); System.out.println("end"); } }
為什么第2個布爾值是false呢?:
this.isInterrupted():方法不是static的
package First; public class Run { public static void main(String[] args) { try { Thread myThread= new MyThread(); myThread.start(); Thread.sleep(1000); Thread.interrupted(); System.out.println("是否停止1? = "+myThread.isInterrupted()); System.out.println("是否停止1? = "+myThread.isInterrupted()); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end"); } }
兩者之間的區別:
能停止的線程-異常法(這兩個實驗我都沒成功)
package First; public class MyThread extends Thread { public void run() { super.run(); for(int i = 0;i <50000;i++) { if(Thread.interrupted()) { System.out.println("已經是停止的狀態,退出"); break; } System.out.println("i"+(i+1)); } } }
package First; public class Run { public static void main(String[] args) { try { Thread myThread= new MyThread(); myThread.start(); Thread.sleep(2000); Thread.interrupted(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end"); } }
雖然停止了線程,但如果for語句下面還有語句,還是會繼續執行的
package First; public class MyThread extends Thread { public void run() { super.run(); try { for(int i = 0;i <50000;i++) { if(Thread.interrupted()) { System.out.println("已經是停止的狀態,退出"); break; } System.out.println("i"+(i+1)); } System.out.println("我在for下面"); } catch (Exception e) { System.out.println("進入MyThread.java類run方法中的catch"); } } }
package First; public class Run { public static void main(String[] args) { try { Thread myThread= new MyThread(); myThread.start(); Thread.sleep(2000); Thread.interrupted(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end"); } }