Java面试---锁

一、公平锁与非公平锁

1、公平锁

定义:是指多个线程按照申请锁的顺序来获取锁,即按先来后到

2、非公平锁

定义:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或饥饿现象。简单来说就是后申请的线程的优先级较高,可以抢占先申请的线程。其中ReentrantLock默认就是非公平锁。即在创建锁的时候将值设false。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

3、二者区别

公平锁:在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当线程是等待队列的第一个,就站有锁,否则就会加入等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁:比较粗鲁,上来就直接尝试站有锁,如果尝试失败,就再采用类似公平锁那种方式。

非公平锁的优点在于吞吐量比公平锁大,对于Synchronized而言,也是一种非公平锁

二、可重入锁(递归锁)

1、定义

可重入锁:指的是统一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。可重入锁最大的作用是避免死锁。

2、验证可重入锁——ReentrantLock/Synchronized

2.1、Synchronized
public class Phone{

    public synchronized void sendSMS(){
        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
        sendEmail();
    }

    public synchronized void sendEmail(){
        System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");

    }

}
public class ReentrantLockDemo {

    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"t1").start();
         new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"t2").start();

    }

}

运行结果
在这里插入图片描述

在此个例子中,我们在Phone中定义了两个方法,分别加了synchronized锁,可重入锁就是在一个线程持有锁的情况,可以在此请求该锁而不会被阻塞,我们看到t1线程先拿到了锁执行了sengSMS方法,在sendSMS方法中有调用了sendEmail方法,由输出结果可得,t1在执行完sendSMS后再次请求锁是可以直接获取到的。

2.2、ReentrantLock
public class LockDemo implements Runnable {

    Lock lock = new ReentrantLock();

    @Override
    public void run() {

        get();

    }

    public void get() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t invoked get()");
            set();
        }finally {
            lock.unlock();
        }
    }

    private void set() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t invoked set()");
        }finally {
            lock.unlock();
        }

    }
}
public class ReentrantLockDemo {

    public static void main(String[] args) {
        LockDemo demo = new LockDemo();

        Thread t3 = new Thread(demo);
        Thread t4 = new Thread(demo);
        t3.start();
        t4.start();
    }

}

运行结果
在这里插入图片描述
但需要注意:Lock的方法需要手动释放锁,所以当我们最好加锁与解锁两两匹配,否则会导致死锁的现象。

三、自旋锁

1、定义

自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用自旋的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

详细介绍CAS

自旋锁的好处:循环比较获取直到成功为止,没有类似wait的阻塞

2、例子验证

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();  //原子引用线程

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in myLock");
        do {

        }while (!atomicReference.compareAndSet(null,thread));
        System.out.println(Thread.currentThread().getName() + "\t invoked myLock");
    }

    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in myUnlock");

        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock ");

    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            try {
                Thread.sleep(5000);
                System.out.println("休眠5秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        },"AA").start();

        new Thread(()->{
            spinLockDemo.myLock();
            try {
                Thread.sleep(1000);
                System.out.println("休眠1秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        },"BB").start();

    }

}

运行结果

在这里插入图片描述

结果证明,线程AA执行myLock方法进入睡眠状态,但是线程AA没有执行完,线程BB进入自旋状态,直到线程AA执行完之后线程BB才开始执行。

四、独占锁(写锁)/共享锁(读锁)/互斥锁

1、定义

独占锁:指该锁一次只能被一个线程所持有。ReentrantLock和Synchronized都是独占锁

共享锁:指该锁可被多个线程所持有

对于ReentrantReadWriteLock:其读锁是共享锁,其写锁是独占锁

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程都是互斥的。

2、举例验证

public class MyCache {

    private volatile Map<String,Object> map = new HashMap<>();
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key,Object value){

        rwLock.writeLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + " \t 正在写入" + key);
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");

        }catch (Exception e){

        }finally {
            rwLock.writeLock().unlock();
        }
    }

    public void get(String key){
        rwLock.readLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + " \t 正在读取" + key);
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成" + result);

        }catch (Exception e){

        }finally {
            rwLock.readLock().unlock();
        }
    }

}
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for(int i = 1; i <= 5; i++){
            final int tempInt = i;
            new Thread(()->{
                myCache.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }
        for(int i = 1; i <= 5; i++){
            final int tempInt = i;
            new Thread(()->{
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }


}

结果为

在这里插入图片描述

3、总结

读-读:可以共存

读-写:不能共存

写-写:不能共存

写操作:原子+独占,整个过程必须是一个完整的统一体,中间不允许被分割,被打断

五、CountDownLatch

1、定义

CountDownLatch:让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。

2、举例

public enum CountryEnum {

    ONE(1,"唐"),TWO(2,"宋"),THREE(3,"元"),FOUR(4,"明"),FIVE(5,"清");

    @Getter
    private Integer retCode;
    @Getter
    private String retMessage;

    CountryEnum(Integer retCode,String retMessage){
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public static CountryEnum forEach_CountryEnum(int index){
        CountryEnum[] myArray = CountryEnum.values();
        for(CountryEnum element : myArray){
            if(index == element.getRetCode()){
                return element;
            }
        }
        return null;
    }
}
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for(int i = 1; i <= 5; i++){
            new Thread(()->{
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + "朝灭亡");
            },CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
        }
        countDownLatch.await();  //当减为0的时候,被阻止的主线程才能被使用
        System.out.println(Thread.currentThread().getName() + "朝");
    }
}

六、Semaphore

1、定义

信号量主要用于两个目的:一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制

2、举例

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//模拟3个停车位
        for(int i = 0; i <= 6; i++){ //模拟6辆汽车
            new Thread(()->{
                try {
                    semaphore.acquire();

                System.out.println(Thread.currentThread().getName() + "抢到停车位");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "停车3秒后离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();

        }
    }
}

七、阻塞队列

1、阻塞队列种类

类型解释
ArrayBlockingQueue由数组结构组成的有界阻塞队列
LinkedBlockingQueue由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列
PriorityBlockingQueue支持优先级排序的无界阻塞队列
DelayQueue使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue不存储元素的阻塞队列,也即单个元素的队列
LinkedTransferQueue由链表结构组成的无界阻塞队列
LinkedBolckingDegue由链表结构组成的双向阻塞队列

1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列。此队列按FIFO(先进先出)原则对元素进行排序。

2、LinkedBlockingQueue:一个基于链表结构的阻塞队列。此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。

3、SynchronizedQueue:一个不存储元素的阻塞队列,每个输入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高。

当阻塞队列为空时,从队列中获取元素的操作将会被阻塞;

当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。

2、ArrayBlockingQueue的使用

public class BlockingQueueDemo {

    public static void main(String[] args) throws InterruptedException {

        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.add("a");
        blockingQueue.add("b");
        blockingQueue.add("c");
//        blockingQueue.add("x"); //此时阻塞队列已满,再往队里添加元素会抛异常IllegalStateException: Queue full

        blockingQueue.remove();
        blockingQueue.remove();
        blockingQueue.remove();
//        blockingQueue.element(); //当阻塞队列为空时,获取不到元素或报NoSuchElementException
        blockingQueue.offer("a");//成功为true,失败false
        blockingQueue.poll();  //阻塞队列为空时,返回null
        blockingQueue.peek(); //首元素
        blockingQueue.put("a"); //当阻塞队列满时,生产者线程继续往队里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出
        blockingQueue.take(); //当阻塞队列空时,消费者线程试图从队里take元素,队列会一直阻塞消费者线程直到队列可用
        blockingQueue.offer("a",2L, TimeUnit.SECONDS);  //当阻塞队列满时,队列会则色生产者线程一定时间,超过限时后生产者线程退出
        blockingQueue.poll(2L,TimeUnit.SECONDS);

    }
}

3、SynchronousQueue的使用

public class SynchronousQueueDemo {

    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "\t put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "\t put 3");
                blockingQueue.put("3");

            }catch (Exception e){
                e.printStackTrace();
            }
        },"AAA").start();

        new Thread(()->{
            try {
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

            }catch (Exception e){
                e.printStackTrace();
            }
        },"BBB").start();
    }
}

