java中的線程:java中,每個線程都有一個調用棧存放在線程棧之中,一個java應用總是從main()函數開始運行,被稱為主線程。一旦創建一個新的線程,就會產生一個線程棧。線程總體分為:用戶線程和守護線程,當所有用戶線程執行完畢的時候,JVM自動關閉。
但是守候線程卻不獨立於JVM,守候線程一般是由操作系統或者用戶自己創建的。
線程的生命周期:當一個線程被創建之后,進入新建狀態,JVM則給他分配內存空間,並進行初始化操作。當線程對象調用了start()方法,該線程就處於就緒狀態(可執行狀態),JVM會為其創建方法調用棧、和程序計數器,處於可執行狀態下的線程隨時可以被cpu調度執行。
CPU執行該線程的時候,該線程進入執行狀態。執行過程中,該線程遇倒像wait()等待阻塞、以及synchronized鎖同步阻塞或者調用線程的sleep()方法等進入一個阻塞狀態,阻塞之后通過notify()或者notifyAll()方法喚醒重新獲取對象鎖之后再行進入就緒狀態,
等待cpu執行進去執行狀態、當線程執行完或者return則線程正常結束,如果發生處理的運行時異常,則線程因為異常而結束。這是一個線程的整個運行的生命周期。如下圖所示:

