天天看點

java多線程程式設計1. 多線程程式設計2. Thread和Runnable3.線程安全問題4. 線程同步5. wait/notify 等待/通知機制

1. 多線程程式設計

java多線程程式設計1. 多線程程式設計2. Thread和Runnable3.線程安全問題4. 線程同步5. wait/notify 等待/通知機制

2. Thread和Runnable

 java中實作多線程的方式有兩種,繼承Thread類、實作Runnable接口

2.1 Thread

開發人員可以編寫一個類繼承Thread,并重寫run方法,在run方法裡面編寫線程将要執行的代碼。

建立線程對象後,隻需要調用start()方法即可讓線程進入就緒隊列,等待作業系統排程。

需要特别注意的是排程具有随機性和随時性。也就是說無法确定下一次排程哪個線程,也無法确定什麼時刻進行排程

public class MyThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();//把myThread加入到就緒隊列裡面
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread執行了");
    }
}      

2.2 Runnable

除了繼承Thread重寫run方法外,在簡單的情況下,還可通過實作Runnable接口的方式編寫線程執行的代碼

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Runnable接口方式實作多線程");
    }
});      

3.線程安全問題

一個資料,如一個對象或對象中的某個字段,如果有多個線程可以同時通路它,就可能會出現線程安全問題:資料錯亂、程式出錯或其他無法預知的問題

比如線程1要周遊一個list,線程2要把這個list清空,如果這兩個線程同時執行就可能會出現線程安全問題

public class ThreadQuestionTest {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < list.size(); i++) {
                    System.out.println(list.get(i));
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                list.clear();
            }
        });
        thread1.start();
        thread2.start();
    }
}      

輸出:

0
null
null
null
null
...
           

4. 線程同步

線程同步控制,即使用某種方式使得一個線程在操作完某個資料前,别的線程無法操作這個資料,進而避免多個線程同時操作一個資料,進而避免線程安全問題

線程同步控制的方式有同步鎖機制、等待/通知機制、信号量機制等,它們應用在不同複雜度的場景下

4.1同步代碼塊

synchronized同步鎖機制

Java中每個對象都有一把鎖,同一時刻隻能有一個線程持有這把鎖。線程可以使用synchronized關鍵字向系統申請某個對象的鎖,得到鎖之後,别的線程再申請該鎖時,就隻能等待。持有鎖的線程在這次操作完成後,可以釋放鎖,以便其他線程可以獲得鎖

synchronized有兩種形式,synchronized代碼塊和synchronized方法

synchronized代碼塊,又稱同步代碼塊:

public class SynchronizedBlockTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list) {
                    for (int i = 0; i < list.size(); i++) {
                        System.out.println(list.get(i));
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list) {
                    list.clear();
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}      

View Code

4.2 同步方法

public class SynchronizedMethodTest {

    public static void main(String[] args) {
        Data data = new Data();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                data.bianliList();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                data.clearList();
            }
        });
        thread1.start();
        thread2.start();
    }
}

class Data {
    private List<Integer> list;
    public Data() {
        list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
    }
    public synchronized void bianliList() {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
    public synchronized void clearList() {
        list.clear();
    }
}      

View Code

非靜态同步方法申請的鎖是類的目前對象的鎖,靜态同步方法申請的鎖是類的Class對象的鎖。同步方法執行完後即向系統歸還鎖

synchronized代碼塊和synchronized方法的效果一樣,可根據具體場景靈活選用 

對于簡單的需要線程同步控制的應用場景,synchronized基本夠用

但需要注意,所有需要同步的線程必須都申請同一個對象的鎖,當申請不同的鎖或者有的線程沒有使用synchronized時,同步鎖機制就會失效

5. wait/notify 等待/通知機制

對于稍複雜的情況,比如多個線程需要互相合作有規律的通路共享資料,就可以使用wait/notify機制,即等待/通知機制,也稱等待/喚醒機制

等待/通知機制建立在synchronized同步鎖機制的基礎上,即在同步代碼塊(或同步方法)内,如果目前線程執行了lockObject.wait()(lockObject表示提供鎖的對象),則目前線程立即暫停執行,并被放入阻塞隊列,并向系統歸還所持有的鎖,并在lockObject上等待,直到别的線程調用lockObject.notify()

如果有多個線程在同一個對象上等待,notify()方法隻會随機通知一個等待的線程,也可以使用notifyAll()方法通知所有等待的線程。被通知的線程獲得鎖後會進入就緒隊列

public class WaitNotifyTest {
    public static void main(String[] args) {
        Object lockObject = new Object();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockObject) {
                    try {
                        System.out.println("線程1即将開始在lockObject上等待");
                        lockObject.wait();
                        System.out.println("線程1收到通知并獲得鎖,開始繼續執行");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockObject) {
                    System.out.println("線程2将随機通知在lockObject上等待的線程");
                    lockObject.notify();
                }
            }
        });
        thread2.start();
        thread1.start();
    }
}      

View Code

5.1 wait/notify-生産者消費者執行個體

一個很典型的生産者消費者例子:現有一個生産者、一個消費者、10個盤子(緩沖區),生産者把生産的産品放入空盤子中,當沒有空盤子時就停止生産;消費者消費盤子中的産品,當所有的盤子都是空盤子時就停止消費

public class ProducerConsumerTest{
    public static void main(String[] args) {
        List<Integer> buffer = new LinkedList<Integer>();
        int maxSize = 10;

        Producer producer = new Producer(buffer, maxSize);
        Consumer consumer = new Consumer(buffer);

        producer.start();
        consumer.start();
    }
}
//模拟生産者
class Producer extends Thread {

    private List<Integer> buffer; //緩沖區,表示多個盤子
    private int maxSize; //表示盤子個數
    public Producer(List<Integer> buffer, int maxSize) {
        this.buffer = buffer;
        this.maxSize = maxSize;
    }
    @Override
    public void run() {
        int id = 0;
        while (true) {
            synchronized (buffer) {
                if (buffer.size() < maxSize) {//有空盤子則繼續生産
                    id++;//表示生産了一個産品
                    buffer.add(id); //表示把産品放入一個空盤子中
                    System.out.println("生産産品" + id + "并通知消費者可以消費了");
                    buffer.notify(); //通知消費者有産品可以消費了
                } else {
                    //如果沒有空盤子則等待
                    System.out.println("沒有空盤子了,生産者停止生産産品");
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

//模拟消費者
class Consumer extends Thread {
    private List<Integer> buffer; //緩沖區,表示多個盤子
    public Consumer(List<Integer> buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (buffer) {
                if (buffer.size() > 0) { //有不空的盤子
                    int id = buffer.remove(0); //表示消費了一個産品
                    System.out.println("消費産品" + id + "并通知生産者有空盤了");
                    buffer.notify();
                } else {
                    //全部都是空盤子則等待
                    System.out.println("全部都是空盤子,消費者停止消費産品");
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}      

View Code

轉載于:https://www.cnblogs.com/renjing/p/thread.html