天天看點

Java線程篇

線程是任務排程和執行的基本機關,可以看做是輕量級的程序,多線程是指在同一程式中有多個順序流在執行,也就是一個程序中同時執行多個線程,兩個或兩個以上的線程對同一個變量的操作.如果兩個線程修改同一個對象的狀态,根據線程通路資料的次序,可能會産生錯誤的資料,也就常說的并發問題.

線程的基本概念與建立

在學線程之前,首先來來了解一下線程與程序的差別:

程序:

  • 是系統資源配置設定的基本機關
  • 每個程序都有獨立的代碼和資料空間,程式切換會有較大的開銷
  • 在作業系統中可以同時運作多個程式
  • 在運作時會為每個程序配置設定不同的記憶體空間

線程:

  • 是任務排程和執行的基本機關
  • 可以看做是輕量級的程序,同一類的線程共享代碼和資料空間,線程之間切換開銷較小
  • 多個線程可以運作在同一程序中
  • 除了CPU,系統不會為線程配置設定記憶體,線程組之間隻能共享資料

線程和程序一樣分為五個階段:建立、就緒、運作、阻塞、終止

建立的線程有四種方式:

  • 繼承Thread
  • 通過實作Runnable接口
  • 通過實作Callable接口
  • 通過線程池建立

1.繼承Thread類

class TestThread extends Thread{
    private volatile int ticket = 10;
    @Override
    public void run() {
        synchronized (this){
            while (ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "賣票操作  ticket = "+ticket--);
            }
        }

    }
}           
public class Test{
    public static void main(String[] args) throws Exception{
        TestThread testThread = new TestThread();
        testThread.run();
    }

}           

運作結果:

Java線程篇

2.實作Runnable接口

經典的生産者和消費者

class Product implements Runnable{//生産者

    private Message message;

    Product(Message message) {
        this.message = message;
    }

    public void run() {
        for (int x = 0 ; x < 100 ; x++){
            if (x % 2 == 0){
                this.message.set("生産者","生産");
            }else {
                this.message.set("消費者","消費");
            }
        }
    }
}
----
class Consumer implements Runnable{//消費者

private Message message;

    public Consumer(Message message) {
        this.message = message;
    }

    public void run() {
        for (int x = 0 ; x < 100 ; x++){
            System.out.println(this.message.get());
        }
    }
}
----
class Message{ //消息中心
    private String title;
    private String content;
    private boolean flag = true;//表示生産或者消費的形式
    // flag = true 允許生産,不允許消費  flag= false 允許消費,不允許生産
    synchronized void set(String title, String content){
        if (!flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.title = title;
        this.content = content;
        this.flag = false; //生産完成
        notify();//喚醒等待的線程
    }
    synchronized String get(){
        if (flag == true){
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            return this.title + "           ------------       "+this.content;
        }finally {
            this.flag = true; // 可繼續生産
            notify();//喚醒等待線程
        }
    }
}
----
public class Producer {
        public static void main(String[] args) throws Exception{
            Message message = new Message();
            new Thread(new Product(message),"生産者").start();//啟動生産者線程
            new Thread(new Consumer(message),"消費者").start();//啟動消費者線程
        }
}           

運作結果

Java線程篇

3.實作Callable接口

class TheThread implements Callable<String>{

    private boolean flag = false;
    @Override
    public String call() throws Exception {
        synchronized (this){
            if (this.flag==false){
                this.flag = true;
                return Thread.currentThread().getName()+"搶答成功";
            }else {
                return Thread.currentThread().getName()+"搶答失敗";
            }
        }
    }
}           
public class Competition {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TheThread theThread = new TheThread();
        FutureTask<String> futureTaskA = new FutureTask<>(theThread);
        FutureTask<String> futureTaskB = new FutureTask<>(theThread);
        FutureTask<String> futureTaskC = new FutureTask<>(theThread);
        new Thread(futureTaskA,"競賽者A").start();
        new Thread(futureTaskB,"競賽者B").start();
        new Thread(futureTaskC,"競賽者C").start();
        System.out.println(futureTaskA.get());
        System.out.println(futureTaskB.get());
        System.out.println(futureTaskC.get());
    }
}           

Java線程篇

4.使用線程池

class ExecutorTest{

