一、多線程以及安全執行緒 java安全執行緒就是指控制多個線程對某個資源的有序訪問或修改。這涉及兩個很重要的概念:java的記憶體模型和java的線程同步機制。 1.java的記憶體模型 要解決兩個主要的問題:可見度和有序性 可見度: 多個線程之間是不能互相傳遞資料通訊的,它們之間的溝通只能通過共用變數來進行 2.線程同步 由於同一進程內的多個線程共用記憶體空間,在Java中,就是共用執行個體,當多個線程試圖同時修改某個執行個體的內容時,就會造成衝突,因此,線程必須實現共用互斥,使多線程同步。 最簡單的同步是將一個方法標記為synchronized,對同一個執行個體來說,任一時刻只能有一個synchronized方法在執行。當一個方法正在執行某個synchronized方法時,其他線程如果想要執行這個執行個體的任意一個synchronized方法,都必須等待當前執行synchronized方法的線程退出此方法後,才能依次執行。 但是,非synchronized方法不受影響,不管當前有沒有執行synchronized方法,非synchronized方法都可以被多個線程同時執行。 synchronized和volatile關鍵字的區別: (1)synchronized關鍵字保證了多個線程對於同步塊是互斥的,synchronized作為一種同步手段,解決java多線程的執行有序性和記憶體可見度,而volatile關鍵字只解決多線程的記憶體可見度問題; (2)volatile關鍵字是java提供的一種同步手段,只不過它是輕量級。volatile只能保證多線程的記憶體可見度,不能保證多線程的執行有序性。而最徹底的同步要保證有序性和可見度,例如synchronized。 任何被volatile修飾的變數,都不拷貝副本到工作記憶體,任何修改都及時寫在主存。因此對於Valatile修飾的變數的修改,所有線程馬上就能看到,但是volatile不能保證對變數的修改是有序的。 3.基本知識 JAVA多線程實現方式:繼承Thread類、實現Runnable介面 使用哪一種:http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html 最好是後一種 二、執行個體代碼 功能:編寫一個安全執行緒的計數器,5個線程同時跑,計數到1000,輸出線程名和計數直到1000 代碼:
import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;/*** 功能:安全執行緒的計數器,5個線程同時跑,計數到1000,輸出線程名和計數* @author Yolanda**/public class MySafeThread implements Runnable{ private static AtomicInteger count = new AtomicInteger(0);//安全執行緒的計數變數 private int threadCount = 0;//線程編號 private static int num = 1; /** * 功能:計數 */ public static void calc(){ while((count.get())<1000) { count.incrementAndGet();//自增1,返回更新值 System.out.println("正在運行是線程" + Thread.currentThread().getName() + ":" + count); } } /** * 功能:線程運行方法,每次只能一個線程訪問 */ public synchronized void run() { while(true) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } MySafeThread.calc(); } } public static void main(String[] args) { //建立五個線程執行個體並啟動 for (int i = 1; i < 6; i++) { Thread mySafeThread = new Thread(new MySafeThread()); mySafeThread.start(); } } }
結果:
正在運行是線程Thread-1:2
正在運行是線程Thread-4:4
正在運行是線程Thread-0:4
正在運行是線程Thread-3:2
正在運行是線程Thread-3:9
正在運行是線程Thread-3:10
正在運行是線程Thread-0:8
正在運行是線程Thread-4:7
正在運行是線程Thread-4:13
正在運行是線程Thread-4:14
正在運行是線程Thread-4:15
正在運行是線程Thread-4:16
正在運行是線程Thread-4:17
正在運行是線程Thread-4:18
正在運行是線程Thread-4:19
正在運行是線程Thread-4:20 ......
正在運行是線程Thread-4:988
正在運行是線程Thread-4:989
正在運行是線程Thread-4:990
正在運行是線程Thread-4:991
正在運行是線程Thread-4:992
正在運行是線程Thread-4:993
正在運行是線程Thread-4:994
正在運行是線程Thread-4:995
正在運行是線程Thread-4:996
正在運行是線程Thread-4:997
正在運行是線程Thread-4:998
正在運行是線程Thread-4:999
正在運行是線程Thread-4:1000
正在運行是線程Thread-2:399
正在運行是線程Thread-1:398
正在運行是線程Thread-0:700
正在運行是線程Thread-3:402
===========================我是更新線==============================
看上面的結果其實是有一點不對的,前面的幾個計數發生了重複,這是由於鎖的位置不正確導致的。所以對這個程式重寫了一下。
package com.yolanda.fun.thread;import java.util.concurrent.atomic.AtomicInteger;public class MySafeCalcThread1 implements Runnable {private static AtomicInteger count = new AtomicInteger(0); public synchronized static void calc() { if ((count.get()) < 1000) { int c = count.incrementAndGet();// 自增1,返回更新值 System.out.println("正在運行是線程" + Thread.currentThread().getName() + ":" + c); }}public void run() { while(true) { MySafeCalcThread1.calc(); try { Thread.sleep(0); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}public static void main(String[] args) {for (int i = 0; i < 6; i++) {MySafeCalcThread1 thread = new MySafeCalcThread1();Thread t = new Thread(thread);t.start();}}}
然後結果就對啦,就沒有重複計數的問題啦。
結果:
正在運行是線程Thread-0:1
正在運行是線程Thread-1:2
正在運行是線程Thread-0:4
正在運行是線程Thread-2:3
正在運行是線程Thread-2:7
正在運行是線程Thread-0:6
正在運行是線程Thread-0:9
正在運行是線程Thread-1:5
正在運行是線程Thread-0:12
正在運行是線程Thread-5:11
正在運行是線程Thread-3:10
正在運行是線程Thread-2:8
.......
正在運行是線程Thread-4:996
正在運行是線程Thread-4:997
正在運行是線程Thread-4:998
正在運行是線程Thread-4:999
正在運行是線程Thread-4:1000
正在運行是線程Thread-1:743
正在運行是線程Thread-3:742
正在運行是線程Thread-0:935
正在運行是線程Thread-5:884
正在運行是線程Thread-2:747
還有一種寫法是這樣子的~
package com.yolanda.fun.thread;import java.util.concurrent.atomic.AtomicInteger;public class MySafeCalcThread2 implements Runnable {private static AtomicInteger count = new AtomicInteger(0);public static void calc() {while ((count.get()) < 1000) {int c = 0;synchronized (count) {if ((count.get()) < 1000) {c = count.incrementAndGet();// 自增1,返回更新值}}System.out.println("正在運行是線程" + Thread.currentThread().getName() + ":" + c);}}public void run() { while(true) { MySafeCalcThread2.calc(); try { Thread.sleep(0); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}public static void main(String[] args) {for (int i = 0; i < 6; i++) {MySafeCalcThread2 thread = new MySafeCalcThread2();Thread t = new Thread(thread);t.start();}}}
結果:
正在運行是線程Thread-0:1
正在運行是線程Thread-3:2
正在運行是線程Thread-4:3
正在運行是線程Thread-5:4
正在運行是線程Thread-0:5
正在運行是線程Thread-0:6
正在運行是線程Thread-2:7
正在運行是線程Thread-2:8
正在運行是線程Thread-1:9
正在運行是線程Thread-2:10
........
正在運行是線程Thread-2:990
正在運行是線程Thread-2:991
正在運行是線程Thread-2:992
正在運行是線程Thread-1:993
正在運行是線程Thread-2:994
正在運行是線程Thread-0:995
正在運行是線程Thread-5:996
正在運行是線程Thread-5:997
正在運行是線程Thread-5:998
正在運行是線程Thread-5:999
正在運行是線程Thread-5:1000
============================我是更新線===============================
在此處補充一下JAVA多線程實現的兩種方式
第一種:繼承Thread類
package com.yolanda.fun.thread;public class MyThread extends Thread {public void run() {for (int i = 0; i < 10; i++) {System.out.println("線程" + Thread.currentThread().getName() + "在運行");}}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();for (int i = 0; i < 10; i++) {System.out.println("線程" + Thread.currentThread().getName() + "在運行");// 線程main}}}
結果:
線程main在運行
線程Thread-0在運行
線程main在運行
線程main在運行
線程Thread-0在運行
線程Thread-0在運行
線程main在運行
線程Thread-0在運行
線程main在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程main在運行
線程main在運行
線程main在運行
線程main在運行
線程main在運行
結果分析:這裡可以看到main線程和Thread-0線程交替運行。
所謂的多線程,指的是兩個線程的代碼可以同時運行,而不必一個線程需要等待另一個線程內的代碼執行完才可以運行。對於單核CPU來說,是無法做到真正的多線程的,每個時間點上,CPU都會執行特定的代碼,由於CPU執行代碼時間很快,所以兩個線程的代碼交替執行看起來像是同時執行的一樣。那具體執行某段代碼多少時間,就和分時機制系統有關了。分時系統把CPU時間劃分為多個時間片,作業系統以時間片為單位片為單位各個線程的代碼,越好的CPU分出的時間片越小。所以看不到明顯效果也很正常,一個線程列印5句話本來就很快,可能在分出的時間片內就執行完成了。所以,最簡單的解決辦法就是把for迴圈的值調大一點就可以了(也可以在for迴圈裡加Thread.sleep方法)。
第二種:實現Runnable介面。和繼承自Thread類差不多,不過實現Runnable後,還是要通過一個Thread來啟動
package com.yolanda.fun.thread;public class MyRunnable implements Runnable{public void run() {for (int i = 0; i < 10; i++) {System.out.println("線程" + Thread.currentThread().getName() + "在運行");}}public static void main(String[] args) {MyRunnable thread = new MyRunnable();Thread t = new Thread(thread);t.start();for (int i = 0; i < 10; i++) {System.out.println("線程" + Thread.currentThread().getName() + "在運行");}}}
結果:
線程main在運行
線程Thread-0在運行
線程main在運行
線程Thread-0在運行
線程main在運行
線程Thread-0在運行
線程main在運行
線程main在運行
線程main在運行
線程main在運行
線程main在運行
線程main在運行
線程main在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
線程Thread-0在運行
兩個線程也是交替運行。
其實Thread類也是實現的Runnable介面。
兩種實現方式對比的關鍵就在於extends和implements的對比,當然是後者好。因為第一,繼承只能但繼承,實現可以多實現;第二,實現的方式對比繼承的方式,也有利於減小程式之間的耦合。
因此,多線程的實現幾乎都是使用的Runnable介面的方式。
線程狀態
虛擬機器中的線程狀態有六種,定義在Thread.State中:
1、建立狀態NEW
new了但是沒有啟動的線程的狀態。比如"Thread t = new Thread()",t就是一個處於NEW狀態的線程
2、可運行狀態RUNNABLE
new出來線程,調用start()方法即處於RUNNABLE狀態了。處於RUNNABLE狀態的線程可能正在Java虛擬機器中運行,也可能正在等待處理器的資源,因為一個線程必須獲得CPU的資源後,才可以運行其run()方法中的內容,否則排隊等待
3、阻塞BLOCKED
如果某一線程正在等待監視器鎖,以便進入一個同步的塊/方法,那麼這個線程的狀態就是阻塞BLOCKED
4、等待WAITING
某一線程因為調用不帶逾時的Object的wait()方法、不帶逾時的Thread的join()方法、LockSupport的park()方法,就會處於等待WAITING狀態
5、逾時等待TIMED_WAITING
某一線程因為調用帶有指定正等待時間的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就會處於逾時等待TIMED_WAITING狀態
6、終止狀態TERMINATED
線程調用終止或者run()方法執行結束後,線程即處於終止狀態。處於終止狀態的線程不具備繼續啟動並執行能力。