我們經常聽到程序是不是多線程的、具備不具備線程安全。那什么是線程呢?怎樣算是線程安全呢?我們一起來了解一下吧!
什么是線程?
每個正在系統上運行的程序都是一個進程。每個進程包含一到多個線程。進程也可能是整個程序或者是部分程序的動態執行。線程是一組指令的集合,或者是程序的特殊段,它可以在程序里獨立執行。也可以把它理解為代碼運行的上下文。所以線程基本上是輕量級的進程,它負責在單個程序里執行多任務。通常由操作系統負責多個線程的調度和執行。
什么是多線程?
多線程是為了使得多個線程並行的工作以完成多項任務,以提高系統的效率。線程是在同一時間需要完成多項任務的時候被實現的。
使用線程的好處有以下幾點:
·使用線程可以把占據長時間的程序中的任務放到后台去處理
·用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
·程序的運行速度可能加快
·在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較游泳了。在這種情況下我們可以釋放一些珍貴的資源如內存占用等等。
什么是線程安全?
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
或者說:一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
線程安全問題都是由全局變量及靜態變量引起的。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
當我們查看JDK API的時候,總會發現一些類說明寫着,線程安全或者線程不安全,比如說StringBuilder中,有這么一句,“將StringBuilder
的實例用於多個線程是不安全的。如果需要這樣的同步,則建議使用StringBuffer
。 ”,那么下面手動創建一個線程不安全的類,然后在多線程中使用這個類,看看有什么效果。
Count.java:
1 public class Count {
2 private int num;
3 public void count() {
4 for(int i = 1; i <= 10; i++) {
5 num += i;
6 }
7 System.out.println(Thread.currentThread().getName() + "-" + num);
8 }
9 }
在這個類中的count方法是計算1一直加到10的和,並輸出當前線程名和總和,我們期望的是每個線程都會輸出55。
ThreadTest.java:
10 public class ThreadTest {
11 public static void main(String[] args) {
12 Runnable runnable = new Runnable() {
13 Count count = new Count();
14 public void run() {
15 count.count();
16 }
17 };
18 for(int i = 0; i < 10; i++) {
19 new Thread(runnable).start();
20 }
21 }
22 }
這里啟動了10個線程,看一下輸出結果:
Thread-0-55
Thread-1-110
Thread-2-165
Thread-4-220
Thread-5-275
Thread-6-330
Thread-3-385
Thread-7-440
Thread-8-495
Thread-9-550
只有Thread-0線程輸出的結果是我們期望的,而輸出的是每次都累加的,這里累加的原因以后的博文會說明,那么要想得到我們期望的結果,有幾種解決方案:
1. 將Count中num變成count方法的局部變量;
33 public class Count {
34 public void count() {
35 int num = 0;
36 for(int i = 1; i <= 10; i++) {
37 num += i;
38 }
39 System.out.println(Thread.currentThread().getName() + "-" + num);
40 }
41 }
2. 將線程類成員變量拿到run方法中;
42 public class ThreadTest4 {
43 public static void main(String[] args) {
44 Runnable runnable = new Runnable() {
45 public void run() {
46 Count count = new Count();
47 count.count();
48 }
49 };
50 for(int i = 0; i < 10; i++) {
51 new Thread(runnable).start();
52 }
53 }
54 }
3. 每次啟動一個線程使用不同的線程類,不推薦。
上述測試,我們發現,存在成員變量的類用於多線程時是不安全的,而變量定義在方法內是線程安全的。想想在使用struts1時,不推薦創建成員變量,因為action是單例的,如果創建了成員變量,就會存在線程不安全的隱患,而struts2是每一次請求都會創建一個action,就不用考慮線程安全的問題。