1.多線程使用基礎

前記:師夷長技以自強

1.JDK中支持的類和接口

Thread:

線程類的聲明部分如下:

public class Thread
extends Object
implements Runnable

可以看出Thread是一個直接繼承自Object的類,並且實現了Runnable接口。
根據JDK文檔可知,創建一個線程的方式有兩種:
(1)繼承Thread類;
(2)把實現Runnable接口的類的對象作爲Thread類構造函數參數。
ex1(繼承):

public class ThreadInheri {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        System.out.println("MyThread running!");
    }
}

output:
MyThread running!
ex2(接口):

public class ThreadImp {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        new Thread(myThread1).start();
    }
}

class MyThread1 implements Runnable{

    @Override
    public void run() {
        System.out.println("MyThread1 running!");
    }
}

output:
MyThread1 running!
Runnable接口
當欲創建的進程類已經有了繼承的父類時,由於java語言的單繼承性就,此時可以通過實現Runnable接口來創建線程類。
注:Thread類作爲實現了RUnnable接口的類也可以傳遞給Thread類的構造函數,此時實際上是將一個Thread對象中的run方法交由其他的線程進行調用。

2.線程的特性

啓動的隨機性
執行start()方法的順序不代表線程啓動的順序
ex3:

public class Test {
    public static void main(String[] args) {
        MyThread t1 = new MyThread(1);
        MyThread t2 = new MyThread(2);
        MyThread t3 = new MyThread(3);
        MyThread t4 = new MyThread(4);
        MyThread t5 = new MyThread(5);
        MyThread t6 = new MyThread(6);
        MyThread t7 = new MyThread(7);
        MyThread t8 = new MyThread(8);
        MyThread t9 = new MyThread(9);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        t7.start();
        t8.start();
        t9.start();
    }
}

class MyThread extends Thread{
    private int i;

    public MyThread(int i) {
        super();
        this.i = i;
    }

    @Override
    public void run() {
        super.run();
        System.out.println(i);
    }
}



