Java面试——锁

公平锁:是指多个线程按照申请锁的顺序来获取锁,有点先来后到的意思。在并发环境中,每个线程在获取锁时会先查看此锁维护的队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己。
非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,上来就尝试占有锁,如果尝试失败,就再采用类似公平锁的方式获取锁。有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

ReentrantLock:并发包中 ReentrantLock 的创建可以指定构造函数的 boolean 类型来得到公平锁或非公平锁,默认是false(非公平锁)。非公平的优点在于吞吐量比公平锁大。对于Synchronized 锁也是一种非公平锁。

可重入锁(又名递归锁):指同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码。也就是说,线程可以进入任何一个它已经拥有的锁,所同步的代码块。synchronized 和 unlock 都是可重入锁。

//简单理解,就是方法1 是一个同步方法,里面包含了一个方法2 也是同步方法,但是当进入方法1后,也就获得了方法2的锁,即可重入锁
public synchronized void method1(){
	System.out.println("方法1 synchronized");
	method2();
}

public synchronized  void method2(){
	System.out.printf("方法2 synchronized");
}

自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁,这样的好处是减少了上下文切换的消耗,确定是循环会消耗 CPU。循环比较直到成功为止。

public final int getAndAddInt(Object var1, long var2, int var4){
    int var5;
    do{
        //根据对象和地址偏移量获取内存中的值
        var5 = this.getIntVolatile(var1, var2);
    //将获取到的值 var5 传入,此方法内部会先比较var2地址的值是否等于 var5,相等则修改var5值并返回,否则重新进入循环。
    }while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
}

手写一个自旋锁:思想就是通过 while 中的循环条件来充当锁,当条件成立时,表示未获得锁,进行死循环,直到 while 条件不成立,也就是获得锁。就退出死循环,执行业务逻辑。具体查看如下代码:

public class Test {
	public static void main(String[] args) throws Exception {
		/*执行结果展示:  AA   myLock
						BB   myLock
						AA    unLock
						BB    unLock
		 *  分析:我们 AA 线程休眠了 5秒足以让 BB 线程执行结束,那为什么 BB 执行到 myLock 之后就没有继续执行呢。
		 *  其实,BB 一直执行着,只不过陷入了 while 死循环中,因为 AA 将线程置为非空。
		 *  等到 5 秒后,AA unlock 重新将线程=null时,BB 获取线程并执行任务。over
		 */
		Test test = new Test();
		new Thread(()->{
				test.myLock();
				try {
					TimeUnit.SECONDS.sleep(5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				test.unLock();
			},"AA").start();

			TimeUnit.SECONDS.sleep(1);

			new Thread(()->{
				test.myLock();
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				test.unLock();
			},"BB").start();
	}

	//对线程保证原子性
	AtomicReference<Thread> atomicReference = new AtomicReference<>();
	//获取锁,其实质,将锁看做一个条件判断,只要这个判断能够保证线程安全即可。
	//如下:我们将线程是否为空作为条件,如果是空的就没锁,自己可以对其加锁,将其值设为自己。
	//如果使用完,使用unlock 将线程设置为 null,其他线程通过判断来获得锁,其实就像一种约定而已。
	public void myLock(){
		Thread thread = Thread.currentThread();
		System.out.println(thread.getName()+"   myLock");
		while (!atomicReference.compareAndSet(null,thread)){
		   
		}
	}
	
	//释放锁
	public void unLock(){
		Thread thread = Thread.currentThread();
		atomicReference.compareAndSet(thread,null);
		System.out.println(thread.getName()+"    unLock");
	}
}

【独占锁】(写锁):指该锁只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁。
【共享锁】(读锁):指该锁可被多个线程持有。
【1】不加读写锁时,代码及出现的问题如下:创建5个线程进行写入,5个线程进行读取。

public class ReadWriteLock {
    private volatile Map map = new HashMap();

    //写入方法
    public void put(String k,Object v){
        System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );
        try {
            TimeUnit.MICROSECONDS.sleep(30);
        }catch (Exception e){
            e.printStackTrace();
        }
        map.put(k,v);
        System.out.println(Thread.currentThread().getName()+"   写入完成");
    }

    //读方法
    public void get(String k){
        System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );
        try {
            TimeUnit.MICROSECONDS.sleep(10);
        }catch (Exception e){
            e.printStackTrace();
        }
        Object v = map.get(k);
        System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);
    }

    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReadWriteLock();

        //写入数据
        for (int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }

        //读取数据
        for(int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

【2】上述代码输出如下第一个线程未写入完成时,其他线程就进入了该方法,进行了写操作。不符合多线程安全特性。
 

【3】加入读写锁:ReentrantReadWriteLock(读写锁)位于 JUC 包下

public class ReadWriteLock{
    private volatile Map map = new HashMap();
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    //写入方法
    public void put(String k,Object v){
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );
            try {
                TimeUnit.MICROSECONDS.sleep(30);
            }catch (Exception e){
                e.printStackTrace();
            }
            map.put(k,v);
            System.out.println(Thread.currentThread().getName()+"   写入完成");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }
    }

    //读方法
    public void get(String k){
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            }catch (Exception e){
                e.printStackTrace();
            }
            Object v = map.get(k);
            System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReadWriteLock();
        //写入数据
        for (int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }

        //读取数据
        for(int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

【4】加入读写锁后,输出如下:
 


----关注公众号,获取更多内容----

posted @ 2020-11-21 17:06  Java程序员进阶  阅读(59)  评论(0编辑  收藏  举报