經過Thread類或Runnable接口建立線程對象以後進入初始狀態;調用start方法進入可運行狀態(就緒狀態),此時並非真正的運行,只是表明已經作好了運行前的各項裝備;若是此線程獲取到cpu的時間片,則進入到真正的可運行狀態,執行run方法裏面的業務邏輯;若是run方法執行完畢或調用stop方法則線程運行結束,進入死亡狀態;在運行狀態時調用不一樣方法也會進入其餘不一樣狀態,若是調用強制運行方法join或休眠方法將進入等待狀態,時間到後自動進入就緒狀態,隨時準備獲取cpu時間片;若是看到synchronized則進入同步隊列等待狀態,或者若是調用了wait方法則進入等待狀態,等待狀態的線程必需要經過notify喚醒纔可進入等待狀態,若是其它線程執行完畢,本線程拿到同步鎖則進入就緒狀態,等待獲取cpu時間片。某個線程是否會執行只能看它可否爭搶到cpu時間片,可是經過調高優先級來讓線程更大機率的被優先執行。 參考文檔:https://mp.weixin.qq.com/s?src=11×tamp=1513562547&ver=581&signature=30FEkCCQvF3E1tt67vYVym5tRNsSk3d8HGe0v9TAonJmhLh4-53fDEBbgwNFOlgp5rAlGFAJQXYnviaFRwiQ9NmbtIWnZGpotGcuV0Ok*3WzWxg4X6e2mxU0JrgbRb&new=1html
多線程運行的原理是:cpu在線程中作時間片的切換。cpu負責程序的執行,在每一個時間點它其實只能運行一個程序而不是多個程序,不停的在多個程序之間高速切換,而一個程序其實就是一個進程即多個線程,說到底其實就是cpu在多個線程之間不停的作高速切換,而開多個線程就是不讓cpu歇着,最大程度的壓榨它來爲程序服務。實現多線程有三種方式:繼承Thread類;實現Runnable接口;使用線程池。java
public class MyExtendsThread extends Thread {
String flag;
public MyExtendsThread(String flag){
this.flag = flag;
}
@Override
public void run(){
String name = Thread.currentThread().getName();
System.out.println("線程"+name+"開始工做了...");
Random random = new Random();
for (int i = 0;i < 20;i++){
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(name+"============="+flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t0 = new MyExtendsThread("t0");
Thread t1 = new MyExtendsThread("t1");
t0.start();
t1.start();
// t0.run();
// t1.run();
}
}
複製代碼
調用線程要用start方法,而不是run方法,使用run方法只是調用方法,實際執行的仍是Main線程,而調用start方法能夠明顯的看到線程爭搶。git
public class MyThreadImplementRunnable implements Runnable {
int x;
public MyThreadImplementRunnable(int x) {
this.x = x;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("線程"+name+"開始執行");
Random random = new Random();
for(int i = 0;i<20;i++){
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(name+"============="+x);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThreadImplementRunnable(1),"線程1");
Thread t2 = new Thread(new MyThreadImplementRunnable(2),"線程2");
t1.start();
t2.start();
}
}
複製代碼
public class MyThreadImplementCallable implements Callable<String> {
String name;
public MyThreadImplementCallable(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"開始工做==============");
Random random = new Random();
Thread.sleep(random.nextInt(5)*100); //模擬執行業務
return name+":執行完成";
}
public static void main(String[] args) throws Exception{
MyThreadImplementCallable callable = new MyThreadImplementCallable("測試");
FutureTask<String> futureTask = new FutureTask<String>(callable);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); //獲取任務線程執行結果
System.out.println("線程的執行結果:"+result);
}
}
複製代碼
見下面的線程池專講。 參考文檔:https://www.cnblogs.com/langtianya/archive/2013/03/14/2959713.htmlgithub
public class MySynchronized {
public static void main(String[] args){
final MySynchronized synchronized1 = new MySynchronized();
final MySynchronized synchronized2 = new MySynchronized();
new Thread("thread1"){
@Override
public void run(){
synchronized (synchronized1){
try {
System.out.println(this.getName()+":start");
Thread.sleep(1000);
System.out.println(this.getName()+":wake up");
System.out.println(this.getName()+":end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread("thread2"){
@Override
public void run() {
synchronized (synchronized1){ //爭搶同一把鎖時,線程1沒釋放以前,線程2只能等待
// synchronized (synchronized2){ //若是不是一把鎖,能夠看到兩句話交叉打印,發生爭搶
System.out.println(this.getName()+":start");
System.out.println(this.getName()+":end");
}
}
}.start();
}
}
複製代碼
synchronized是java中的關鍵字,屬於java語言的內置特性。若是一個代碼塊使用synchronized修飾,則這塊代碼是同步的,當一個線程獲取到這個鎖而且開始執行時,其它線程只能一直眼睜睜的等着這個線程執行而後釋放鎖,其中釋放鎖只有兩種緣由:1.線程正常執行完畢;2.線程執行時發生異常,jvm自動將鎖釋放。能夠看到使用synchronized關鍵字以後每一個時刻只會有一個線程執行代碼塊裏面的共享代碼,線程安全;缺點也很明顯,其它線程只能等鎖釋放,資源浪費嚴重。編程
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
}
複製代碼
lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用來獲取鎖的。 unLock()方法是用來釋放鎖的。緩存
public class MyLock {
private static ArrayList<Integer> arrayList = new ArrayList<Integer>();
private static Lock lock = new ReentrantLock();
public static <E> void main(String[] args) {
new Thread() {
@Override
public void run() {
Thread thread = Thread.currentThread();
lock.lock(); //獲取鎖
try {
System.out.println(thread.getName() + "獲得了鎖");
for (int i = 0; i < 5; i++) {
arrayList.add(i);
}
} catch (Exception e) {
} finally {
System.out.println(thread.getName() + "釋放了鎖");
lock.unlock(); //釋放鎖
}
};
}.start();
new Thread() {
@Override
public void run() {
Thread thread = Thread.currentThread();
lock.lock();
try {
System.out.println(thread.getName() + "獲得了鎖");
for (int i = 0; i < 5; i++) {
arrayList.add(i);
}
} catch (Exception e) {
} finally {
System.out.println(thread.getName() + "釋放了鎖");
lock.unlock();
}
};
}.start();
}
}
複製代碼
//觀察現象:一個線程得到鎖後,另外一個線程取不到鎖,不會一直等待
public class MyTryLock {
private static List<Integer> arrayList = new ArrayList<Integer>();
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread("線程1") {
@Override
public void run() {
Thread thread = Thread.currentThread();
boolean tryLock = lock.tryLock();
System.out.println(thread.getName()+"======="+tryLock);
if(tryLock){
try {
System.out.println(thread.getName() + "獲得了鎖");
for(int i = 0;i < 20;i++){
arrayList.add(i);
}
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(thread.getName() + "釋放了鎖");
}
}
}
}.start();
new Thread("線程2") {
@Override
public void run() {
Thread thread = Thread.currentThread();
boolean tryLock = lock.tryLock();
System.out.println(thread.getName()+"======="+tryLock);
if(tryLock){
try {
System.out.println(thread.getName() + "獲得了鎖");
for(int i = 0;i < 20;i++){
arrayList.add(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(thread.getName() + "釋放了鎖");
}
}
}
}.start();
}
}
複製代碼
線程1和線程2共享成員變量arrayList,當線程1獲取鎖的時候,線程2就獲取不到鎖,沒辦法執行它的業務邏輯,只有等線程1執行完畢,釋放了鎖,線程2才能獲取鎖,執行它的代碼,進而保證了線程安全。安全
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
複製代碼
一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操做分開,分紅2個鎖來分配給線程,從而使得多個線程能夠同時進行讀操做。bash
/**
* @author 劉俊重
* 若是有一個線程已經佔用了讀鎖,則此時其餘線程若是要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。
* 若是有一個線程已經佔用了寫鎖,則此時其餘線程若是申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。
*/
public class MyReentrantReadWriteLock {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public static void main(String[] args) {
final MyReentrantReadWriteLock myTest = new MyReentrantReadWriteLock();
new Thread("線程1"){
@Override
public void run(){
myTest.read(Thread.currentThread());
myTest.writer(Thread.currentThread());
}
}.start();
new Thread("線程2"){
@Override
public void run(){
myTest.read(Thread.currentThread());
myTest.writer(Thread.currentThread());
}
}.start();
}
/**
* @Description 讀方法
* @Author 劉俊重
* @Date 2017/12/18
*/
private void read(Thread thread){
readWriteLock.readLock().lock();
try {
long start = System.currentTimeMillis();
while (System.currentTimeMillis()-start<=1){
System.out.println(thread.getName()+"===正在執行讀操做");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
System.out.println(thread.getName()+"==釋放讀鎖");
}
}
/**
* @Description 寫方法
* @Author 劉俊重
* @Date 2017/12/18
*/
private void writer(Thread thread){
readWriteLock.writeLock().lock();
try {
long start = System.currentTimeMillis();
while (System.currentTimeMillis()-start<=1){
System.out.println(thread.getName()+"===正在執行寫操做");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
System.out.println(thread.getName()+"==釋放寫鎖");
}
}
}
複製代碼
Lock和Synchronized的選擇:多線程
程序執行時有主內存,每一個線程工做時也有本身的工做內存。當一個線程開始工做時會從主內存中拷貝一個變量的副本到工做內存中,在工做內存中操做完副本時再更新回主內存。當存在多線程時,若是工做內存A處理完還沒來得及更新回主內存以前,工做內存B就從主內存中拉取了這個變量,那麼很明顯這個變量並非最新的數據,會出現問題。怎麼解決呢?可使用volatile,volatile有個最顯著的特性就是對它所修飾變量具備可見性,什麼意思呢,就是當一個線程修改了變量的值,新的值會馬上(立刻)同步到主內存中,其它線程使用時拉取到的就是最新的變量值。儘管volatile能保證變量的可見性,但並不能保證線程安全,由於它不能保證原子性。要想線程安全仍是要用同步或者鎖。 有一篇文檔寫volatile寫的很好,貼一下:http://dwz.cn/76TMGW併發
JDK1.5以後引入了高級併發特性,在java.util.concurrent包中,是專門用於多線程併發編程的,充分利用了現代計算機多處理器和多核心的功能以編寫大規模併發應用程序。主要包含原子量、併發集合、同步器、可重入鎖,並對線程池的建立提供了強力的支持。
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//獲取cpu核心數
int num = Runtime.getRuntime().availableProcessors();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(num);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(8);
ScheduledExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
}
複製代碼
說到線程池使用以前再強調一下Runnable的孿生兄弟——Callable,他們兩個很像,只是Runnable的run方法不會有任何返回結果,主線程沒法得到任務線程的返回值;可是Callable的call方法能夠返回結果,可是主線程在獲取時是被阻塞,須要等待任務線程返回才能拿到結果,因此Callable比Runnable更強大,那麼怎麼獲取到這個執行結果呢?答案是Future,使用Future能夠獲取到Callable執行的結果。 如今開始說線程池怎麼使用,也有兩種方式,一種Runnable的,一種Callable的:
public class TestPoolWithRunnable {
public static void main(String[] args) throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i=0;i<10;i++){
Future<?> submit = pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始執行");
}
});
System.out.println("執行結果:"+submit.get()); //全部的執行結果全是null
}
pool.shutdown(); //關閉線程池
}
}
複製代碼
/**
* @author 劉俊重
* Callable 跟Runnable的區別:
* Runnable的run方法不會有任何返回結果,因此主線程沒法得到任務線程的返回值
* Callable的call方法能夠返回結果,可是主線程在獲取時是被阻塞,須要等待任務線程返回才能拿到結果
*/
public class TestPoolWithCallable {
public static void main(String[] args) throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(4);
for(int i=0;i<10;i++){
Future<String> future = pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(500);
return "===="+Thread.currentThread().getName();
}
});
//從Future中get結果,這個方法是會被阻塞的,一直要等到線程任務返回結果
System.out.println("執行結果:"+future.get());
}
pool.shutdown();
}
}
複製代碼
如何解決獲取執行結果阻塞的問題? 在使用future.get()方法獲取結果時,這個方法是阻塞的,怎麼提升效率呢?若是在不要求立馬拿到執行結果的狀況下,能夠先將執行結果放在隊列裏面,待程序執行完畢以後在獲取每一個線程的執行結果,示例代碼以下:
public class TestThreadPool {
public static void main(String[] args) throws Exception{
Future<?> submit = null;
//建立緩存線程池
ExecutorService cachePool = Executors.newCachedThreadPool();
//用來存在Callable執行結果
List<Future<?>> futureList = new ArrayList<Future<?>>();
for(int i = 0;i<10;i++){
//cachePool提交線程,Callable,Runnable無返回值
//submit = cachePool.submit(new TaskCallable(i));
submit = cachePool.submit(new TaskRunnable(i));
//把這些執行結果放到list中,後面再取能夠避免阻塞
futureList.add(submit);
}
cachePool.shutdown();
//打印執行結果
for(Future f : futureList){
boolean done = f.isDone();
System.out.println(done?"已完成":"未完成");
System.out.println("線程返回結果:"+f.get());
}
}
}
複製代碼
把submit放在list集合中,線程直線完畢以後再取。
直接使用new Thread().start()的方式,對於通常場景是沒問題的,但若是是在併發請求很高的狀況下,就會有隱患:
不論是經過Executors建立線程池,仍是經過Spring來管理,都得知道有哪幾種線程池:
由以上線程池類型可知,除了CachedThreadPool其餘線程池都有飽和的可能,當飽和之後就須要相應的策略處理請求線程的任務,好比,達到上限時經過ThreadPoolExecutor.setRejectedExecutionHandler方法設置一個拒絕任務的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy幾種策略。