栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java面试题(JUC&JVM)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java面试题(JUC&JVM)

Java中的锁

1. 公平锁和非公平锁2. 可重入锁(递归锁)3. 独占锁(写锁)和共享锁(读锁)/互斥锁4. 自旋锁5. Synchronized和Lock的区别

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 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的区别

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

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

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

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/770981.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号