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在效率上会更高一些;

原文链接

 

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