结果为每停止5秒打印一次数据,能得到:

SynchronousQueue是不存储元素的阻塞队列,也即单个元素的队列

没有容量;每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。

八、Synchronized和Lock的区别

1、Synchronized和Lock的区别

SynchronizedReentrantLock
原始构成关键字,属于JVM层面具体类,是api层面的锁
使用方法不需要手动释放锁,执行完后系统会让线程释放对锁的占用需要用户手动释放锁,如若没有释放,可能会导致死锁现象(释放需要lock()和unlock()方法配合try/finally语句块来完成)
等待是否可中断不可中断,除非抛出异常或者正常运行完成可中断 1、设置超时方法try lock (long timeout,TimeUnit unit)、2、lock Interruptibly()放代码块中,调用interrupt()方法可中断
加锁是否公平非公平锁两者都可以,默认为非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
锁绑定多个条件没有用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是想synchronized要么随机唤醒一个线程,要么唤醒全部线程

2、精确唤醒实例

public class ShareResource {

    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();
    public void print5(){  //print10  print15
        lock.lock();
        try{
            //1.判断
            while(number != 1){  // 2  3
                c1.await();
            }
            //2.干活
            for(int i = 1; i <= 5; i++ ){
                System.out.println(Thread.currentThread().getName() +"\t" + i);
            }
            //3.通知
            number = 2;   // 3   1
            c2.signal();  //c3.signal();  c1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for(int i = 1; i <= 10; i++){
                shareResource.print5();
            }
        },"A").start();
    }
}

九、线程通信之生产者消费者阻塞队列的实现

public class MyResource {

    private volatile boolean FLAG = true;   //默认开启  生产+消费   生产一个消费一个,保持线程的可见性
    //使用原子的方式进行加操作,不要用i++,++i
    private AtomicInteger atomicInteger = new AtomicInteger();  //默认值为0
    BlockingQueue<String> blockingQueue = null;  //通顺,适配,通用
    public MyResource(BlockingQueue<String> blockingQueue){
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProd() throws InterruptedException {
        String data = null;
        boolean retValue;
        while (FLAG){
            data = atomicInteger.incrementAndGet() + "";
            retValue = blockingQueue.offer(data,2L, TimeUnit.SECONDS);
            if(retValue){
                System.out.println(Thread.currentThread().getName() + "\t插入队列" + data+ "成功");
            }else {
                System.out.println(Thread.currentThread().getName() + "\t插入队列" + data+ "失败");
            }
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(Thread.currentThread().getName() + "\t FLAG==false 生产动作结束");

    }

    public void myConsumer() throws InterruptedException {
        String result = null;
        while (FLAG){
            result = blockingQueue.poll(2L,TimeUnit.SECONDS);
            if(null == result || result.equalsIgnoreCase(" ")){
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t 超过2秒没有取到,消费退出");
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t 消费队列" + result + "成功");
        }
    }
    public  void stop(){
        this.FLAG=false;
    }
}
public class ProdConsumer_BlockQueueDemo {

    public static void main(String[] args) {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
            try {
                myResource.myProd();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Prod").start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
            try {
                myResource.myConsumer();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Consumer").start();

        System.out.println("活动结束");
        myResource.stop();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值