Java 高併發編程 重入鎖 && 面試題

public class ReentrantLock5 extends Thread{
    private static Lock lock = new ReentrantLock(true);
    // true 獲得公平鎖
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + i+" 獲得鎖");
            }finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        ReentrantLock5 rl=new ReentrantLock5();
        Thread th1=new Thread(rl);
        Thread th2=new Thread(rl);
        th1.start();
        th2.start();
    }
}
ReentrantLock還可以指定爲公平鎖什麼是公平鎖,什麼是不公平鎖,假設很多個線程訪問同一份資源的時候都要鎖定,
其中某一個線程A如果拿到了,他佔有這把鎖之後,其他人是都訪問不了的,都得在那等着;什麼時候一旦線程A釋放了這把鎖,那麼剩下的線程中哪個線程得到這把鎖,這事說不定,這個要看線程調度器自己去選哪個了,所以這叫競爭鎖;也就是說等待的線程裏面是沒
有公平性可言的;不過這種效率比較高,線程調度器不用計算到底哪個線程等的時間更長,所以默認的synchronized是非公平鎖;
公平鎖就是,誰等的時間長讓誰得到那把鎖。

面試題:生產者消費者程序:  

要求:寫一個固定容量同步容器,擁有put和get方法,以及getCount方法,能夠支持2個生產者線程以及10個消費者線程的阻塞調用

public class ResumeContainer<T> {
    private List<T> lists = new ArrayList<>();
    final private LinkedList<T> list = new LinkedList<>();

    private int max_ = 10;
    private int cnt = 0;

    public synchronized void put(T t){
        while (list.size() == max_){// 對比這裏if 和 while 帶來的區別
            try {
                System.out.println("list size --------------------: " + list.size());
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.add(t);
        System.out.println("producer put " + t);
        cnt++;
        this.notifyAll();// 通知消費者前來消費
    }
    public synchronized T get(){
        T t = null;
        while (list.size() == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        t = list.remove(0);
        cnt --;
        this.notifyAll();
        return t;
    }
    public static void main(String[] args){
        ResumeContainer<Object> container = new ResumeContainer<>();
        // 消費者線程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    System.out.println("consumer get "+container.get());
                }
            }, "get ").start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(() ->{
                for (int j = 0; j < 25; j++) {
                    container.put(j);
                }
            }, "put ").start();
        }
    }
}

使用while

假設容器中已經滿了,如果用的是if,這個線程A發現list.size()==max已經滿了,就this.wait()住了;如果容器中被拿走了元素,線程A被叫醒了,它會從this.wait()開始繼續往下運行,準備執行lists.add(),可是它被叫醒了之後還沒有往裏扔的時候,另外一個線程往list裏面扔了一個,線程A拿到鎖之後不再進行if判斷,而是繼續執行lists.add()就會出問題了;如果用while,this.wait()繼續往下執行的時候需要在while中再檢查一遍,就不會出問題。

改進

public class ResumeContainer<T> {
    private List<T> lists = new ArrayList<>();
    final private LinkedList<T> list = new LinkedList<>();

    private Lock lock = new ReentrantLock();
    private Condition producer = lock.newCondition();
    private Condition consumer = lock.newCondition();

    private int max_ = 10;
    private int cnt = 0;

    public void put(T t){
        try{
            lock.lock();
            while (list.size() == max_){
                System.out.println("=============== list full ================");
                producer.await();
            }
            System.out.println(String.format("=============== put  %s  ================",String.valueOf(t)));
            list.add(t);
            cnt++;
            consumer.signalAll();// 通知消費者線程進行消費
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public T get(){
        T t = null;
        try{
            lock.lock();
            while (list.size()==0){
                consumer.await();
            }
            t = list.removeFirst();
            System.out.println(String.format("=============== get  %s  ================",String.valueOf(t)));
            cnt++;
            producer.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return t;
    }
    public static void main(String[] args){
        ResumeContainer<Object> container = new ResumeContainer<>();
        // 消費者線程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    container.get();
                }
            }, "get ").start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(() ->{
                for (int j = 0; j < 25; j++) {
                    container.put(j);
                }
            }, "put ").start();
        }
    }
}

使用wati和notify寫線程程序的時候就像使用彙編語言一樣,寫起來會比較費勁,使用lock和condition好處在於可以精確的通知那些線程被叫醒,哪些線程不必被叫醒,這個效率顯然要比notifyAll把所有線程全叫醒要高很多。

線程局部變量

public class ThreadLocal {
    volatile static Person p = new Person();
    public static void main(String[] args){
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(p.name);
        }).start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            p.name = "wangwu";
        }).start();
    }
}
class Person{
    public String name = "zhangsan";
}

現在這兩個線程是互相影響的;第二個線程改了名字之後,第一個線程就能讀的到了;有的時候就想線程2的改變,不想讓線程1知道ThreadLocal線程局部變量 * * ThreadLocal是使用空間換時間,synchronized是使用時間換空間  

public class ThreadLocal1 {
    static ThreadLocal<Person> tl = new ThreadLocal<>();
    public static void main(String[] args){
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(tl.get());
        }).start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tl.set(new Person());
            System.out.println(tl.get());
        }).start();
    }
}
class Person{
    public String name = "zhangsan";
}
輸出:
Basic.Person@2ee37501
null

上面代碼的輸出,在兩個線程裏面對person進行了修改,二者得輸出都是不一樣的,也就是同一個變量在倆個線程中是隔離的ThreadLocal的意思就是,tl裏面的變量,自己的線程自己用;你別的線程裏要想用的話,不好意思你自己往裏扔;不能用我線程裏面放的東西;相當於每個線程都有自己的變量,互相之間不會產生衝突;可以理解爲person對象每個線程裏面拷貝了一份,改的都是自己那份,都是自己線程本地的變量,所以空間換時間;ThreadLocal在效率上會更高一些;

原文鏈接

 

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