output:
4
8
2
6
1
5
9
3
7
運行的異步性
多線程(包括main線程)併發執行的過程中相互競爭處理器運行。
ex4:

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.setName("myThread");
        thread.start();

        try {
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("main="+Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {

        try {
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("run=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}



output:
main=main
run=myThread
main=main
run=myThread
run=myThread
main=main
run=myThread
main=main
run=myThread
run=myThread
main=main
main=main
run=myThread
main=main
run=myThread
main=main
run=myThread
main=main
main=main
run=myThread
注:使用start方法調用線程是異步的,其本質是把線程改爲就緒狀態,但是否馬上運行還得看線程調度器。如果直接調用run方法,則線程的運行時同步的。

3.線程安全

非線程安全
非線程安全主要是指多個線程對同一個對象中的同一個實力變量進行操作時會出現值被更改、值不同步的情況。
ex5:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread a = new Thread(myThread, "A");
        Thread b = new Thread(myThread, "B");
        Thread c = new Thread(myThread, "C");
        Thread d = new Thread(myThread, "D");
        Thread e = new Thread(myThread, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

class MyThread extends Thread{
    private int count = 5;

    @Override
    public void run() {
        count--;
        System.out.println(currentThread().getName()+":count="+count);
    }
}



output:
C:count=3
A:count=3
D:count=2
B:count=1
E:count=0
在上例中,因爲多個線程都改寫了變量count,導致線程C和A訪問時引發線程安全問題。其原因是線程C輸出的值是經過線程C和A修改的,也就是線程C在執行run方法時被線程A的run方法所打斷。
synchronized使用此關鍵字可以把run方法變爲臨界區,一次只允許一個訪問,把異步執行變爲同步執行。如下所示:

ex6:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread a = new Thread(myThread, "A");
        Thread b = new Thread(myThread, "B");
        Thread c = new Thread(myThread, "C");
        Thread d = new Thread(myThread, "D");
        Thread e = new Thread(myThread, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

class MyThread extends Thread{
    private int count = 5;

    @Override
    synchronized public void run() {
        count--;
        System.out.println(currentThread().getName()+":count="+count);
    }
}

4.相關API

currentThread
返回代碼段正在被哪個線程調用的信息。
ex7:

class MyThread extends Thread{
   public MyThread() {
       System.out.println("MyThread Constructed:"+currentThread().getName());
   }

   @Override
   public void run() {
       super.run();
       System.out.println("MyThread run:"+currentThread().getName());
   }
}

public class Test{
   public static void main(String[] args) {
       MyThread myThread = new MyThread();
       myThread.start();
   }
}

output:
MyThread Constructed:main
MyThread run:Thread-0
可以看到構造函數是被main線程調用的,run函數是被Thread-0線程調用的。
isAlive
測試線程是否屬於激活狀態,即已啓動尚未終止的狀態。
sleep
在指定的毫秒數內讓當前“正在執行的線程”(this.currentThread()返回的線程)休眠。
getId
取得線程的唯一標識。

4.1停止線程

this.interrupted測試當前線程是否已中斷(運行this.interrrupted方法的線程)。值得注意的是,這個方法被調用後會清除線程的中斷狀態。如下所示:
ex8:


public class Test{
    public static void main(String[] args){
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());
        System.out.println(Thread.interrupted());
    }
}

output:
true
false
isInterrupted
測試製定的線程是否已經停止,這個方法是不清除線程的中斷狀態的。如下所示:
ex9:

class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 50000; i++) {
            System.out.println("i="+(i+1));
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(100);
        myThread.interrupt();
        System.out.println("is interrupted? "+myThread.isInterrupted());
        System.out.println("is interrupted? "+myThread.isInterrupted());
    }
}

output:(只顯示了部分)
is interrupted? true
is interrupted? true
那麼,究竟如何停止線程呢?
異常法
ex10:

class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 500000; i++) {
                if(this.interrupted()){
                    System.out.println("I am interrupted!!!");
                    throw new InterruptedException();
                }
                System.out.println("i="+(i+1));
            }
            System.out.println("I am under for loop.");
        } catch (InterruptedException e) {
            System.out.println("I come into catch!!!");
            e.printStackTrace();
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(1000);
        myThread.interrupt();
    }
}

output:(部分)
i=75920
I am interrupted!!!
I come into catch!!!
暴力停止
ex11

class MyThread extends Thread{
    private int i = 0;

    @Override
    public void run() {
        super.run();
        try {
            while (true){
                i++;
                System.out.println("i="+i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Test{
    public static void main(String[] args){
        try {
            MyThread myThread = new MyThread();
            myThread.start();
            Thread.sleep(8000);
            myThread.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
需要指出的是stop方法已經被廢棄,一來強行關閉線程導致某些清理工作不能進行,二來對鎖定的對象進行了“解鎖”而導致數據得不到同步處理。比如下面的情況:
ex12

class SynchronizedObject{
    private String username="a";
    private String password="aa";

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    synchronized public void printString(String username, String password){
        try {
            this.username = username;
            Thread.sleep(100000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
class MyThread extends Thread{
    private SynchronizedObject object;

    public MyThread(SynchronizedObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        object.printString("b","bb");
    }
}

public class Test{
    public static void main(String[] args){
        try {
            SynchronizedObject object = new SynchronizedObject();
            MyThread myThread = new MyThread(object);
            myThread.start();
            Thread.sleep(500);
            myThread.stop();
            System.out.println(object.getUsername()+" "+object.getPassword());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:
b aa
return法
ex13:

class MyThread extends Thread{
    @Override
    public void run() {
        while (true){
            if(this.isInterrupted()){
                System.out.println("I am interrupted!!!");
                return;
            }
            System.out.println(System.currentTimeMillis());
        }

    }
}

public class Test{
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:(部分)
1591368524361
1591368524362
I am interrupted!!!

4.2暫停線程

暫停的線程可以繼續恢復,可以使用suspend()暫停線程,使用resume()恢復線程的執行。
ex14:

import org.omg.PortableServer.THREAD_POLICY_ID;

class MyThread extends Thread{
    private long i = 0;

    public long getI() {
        return i;
    }

    public void setI(long i) {
        this.i = i;
    }

    @Override
    public void run() {
        while (true){
            i++;
        }
    }
}

public class Test{
    public static void main(String[] args) {
        try {
            MyThread myThread = new MyThread();
            myThread.start();
            Thread.sleep(5000);

            myThread.suspend();
            System.out.println("A="+System.currentTimeMillis()+" i="+myThread.getI());
            Thread.sleep(5000);
            System.out.println("A="+System.currentTimeMillis()+" i="+myThread.getI());

            myThread.resume();
            Thread.sleep(5000);

            myThread.suspend();
            System.out.println("B="+System.currentTimeMillis()+" i="+myThread.getI());
            Thread.sleep(5000);
            System.out.println("B="+System.currentTimeMillis()+" i="+myThread.getI());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:
A=1591406886243 i=1860497709
A=1591406891243 i=1860497709
B=1591406896243 i=3817579030
B=1591406901244 i=3817579030
可以看到程序被成功暫停了,並且可以恢復成運行狀態。
獨佔然而suspend和resume是被廢除的兩個方法,因爲可能會引起共享的同步對象的獨佔,如下:
ex14:


class SynchronizedObject{
    synchronized public void printString(){
        System.out.println("begin!");
        if(Thread.currentThread().getName().equals("a")){
            System.out.println("a thread suspend forever!");
            Thread.currentThread().suspend();
        }
        System.out.println("end!");
    }
}

public class Test{
    public static void main(String[] args) {
        try {
            SynchronizedObject object = new SynchronizedObject();
            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    object.printString();
                }
            };
            thread1.setName("a");
            thread1.start();
            Thread.sleep(1000);
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    System.out.println("Thread2 start");
                    object.printString();
                }
            };
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:
begin!
a thread suspend forever!
Thread2 start
由上面的結果可以看出,當thread2執行run方法時是不能進入object.printString的。在java中println函數內部自帶同步鎖,使用suspend也有可能造成其同步鎖未被釋放,如下
ex15:

public class Test{
    public static void main(String[] args) {
        try {
            Thread thread = new Thread() {
                private long i = 0;

                @Override
                public void run() {
                    while (true) {
                        i++;
                        System.out.println(i);
                    }
                }
            };
            thread.start();
            Thread.sleep(1000);
            thread.suspend();
            System.out.println("main end!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:(部分)
85552
85553
85554
可以看到main函數中的打印語句沒有被執行。
不同步
如果suspend出現在事務操作中間,則會引起值的不同步現象。

4.3主動讓CPU

yield方法作用是放棄當前的CPU資源,並從新參與cpu的競爭。在下例中,yield的頻繁調用讓cpu頻繁讓出,使程序的運行時間延長。
ex16:

class MyThread extends Thread{
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 500000000; i++) {

            count = count + i;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("time cost:"+(endTime - beginTime)+"ms");
    }
}

public class Test{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

output:
time cost:236ms
加上yield後
ex17:

class MyThread extends Thread{
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 500000000; i++) {
            Thread.yield();
            count = count + i;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("time cost:"+(endTime - beginTime)+"ms");
    }
}

public class Test{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

output:
time cost:102355ms

4.4優先級

線程優先級存在繼承。
setPriority設置線程優先級
getPriority獲取線程優先級

class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread1 run priority="+this.getPriority());
        new MyThread2().start();
    }
}

class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread2 run priority="+this.getPriority());
    }
}
public class Test{
    public static void main(String[] args) {
        System.out.println("main run priority="+Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(6);
        System.out.println("main run priority="+Thread.currentThread().getPriority());
        MyThread1 myThread1 = new MyThread1();
        myThread1.start();

    }
}

output:
main run priority=5
main run priority=6
MyThread1 run priority=6
MyThread2 run priority=6

4.5守護線程

守護線程爲其他線程服務,若此時沒有非守護線程了,則自動銷燬。
Daemon

class MyThread extends Thread{
    private int i = 0;

    @Override
    public void run() {
        try {
            while (true){
                i++;
                System.out.println("i="+(i));
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Test{
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("haha!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

output:
i=1
i=2
i=3
i=4
i=5
haha!

5.總結

本文先後講了線程的基本概念、特性、線程安全還有JDK對線程操作的支持API,這些都是線程使用的基礎。而線程安全是併發的重中之重,下一篇將講如何對對象和變量併發訪問。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章