Java多線程學習(一)

本文章首發微信公衆號:IT筆記分享java

掃碼關注我 😊 git

一、進程和線程

1.1 進程

進程是操做系統運行程序的基本單位,是一次程序的執行。簡單來講一個進程就是一個運行中的程序。github

1.2 線程

線程能夠認爲是在進程中獨立運行的子任務。一個進程會有多個線程。安全

1.3 進程和線程區別

進程和線程最大區別就是,各個進程是獨立的,而線程卻不必定,同一進程中的線程多是相互影響的。進程屬於操做心痛範圍的,同一時間會運行多個程序,每一個進程上的又會有多個線程在執行同個或不一樣的任務。bash

1.4 多線程

多線程就是多個線程同時運行或交替運行。單核CPU的話是順序執行,也就是交替運行。多核CPU的話,由於每一個CPU有本身的運算器,因此在多個CPU中能夠同時運行。微信

二、使用多線程

在Java的JDK開發包中,實現多線程主要有兩種,一種是繼承Thread類,另外一種是實現Runnable接口。其實還有不少事項多線程方法,例如:使用ExecutorService、Callable、Future實現由返回結果的多線程,這是經過線程池建立的任務,能夠參考《一篇搞懂線程池》。如今咱們只來具體來說一下前兩種方法的使用。多線程

2.1 繼承Thread類

在建立多線程前,咱們先來看一下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方法。

2.3 實現Runnable接口

建立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的構造方法。

三、線程安全

自定義的實例變量有共享和不共享之分,在多線程交互的時候很重要。

3.1 不共享數據

建立一個類繼承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變量,彼此變量不共享。

3.2 共享數據

共享數據就是多個線程同時訪問同一個變量,好比買火車票,多個線程同時操做剩餘票數。

3.2.1 線程不安全

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--操做分如下幾步:

  1. 獲取原有i值;
  2. 計算i-1;
  3. 堆i賦值。

在這三步中,若是有多線程同時訪問,就會出現線程不安全狀況。

3.2.2 線程安全

咱們來看看線程安全的代碼:

@Override
    public void run() {
        super.run();
        synchronized (ShareThread.class){
            count--;
            System.out.println("線程" + this.currentThread().getName() + ":count=" + count);
        }

    }
複製代碼

咱們加上synchronized代碼塊加鎖,那麼只有拿到所鎖的線程才能夠執行代碼塊的內容,沒拿到則不斷嘗試獲取鎖,知道拿到爲止。

代碼Github

相關文章
相關標籤/搜索