    public static void main(String[] args) {

        // 建立線程池
        ThreadPoolExecutor service = new ThreadPoolExecutor(5, 200,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024),
                new ThreadFactoryBuilder().setNameFormat("test-thread-pool-%d").build(),
                new ThreadPoolExecutor.AbortPolicy());
        Runnable runnable = () -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("線程1執行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Runnable runnable1 = () -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("線程2執行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        int num = 10;
        for (int i = 0; i < num; i++) {
            service.execute(runnable);
            service.execute(runnable1);
        }
    }

}           

Java線程篇

以上案例都是處理過的線程,都是通過使用了synchronized鎖保證了原子性,也就是線程安全的操作,線程的安全的實作方式,除了使用鎖之外,也可以用CAS實作無鎖安全操作.

CAS無鎖實作線程安全操作

示例:

class CAS{
    private AtomicInteger ticket = new AtomicInteger(10);
    public void run() {
        while (ticket.get()>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "賣票操作  ticket = "+ticket.getAndDecrement());
        }
    }
}           

----

public class CASTest{
    public static void main(String[] args) throws Exception{
        CAS cas = new CAS();
        cas.run();
    }

}           

程式執行結果

Java線程篇

以上是使用了atomic包下線程安全的AtomicInteger,下面是atomic包下的線程安全類以及部分源碼

Java線程篇

Atomiclnteger本身是個整型,最重要的屬性是value,而value使用了volatile關鍵字,value的作用就是保證可見性,即一個線程修改了共享變量時,其他線程讀取的是修改後的新值.

getAndIncrement()方法實作了自增操作.核心實作是先擷取目前值和目标值如果compareAndSet(current,next)成功傳回該方法的目标值.getAndDecrement()方法則實作了自減操作.

AtomicInteger中的CAS操作就是compareAndSet(),作用是每次從記憶體中根據記憶體偏移量(valueOffset)取出資料,将取出的值跟expect比較,如果資料一緻就把記憶體中的值改為update.這樣就保證了原子操作.

AtomicInteger主要實作了整型的原子操作,防止并發的情況下出現異常結果,内部主要依靠JDK中的unsafe類操作記憶體中的資料實作.volatile修飾符保證了可見性,CAS操作保證了原子操作

volatile關鍵字

若某一屬性使用volatile關鍵字,則表示不操作副本,直接操作原始變量.

volatile關鍵字的兩層含義:

一旦一個共享變量(類的成員變量,類的靜态成員變量)被volatile修飾之後,那麼久具體兩層語義:

  • 保證了不同線程對這個變量進行操作的可見性,即一個線程修改了某個共享變量的值,這個新值對其他線程來說是立即可見的.
  • 禁止進行指令重排序

    禁止指令重排序有兩層意思:

  • 當程式執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
  • 在進行指令優化時,不能将在對volatile變量通路的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。

volatile關鍵字需要結合鎖來使用

class MyThread implements Runnable{
    private volatile int ticket = 10;
    @Override
    public void run() {
        synchronized (this){
            while (ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "賣票操作  ticket = "+ticket--);
            }
        }
    }
}           
public class Volatile{
    public static void main(String[] args) throws Exception{
        MyThread myThread = new MyThread();
        new Thread(myThread,"票販子A").start();
        new Thread(myThread,"票販子B").start();
        new Thread(myThread,"票販子C").start();
    }

}           
Java線程篇

本案例與上面的繼承Thread類的案例是同一個.

在并發程式設計中會遇到三個問題:原子性、可見性、有序性

  • 可見性:

    volatile關鍵字在這裡的作用是當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去記憶體中讀取新值.保證了可見性.普通的共享變量不能保證可見性,因為普通變量被修改之後,什麼時候寫入主存是不确定的,當其他線程去讀的時候,記憶體中可能還是原來的舊值,是以無法保證可見性.通過synchronized和Lock也能夠保證可見性,synchronized和lock能保證同一時刻隻有一個線程擷取鎖然後執行同步代碼,并且在釋放鎖之前會将對變量的修改重新整理到主存中,是以可以保證其可見性.

  • 原子性:

    java的記憶體模型隻保證了基本讀取和指派是原子性操作,如果要實作更大範圍操作的原子性,可以通過synchronized和lock來實作.由于synchronized和lock能夠保證一個時刻隻有一個線程執行該代碼塊,那麼自然不存在原子性問題,進而保證了原子性.

  • 有序性:

    java的記憶體模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程的執行,卻會影響到多線程并發執行的正确性.