線程的幾種實現: 1、繼承Thread類,重寫該類的run方法 class MyThread extends Thread { private int i = 0; @Override public void run() { for (i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } } } public class ThreadTest { public static void main(String[] args) { Thread myThread1 = new MyThread(); // 創建一個新的線程 myThread1 此線程進入新建狀態 myThread1 .start(); // 調用start()方法使得線程進入可執行狀態 } } 2、實現Runnable接口,並重寫該接口的run()方法,該run()方法同樣是線程執行體,創建Runnable實現類的實例,並以此實例作為Thread類的target來創建Thread對象,該Thread對象才是真正的線程對象 class MyRunnable implements Runnable { private int i = 0; @Override public void run() { for (i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } } } public class ThreadTest { public static void main(String[] args) { Runnable myRunnable = new MyRunnable(); // 創建一個Runnable實現類的對象 Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創建新的線程 thread1.start(); // 調用start()方法使得線程進入就緒狀態 } } } 3、使用Callable和Future接口創建線程。具體是創建Callable接口的實現類,並實現clall()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象作為Thread對象的target來創建線程 public class ThreadTest { public static void main(String[] args) { Callable<Integer> myCallable = new MyCallable(); // 創建MyCallable對象 FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 30) { Thread thread = new Thread(ft); //FutureTask對象作為Thread對象的target創建新的線程 thread.start(); //線程進入到就緒狀態 } } System.out.println("主線程for循環執行完畢.."); try { int sum = ft.get(); //取得新創建的新線程中的call()方法返回的結果 System.out.println("sum = " + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class MyCallable implements Callable<Integer> { private int i = 0; // 與run()方法不同的是,call()方法具有返回值 @Override public Integer call() { int sum = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); sum += i; } return sum; } } 繼承Thread和實現Runnable接口實現多線程的區別: 繼承Thread類、實現Runnable接口,在程序開發中只要是多線程,肯定永遠以實現Runnable接口為主,因為實現Runnable接口相比繼承Thread類有如下優勢: 1、可以避免由於Java的單繼承特性而帶來的局限; 2、增強程序的健壯性,代碼能夠被多個線程共享,代碼與數據是獨立的; 3、適合多個相同程序代碼的線程區處理同一資源的情況。 線程的優先級別: java線程可以通過setPriority()方法對其設定一個優先級別,高優先級別的線程比低優先級別的線程有更高的幾率得到先執行,優先級可以用0到10的整數表示,0為最低優先級別、10為最高優先級別。當線程調度器決定那個線程需要調度時,會根據這個優先級進行調度選擇;1)Thread類有三個優先級靜態常量:MAX_PRIORITY為10,為線程最高優先級;MIN_PRIORITY取值為1,為線程最低優先級;NORM_PRIORITY取值為5,為線程中間位置的優先級。默認情況下,線程的優先級為NORM_PRIORITY。2)一般來說優先級別高的線程先獲取cpu資源先運行,但特殊情況下由於現在的計算器都是多核多線程的配置,有可能優先級低的線程先執行,具體的執行還是看JVM調度來決定。 幾種線程同步的方法: 1、使用synchronized獲取對象互斥鎖:這種方式是最常用也比較安全的一種方式,采用synchronized修飾符實現的同步機制叫做互斥鎖機制,它所獲得的鎖叫做互斥鎖。每個對象都有一個monitor(鎖標記),當線程擁有這個鎖標記時才能訪問這個資源,沒有鎖標記便進入鎖池。任何一個對象系統都會為其創建一個互斥鎖,這個鎖是為了分配給線程的,防止打斷原子操作。每個對象的鎖只能分配給一個線程,因此叫做互斥鎖。我們在使用同步的時候進來爸鎖的粒度控制的精細一點,有時候沒必要鎖整個方法,只需要鎖一個代碼塊即可達到我們的業務需求,這樣避免其他線程阻塞時間過長造成性能上的影響。 package per.thread; import java.io.IOException; public class Test { private int i = 0; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test = new Test(); Test.MyThread thread1 = test.new MyThread(); Test.MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("線程"+Thread.currentThread().getName()+"進入睡眠狀態"); Thread.currentThread().sleep(10000); } catch (InterruptedException e) { // TODO: handle exception } System.out.println("線程"+Thread.currentThread().getName()+"睡眠結束"); i++; System.out.println("i:"+i); } } } } 2、使用特殊域變量volatile實現線程同步:volatile修飾的變量是一種稍弱的同步機制,因為每個線程中的成員變量都會對這個對象的一個私有拷貝,每個線程獲取的數據都是從私有拷貝內存中獲取,而volatile修飾之后代表這個變量只能從共享內存中獲取,禁止私有拷貝。在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比synchronized關鍵字更輕量級的同步機制。從內存的可見性上來看,寫入volatile變量相當於退出同步代碼塊,而讀取volatile變量相當於進入同步代碼塊。但代碼中過度依賴於volatile變量來控制同步狀態,往往比使用鎖更加不安全,使用同步機制會更安全一些。當且僅當滿足以下所有條件時,才應該使用volatile變量: 1、對變量的寫入操作不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。 2、該變量沒有包含在具有其他變量的不變式中。 class Bank { //需要同步的變量加上volatile private volatile int account = 100; public int getAccount() { return account; } //這里不再需要synchronized public void save(int money) { account += money; } } 3、使用重入鎖Lock實現線程同步: 在jdk1.5以后java.util.concurrent.locks包下提供了這一種方式來實現同步訪問。因為synchronized同步之后會存在一個阻塞的過程,如果這個阻塞的時間過久,嚴重影響我們代碼的質量以及帶來系統性能上的問題。因為我們需要一種機制,讓等待的線程到達一定時間之后能夠響應中斷,這就是Lock的作用。另外lock還可以知道線程有沒有成功獲取到對象鎖,synchronized無法做到。Lock比synchronized提供更多的功能。但要注意的是:1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;2)Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之后,系統會自動讓線程釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。 package com.dylan.thread.ch2.c04.task; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * This class simulates a print queue * */ public class PrintQueue { /** * 創建一個ReentrantLock實例 */ private final Lock queueLock=new ReentrantLock(); /** * Method that prints a document * @param document document to print */ public void printJob(Object document){ //獲得鎖 queueLock.lock(); try { Long duration=(long)(Math.random()*10000); System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",Thread.currentThread().getName(),(duration/1000)); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { //釋放鎖 queueLock.unlock(); } } } 注:關於Lock對象和synchronized關鍵字的選擇: a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,能夠幫助用戶處理所有與鎖相關的代碼。 b.如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼 c.如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally代碼釋放鎖 4、使用ThreadLocal管理變量:如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產生影響;ThreadLocal 類的常用方法: ThreadLocal() : 創建一個線程本地變量 get() : 返回此線程局部變量的當前線程副本中的值 initialValue() : 返回此線程局部變量的當前線程的"初始值" set(T value) : 將此線程局部變量的當前線程副本中的值設置為value public class Bank{ //使用ThreadLocal類管理共享變量account private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){ @Override protected Integer initialValue(){ return 100; } }; public void save(int money){ account.set(account.get()+money); } public int getAccount(){ return account.get(); } } 5、使用阻塞隊列實現線程同步:自從Java 1.5之后,在java.util.concurrent包下提供了若干個阻塞隊列,或Redis消息隊列等來實現同步等等。。。 java多線程並發的業務場景:在互聯網的大環境下很多場景都對並發要求越來越高,像天貓雙十一秒殺、春運火車票搶票、微信搶紅包、以及一些業務對某種資源的請求數量的控制、以及一些業務需要整個系統的輸入輸出順序一致性等問題,這些都需要考慮到並發導致的數據安全性問題。