本文章首發微信公衆號:IT筆記分享java
掃碼關注我 😊 git
進程是操做系統運行程序的基本單位,是一次程序的執行。簡單來講一個進程就是一個運行中的程序。github
線程能夠認爲是在進程中獨立運行的子任務。一個進程會有多個線程。安全
進程和線程最大區別就是,各個進程是獨立的,而線程卻不必定,同一進程中的線程多是相互影響的。進程屬於操做心痛範圍的,同一時間會運行多個程序,每一個進程上的又會有多個線程在執行同個或不一樣的任務。bash
多線程就是多個線程同時運行或交替運行。單核CPU的話是順序執行,也就是交替運行。多核CPU的話,由於每一個CPU有本身的運算器,因此在多個CPU中能夠同時運行。微信
在Java的JDK開發包中,實現多線程主要有兩種,一種是繼承Thread類,另外一種是實現Runnable接口。其實還有不少事項多線程方法,例如:使用ExecutorService、Callable、Future實現由返回結果的多線程,這是經過線程池建立的任務,能夠參考《一篇搞懂線程池》。如今咱們只來具體來說一下前兩種方法的使用。多線程
在建立多線程前,咱們先來看一下Thread的類結構圖:ide
能夠看出Thread實現了Runnable接口,其實Runnable和Thread的區別就在於繼承Thread建立多線程不支持多繼承,爲了多繼承徹底可使用Runnable替換,二者建立的線程本質上沒有區別。@FunctionalInterface是Java8的函數式接口,方便lambda表達式使用。函數
咱們建立一個Mythread.java繼承Thread,重寫run方法:this
package main.java.com.xiaosen.Mythread;
/** * @author xiaosen * @date 2019/2/24 10:36 * @description */
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("this is MyThread");
}
}
複製代碼
運行類的代碼以下:
import main.java.com.xiaosen.Mythread.MyThread;
public class Main {
public static void main(String[] args) {
// 繼承Thread
MyThread myThread = new MyThread();
myThread.start();
// lambda表達式的使用
new Thread(() -> System.out.println("this is lambda thread")).start();
System.out.println("Hello World!");
}
}
複製代碼
運行結果以下:
this is MyThread
Hello World!
this is lambda thread
複製代碼
這裏一低昂要注意myThread調用的是start()方法,若是調用mythread.run()那麼就是單純的方法調用,而不是建立線程去執行。關於lambda表達式只給出使用方法,暫不作介紹。
從運行結果能夠看見"Hello World!"的輸出在兩個線程中間,說明使用多線程和代碼的順序無關。CPU會以隨機的時間來調用線程中的run方法。
建立MyRunnable實現Runnable接口:
package main.java.com.xiaosen.myrunnable;
/** * @author xiaosen * @date 2019/2/24 11:20 * @description */
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("run MyRunnable");
}
}
複製代碼
main方法使用:
// 實現Runnable
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println("Hello World!");
複製代碼
輸出內容:
Hello World!
run MyRunnable
複製代碼
使用Runnable方法須要用到Thread的構造方法。
自定義的實例變量有共享和不共享之分,在多線程交互的時候很重要。
建立一個類繼承Thread:
public class NotShareThread extends Thread{
private int count = 5;
public NotShareThread(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count>0){
count--;
System.out.println("線程" + this.currentThread().getName() + ":count=" + count);
}
}
}
public static void main(String[] args){
NotShareThread notShareThread1 = new NotShareThread("A");
NotShareThread notShareThread2 = new NotShareThread("B");
NotShareThread notShareThread3 = new NotShareThread("C");
notShareThread1.start();
notShareThread2.start();
notShareThread3.start();
}
複製代碼
執行結果:
線程A:count=4
線程B:count=4
線程C:count=4
線程B:count=3
線程A:count=3
線程A:count=2
線程A:count=1
線程B:count=2
線程C:count=3
線程B:count=1
線程A:count=0
線程B:count=0
線程C:count=2
線程C:count=1
線程C:count=0
複製代碼
從輸出結果能夠看到每個線程都有各自的count變量,彼此變量不共享。
共享數據就是多個線程同時訪問同一個變量,好比買火車票,多個線程同時操做剩餘票數。
public class ShareThread extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("線程" + this.currentThread().getName() + ":count=" + count);
}
public static void main(String[] args){
ShareThread shareThread = new ShareThread();
Thread a = new Thread(shareThread, "A");
Thread b = new Thread(shareThread, "B");
Thread c = new Thread(shareThread, "C");
Thread d = new Thread(shareThread, "D");
Thread e = new Thread(shareThread, "E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
複製代碼
輸出:
線程B:count=2
線程E:count=0
線程D:count=1
線程C:count=2
線程A:count=2
複製代碼
能夠發現A,B,C三個線程的值都是2,產生非線程安全狀況,咱們應該避免這種狀況發生。這是由於在執行i--操做分如下幾步:
在這三步中,若是有多線程同時訪問,就會出現線程不安全狀況。
咱們來看看線程安全的代碼:
@Override
public void run() {
super.run();
synchronized (ShareThread.class){
count--;
System.out.println("線程" + this.currentThread().getName() + ":count=" + count);
}
}
複製代碼
咱們加上synchronized代碼塊加鎖,那麼只有拿到所鎖的線程才能夠執行代碼塊的內容,沒拿到則不斷嘗試獲取鎖,知道拿到爲止。
代碼Github