可以通過volatile關鍵字來保證一定的"有序性",也可以通過synchronized和lock來保證有序性,synchronized和lock保證每一刻隻有一個線程執行同步代碼,相當于讓線程順序執行同步代碼,自然就保證了有序性.

java記憶體模型具備一些先天的"有序性",即不需要通過任何手段就能夠保證有序性,這個通常也稱為happens-before原則(先行發生原則).如果兩個操作的執行次序無法從happens-before原則推導出來,那麼他們就不能保證它們的有序性,虛拟機可以随意的對它們進行重排序.

happens-before原則(先行發生原則):

  • 程式次序規則:一個線程内,按照代碼順序,書寫在前面的操作先行發生于書寫在後面的操作
  • 鎖定規則:一個unLock操作先行發生于後面對同一個鎖lock操作
  • volatile變量規則:對一個變量的寫操作先行發生于後面對這個變量的讀操作
  • 傳遞規則:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先行發生于操作C
  • 線程啟動規則:Thread對象的start()方法先行發生于此線程的每個一個動作
  • 線程中斷規則:對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生
  • 線程終結規則:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返

    回值手段檢測到線程已經終止執行

  • 對象終結規則:一個對象的初始化完成先行發生于他的finalize()方法的開始

    這8條原則摘自《深入了解Java虛拟機》。

守護線程

public class Daemon {
    public static void main(String[] args) throws Exception{
       Thread userThread = new Thread(() -> {
           for (int x = 0 ; x < 10 ; x++) {
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName() + "正在執行  x = " + x);
              }
            },"執行線程");
        Thread daemonThread = new Thread(() -> {
            for (int x = 0 ; x < Integer.MAX_VALUE ; x++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在執行  x = " + x);
            }
        },"守護線程");
        daemonThread.setDaemon(true);//将daemonThread設為守護線程
        userThread.start();
        daemonThread.start();
    }
}           
Java線程篇

在Thread類提供了以下的守護線程的操作方法,可自行檢視源碼

  • 設定為守護線程:
public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }           
  • 判斷是否為守護線程
public final boolean isDaemon() {
        return daemon;
    }
           

停止線程

public class StopThread {
    public static boolean flag = true;
    public static void main(String[] args) throws Exception{
        new Thread(() -> {
           long num = 0;
            while (flag){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在執行  num = " + num++);
            }
        },"執行線程").start();
        Thread.sleep(200);//線程運作200毫秒
        flag = false;//停止線程
    }
}           
Java線程篇

多線程操作中使用的啟動線程的方法是Thread類的start()方法,原本也提供有stop()方法來停止線程,但在1.2之後已經廢除,現在停止線程的方法可使用上面案例所使用方式.

線程優先級

public static void main(String[] args) {
        Runnable runnable = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("線程1執行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Runnable runnable1 = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("線程2執行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        Thread thread1 = new Thread(runnable1);
        thread.setPriority(1);
        thread1.setPriority(10);
        thread.start();
        thread1.start();           
Java線程篇

在Java程式設計中,每一個線程都有一個優先級,預設情況下,一個線程繼承父線程的優先級,也可以通過設定setPriority方法來改變線程的優先級,優先級設定的範圍是在MIN_PRIORITY(在Thread類中定義為1),MAX_PRIORITY(定義為10)和NORM_PRIORITY(定義為5),這三個優先級的值在Threa類中是三個靜态常量,如下:

  • public static final int MIN_PRIORITY = 1;
  • public static final int NORM_PRIORITY = 5;
  • public static final int MAX_PRIORITY = 10;

    上面線程優先級的示例及運作結果說明,線程的優先級在優先級最高時并不一定會先搶占到資源,線程的優先級是高度依賴于系統的,例如Windows 有7個優先級别。一些Java優先級将映射到相同的作業系統優先級。在Oracle為Linux提供的Java虛拟機中線程的優先級被忽略一所有線程具有相同的優先級.

以上是線程的基本概念以及基礎操作,若有不足或錯誤,歡迎指出.