1. 程式人生 > >JAVA基礎-多線程

JAVA基礎-多線程

auto 事情 end 無鎖 mage 可能 靜態 exc 創建線程


一、線程與進程

進程:正在計算機中運行的一個程序,當一個程序進入內存運行,即變成一個進程。一個軟件至少有一個進程,
有的軟件在點擊一個應用圖標是可能會給你開多個進程,如:360全家桶。
線程:軟件在計算機中執行的一條路徑。軟件可以是多線程的。
總結:一個程序運行後至少有一個進程,一個進程中可以包含多個線程。而多線程就是一個程序中有多個線程
在同時執行。而JAVA為我們提供了2種方式實現多線程:1、繼承Thread類 2、實現Runnable接口。我們在編寫程
序時的主方法就是一個單獨的線程,線程名叫 "main"。而JAVA虛擬機Jvm運行是多線程運行。

二、Thread類

JAVA實現多線程其中的一種方法就是繼承Thread類,Thread是程序中的執行線程。Java 虛擬機允許應用程序並
發地運行多個執行線程。Thread類常用的構造方法有:1、分配新的 Thread 對象 - Thread(),2、分配新的Thread
對象,將指定的name作為線程名稱 - Thread(String name)。其常用的方法有:
1、改變線程名稱,使之與參數 name 相同:setName(String name)
2、該線程要執行的操作:run()
3、在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行):sleep()
4、使該線程開始執行;Java 虛擬機調用該線程的 run 方法:start()
5、返回該線程的名稱:getName()
6、靜態方法,返回值為Thread對象 - 返回對當前正在執行的線程對象的引用:currentThread()
A:創建線程的步驟:
1.定義一個類繼承Thread。
2.重寫run方法。
3.創建子類對象(創建線程對象)。
4.調用start方法,開啟線程並讓線程執行,同時還會告訴jvm去調用run方法。
舉例:

 1 //定義類MyThread繼承Thread類
 2 public class MyThread extends Thread {
 3 @Override
 4 // 重寫Thread類裏的run方法
 5 public void run() {
 6 // 想要多線程執行的代碼
 7 for (int i = 0; i < 100; i++) {
 8 // 獲取線程名稱和循環的次數
 9 System.out.println(getName() + " " + i);
10 }
11 }
12 }

