编程语言
首页 > 编程语言> > 5. JAVA大厂面试第二季-java锁

5. JAVA大厂面试第二季-java锁

作者:互联网

前言

其实我一直有这样一个感觉,用锁来形容lock其实是不恰当的,因为刻板印象里锁是用来锁住东西的,而一个线程获得了锁表示这个线程可以开始执行业务了,而并不是锁住了,停滞住了,所以我更愿意把java中的锁理解为钥匙,一个线程拿到了钥匙,于是乎一个线程就可以开始运行了。

公平锁和非公平锁

公平锁:先来后到,多个线程按照申请锁的顺序来。

非公平锁:允许插队,一上来就直接尝试占有锁,如果尝试失败就采用类似公平锁的那种方式,有可能后申请的线程比先申请的线程先获取锁,在高并发情况下,有可能会造成优先级反转或者饥饿现象,ReentrantLock默认情况是非公平锁,Synchronized也是非公平锁。

可重入锁(递归锁):指的是同一线程外层获得锁之后,内层递归函数依然可以获取该锁的代码,在同一个线程在外层获取锁的时候,进入内层方法会自动获取锁,所有的锁都应该设计为可重入的,这样可以避免死锁问题。类似于拿到大门钥匙就可以打开房子里其他所有的门。、

/**
 * @ Author wuyimin
 * @ Date 2021/9/7-20:11
 * @ Description
 */
public class LockTest {
    public static void main(String[] args) {
        openReentrantLockDoor openDoor = new openReentrantLockDoor();
        new Thread(() -> {
            openDoor.openBigDoor();
        }, "A").start();

        new Thread(() -> {
            openDoor.openBigDoor();
        }, "B").start();
    }

}

class openReentrantLockDoor{
    //两个不同的锁也可以打开
    ReentrantLock lock=new ReentrantLock();
    ReentrantLock lock1=new ReentrantLock();
    public  void openBigDoor() {
        lock.lock();//只要锁配对,两对以上的lock()和unlock()也成立
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"打开了大门");
            openBedroom();
            openBathRoom();
        }catch (Exception e)
        { e.printStackTrace();
        }finally {
        lock.unlock();
        lock.unlock();
        }
    }
    public void openBedroom() {
        lock1.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"打开卧室门");
        }catch (Exception e)
        { e.printStackTrace();
        }finally {
        lock1.unlock();
        }
    }
    public synchronized void openBathRoom() {
        System.out.println(Thread.currentThread().getName()+"打开浴室门");
    }
}

运行结果

A打开了大门
A打开卧室门
A打开浴室门
B打开了大门
B打开卧室门
B打开浴室门

自旋锁

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

/**
 * @ Author wuyimin
 * @ Date 2021/9/7-21:04
 * @ Description 自旋锁--》自旋的本质就是while+compare方法
 */
public class SelfLock {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "锁住");
        while (!atomicReference.compareAndSet(null, thread)) {
        }
    }

    public void myUnLock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName()+"解锁");
    }

    public static void main(String[] args) {
        SelfLock selfLock = new SelfLock();
        new Thread(() -> {
            selfLock.myLock();
            System.out.println("线程A执行业务中");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                selfLock.myUnLock();
            }
        }, "A").start();
        //保证A线程先启动
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            selfLock.myLock();
            System.out.println("线程B执行业务中");
            selfLock.myUnLock();
        }, "B").start();
    }
}

代码运行结果:

A锁住
线程A执行业务中
B锁住
A解锁
线程B执行业务中
B解锁

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

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

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

对于ReentrantReadWriteLock来说其读锁是共享锁,写锁是独占锁。

读读是共享的,读写,写读,写写的过程是互斥的(可以理解为只要有写就是互斥的)

不加锁的情况:

public class ReadWriteLock {
    public static void main(String[] args) {
        MyData myData = new MyData();
        //写的过程
        for (int i = 0; i <5; i++) {
            final int temp=i;
            new Thread(() -> {
                myData.put(temp+"",temp);
            }, "" + i).start();
        }
        //读的过程
        for (int i = 0; i <5; i++) {
            final int temp=i;
            new Thread(() -> {
                myData.get(temp+"");
            }, "" + i).start();
        }
    }
}

//模拟资源类
class MyData {
    private volatile Map<String, Object> map = new HashMap<>();

    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "正在写入。。。" + key);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "正在读取");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完成" + o);
    }
}

运行结果:

0正在写入。。。0
4正在写入。。。4
3正在写入。。。3
2正在写入。。。2
1正在写入。。。1
0正在读取
1正在读取
2正在读取
4正在读取
3正在读取
4写入完成
3写入完成
0写入完成
3读取完成3
4读取完成null
1写入完成
1读取完成null
2读取完成null
0读取完成null
2写入完成

加锁后

public class ReadWriteLock {
    public static void main(String[] args) {
        MyData myData = new MyData();
        //写的过程
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myData.put(temp + "", temp);
            }, "" + i).start();
        }
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myData.get(temp + "");
            }, "" + i).start();
        }
    }
}

//模拟资源类
class MyData {
    private volatile Map<String, Object> map = new HashMap<>();//保证可见性
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        lock.writeLock().lock();//写锁
        try {
            System.out.println(Thread.currentThread().getName() + "正在写入。。。" + key);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void get(String key) {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "正在读取");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完成" + o);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}

运行结果:

0正在写入。。。0
0写入完成
2正在写入。。。2
2写入完成
1正在写入。。。1
1写入完成
3正在写入。。。3
3写入完成
4正在写入。。。4
4写入完成
2正在读取
0正在读取
1正在读取
3正在读取
4正在读取
1读取完成1
3读取完成3
2读取完成2
0读取完成0
4读取完成4

Process finished with exit code 0

标签:java,读取,Thread,lock,写入,线程,第二季,JAVA,public
来源: https://blog.csdn.net/weixin_51211461/article/details/120165547