Java面试题(JUC&JVM)

1. 公平锁和非公平锁

ReentrantLock默认是非公平锁

对于Synchronized也是非公平锁

ReentrantLock lock = new ReentrantLock(false);

公平锁:多个线程按照申请锁的顺序来获取锁,类似排队,先来先服务。

非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发情况下,有可能会造成优先级反转或饥饿现象。优点在于吞吐量比公平锁大

2. 可重入锁(递归锁)

ReentrantLock和Synchronized也是可重入锁

同一线程外城函数获得锁之后,内层递归函数依旧能获取该锁的代码;同一个线程在外层方法获取锁时,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个他已经拥有的锁所同步着的代码块

作用是防止死锁

案例:证明synchronized和ReentrantLock是一个可重入锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Phone implements Runnable{

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

    }

    public synchronized void sendEms(){
        System.out.println(Thread.currentThread().getName() + "\t sendEms...");
        sendEmail();
    }
    
    Lock lock = new ReentrantLock();
    
    @Override
    public void run() {
        get();
    }

    public void get(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t invoke get method...");
            set();
        }catch (Exception e){
           e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void set(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t invoke set method...");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

public class ReentrantLockDemo {

    public static void main(String[] args) {

        Phone phone = new Phone();
        new Thread( () -> {
            phone.sendEms();
        }, "t1").start();

        new Thread( () -> {
            phone.sendEmail();
        }, "t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=============================");
        new Thread( phone,"t3").start();
        
    }
}

请添加图片描述
注意:对于lock和unlock方法需要两两配对,加锁几次,需要解锁几次

3. 独占锁(写锁)和共享锁(读锁)/互斥锁

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

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

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

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

4. 自旋锁

尝试获取锁的线程不会阻塞,而是采用循环的方式去尝试获取锁,好处是减少线程上下文切换的消耗,没有类似wait的阻塞;缺点是循环时间会消耗CPU,只能保证一个共享变量的原子操作,容易引起ABA问题
请添加图片描述
实例分析:通过CAS完成自旋锁,A线程先进来调用Lock方法自定已持有锁5s,B线程随后进来发现当前有线程持有锁,不是null。所以只能通过自旋等待,直到A释放锁B才能抢到

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {
    // 带有Thread泛型的原子引用
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void MyLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "\t lock...");
        // 第一次判断时,满足条件compareAndSet返回true取反之后返回false
        // 则直接跳过 while判断 当第二个线程来调用时候,相反while为true,则会在while处一直判断当前是不是null(自旋)
        while(!atomicReference.compareAndSet(null, thread)){

        }
    }

    public void MyUnlock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(thread.getName() + "\t unlock...");
    }

    public static void main(String[] args) {

        SpinLockDemo lock = new SpinLockDemo();
        new Thread( () -> {
            lock.MyLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.MyUnlock();
        }, "AAA").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        new Thread( () -> {
            lock.MyLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.MyUnlock();
        }, "BBB").start();

    }
}

请添加图片描述

5. Synchronized和Lock的区别

  1. 原始构成:
    Synchronized是关键字,属于JVM层面;底层是monitorenter正常退出和monitorexit异常退出(通过monitor对象来完成),其实wait/notify方法也依赖于monitor对象,只有在同步方法或者同步代码块中才能调用wait/notify等方法
    lock是一个具体类(JUC下的locks包下的类),是API层面的锁

  2. 使用方法:
    synchronized不需要用户手动去释放锁,当synchronized代码执行后系统会自动让线程释放对锁的应用
    ReentrantLock则需要用户手动去释放锁,若没有释放锁,则会出现死锁现象

  3. 等待是否可中断:
    synchronized不可中断,除非抛出异常或者正常运行完毕
    ReentrantLock可以中断:设置超时方法tryLock(timeout,unit)lockInterruptibly()代码中,调用interrupt()方法可中断

  4. 加锁是否公平:
    synchronized是非公平锁
    ReentrantLock默认是非公平锁,构造方法可以传入false或者true,true为公平锁,false为非公平锁

  5. 锁绑定多个条件Condition:
    synchronized没有,synchronized只能唤醒一个或者全部唤醒线程
    ReentrantLock用来实现分组唤醒需要唤醒的线程,做到精确唤醒

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从现在开始壹并超

你的鼓励,我们就是hxd

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值