主函數:

 1 public static void main(String[] args) {
2 // 創建線程一 3 MyThread myThread01 = new MyThread(); 4 // 為線程一設置線程名 5 myThread01.setName("線程一"); 6 // 開啟線程一 7 myThread01.start(); 8 // 創建線程二 9 MyThread myThread02 = new MyThread(); 10 // 為線程二設置線程名 11 myThread02.setName("線程二"); 12 // 開啟線程二 13 myThread02.start(); 14 }

三、Runnable接口

JAVA實現多線程的另一種方法就是實現Runnable接口,實現 run 方法。然後創建Runnable的子類對象,
傳入到某個線程的構造方法中,開啟線程。Runnable接口其實就是用來指定每個線程要執行的任務,在由線
程類Thread來執行這些任務。Runnable接口只有一個要實現的方法:run(),作用與Thread類的run方法類似,
在啟動該線程時將被調用,也就是說將由其實現類來實現run方法。Runnable接口的常用實現類是Thread類。
B:創建線程的步驟:
1、定義類實現Runnable接口。
2、覆蓋接口中的run方法。
3、創建Thread類的對象
4、將Runnable接口的子類對象作為參數傳遞給Thread類的構造函數。
5、調用Thread類的start方法開啟線程。
舉例:

 1 //創建MyRunable類實現Runnable接口
 2 public class MyRunable implements Runnable {
 3 @Override
 4 //重寫run方法
 5 public void run() {
 6 /*循環
 7 Thread.currentThread().getName:
 8 通過Thread類的靜態方法currentThread()方法,返回正在執行的線程對象,並獲取線程名*/
 9 for (int i = 0; i < 100; i++) {
10 System.out.println(Thread.currentThread().getName() + " :" + i);
11 }
12 }
13 }

*主方法:

 1 public static void main(String[] args) {
 2 // 獲取MyRunable對象(線程任務)
 3 MyRunable r = new MyRunable();
 4 // 根據線程任務創建線程一
 5 Thread t1 = new Thread(r);
 6 // 命名
 7 t1.setName("線程一");
 8 // 開啟線程
 9 t1.start();
10 // 根據線程任務創建線程二
11 Thread t2 = new Thread(r);
12 t2.setName("線程二");
13 t2.start();
14 }

四、多線程安全

由於CUP對線程的處理具有隨機性,這就導致了當線程1進入某一循環,還沒有執行循環裏的語句時,
CUP突然切換到線程2,線程2也進入了循環語句。這樣就會導致本應該只執行一次的條件執行了2次,導致
線程不安全的問題出現。
問題出現的原因:
* 要有多個線程
* 要有被多個線程所共享的數據
* 多個線程並發的訪問共享的數據。
用實現Runnable接口的方式對同一數據進行

操作可能就會出現這種重復的問題:

技術分享

(一)、多線程安全的解決

問題出現了該如何解決呢?多線程處理的機制其實和我們在火車上廁所類似。在火車上有好多人都要去廁所,
但是廁所只能供一個人使用,2個人如果一起擠進了廁所就會發生一些“事情”,那麽火車是怎麽解決這個問題的呢?
我們可以在廁所的門上上一把鎖,路人甲進廁所後把門鎖起來,方便完之後再把鎖打開,路人乙在進去把門鎖上。
而JAVA為了解決多線程的並發安全問題,也給我們提供了這麽一把“鎖” - 同步代碼塊和同步方法。

1、同步代碼塊:

synchronized(鎖對象){

}
舉例:

 1 public class MyRunable implements Runnable {
 2 // 設置一個被操作的數據
 3 int i = 100;
 4 
 5 @Override
 6 public void run() {
 7 while (true) {
 8 // 設置同步代碼塊
 9 synchronized (MyRunable.class) {
10 try {
11 // 為了不要太快,先睡一會
12 Thread.sleep(200);
13 } catch (InterruptedException e) {
14 // TODO Auto-generated catch block
15 e.printStackTrace();
16 }
17 // 對數據進行操作
18 if (i > 0) {
19 System.out.println(Thread.currentThread().getName() + " " + i--);
20 }
21 }
22 }
23 }
24 }

*入口方法:

 1 public static void main(String[] args) {
 2 MyRunable runable = new MyRunable();
 3 Thread t1 = new Thread(runable);
 4 t1.setName("線程A");
 5 Thread t2 = new Thread(runable);
 6 t2.setName("線程B");
 7 Thread t3 = new Thread(runable);
 8 t3.setName("線程C");
 9 t1.start();
10 t2.start();
11 t3.start();
12 }

鎖對象:可以是java中的任意對象。多個同步代碼塊必須使用同一個對象,才能實現多個代碼塊之間的同步。一
般使用當前類的.class對象

2、同步方法:

非靜態方法:this
靜態方法:字節碼對象(類名.class)
舉例:

 1 public class MyRunable implements Runnable {
 2 // 設置一個被操作的數據
 3 int i = 100;
 4 
 5 public void run() {
 6 while (true) {
 7 method();
 8 }
 9 }
10 
11 private synchronized void method() {
12 
13 try {
14 Thread.sleep(150);
15 } catch (InterruptedException e) {
16 // TODO Auto-generated catch block
17 e.printStackTrace();
18 }
19 if (i > 0) {
20 System.out.println(Thread.currentThread().getName() + " :" + i);
21 i--;
22 }else {
23 System.exit(0);
24 }
25 }

*入口方法:

 1 public static void main(String[] args) {
 2 MyRunable runable = new MyRunable();
 3 Thread t1 = new Thread(runable);
 4 t1.setName("線程A");
 5 Thread t2 = new Thread(runable);
 6 t2.setName("線程B");
 7 Thread t3 = new Thread(runable);
 8 t3.setName("線程C");
 9 t1.start();
10 t2.start();
11 t3.start();
12 }

3、優缺點

* 同步:安全性高,效率低。
* 非同步:效率高,但是安全性低。

五、使用多線程對同一數據進行不同的操作

代碼:

 1 public class Demo {
 2     public static class AddSub {
 3         //提供原子操作的Integer的類(無鎖的線程安全整數 AtomicInteger)
 4         AtomicInteger at = new AtomicInteger(1);
 5         int j = 1;
 6 
 7         public static void main(String[] args) {
 8             AddSub ab = new AddSub();
 9             Add a = ab.new Add();
10             Sub b = ab.new Sub();
11             Thread t1 = new Thread(a);
12             Thread t2 = new Thread(a);
13             Thread t3 = new Thread(b);
14             Thread t4 = new Thread(b);
15             t1.start();
16             t2.start();
17             t3.start();
18             t4.start();
19         }
20 
21         public synchronized void add() {
22             j++;
23             System.out.println("add:" + j);
24         }
25 
26         public synchronized void sub() {
27             j--;
28             System.out.println("sub:" + j);
29         }
30 
31         class Add implements Runnable {
32             @Override
33             public void run() {
34                 for (int i = 0; i < 20; i++) {
35                     add();
36                 }
37 
38             }
39         }
40 
41         class Sub implements Runnable {
42             @Override
43             public void run() {
44                 for (int i = 0; i < 20; i++) {
45                     sub();
46                 }
47 
48             }
49         }
50     }
51 }

JAVA基礎-多線程