Java多线程 -- 互斥锁/共享锁/读写锁 快速入门

简介: 什么是互斥锁?在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。
什么是互斥锁?

在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。

什么是共享锁?

互斥锁要求只能有一个线程访问被保护的资源,共享锁从字面来看也即是允许多个线程共同访问资源。

什么是读写锁?

读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

用ReentrantLock手动实现一个简单的读写锁。

MyReadWriteLock.java

/**
 * Created by Fant.J.
 */
public class MyReadWriteLock {
    private Map<String,Object> map = new HashMap<>();

    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    private Lock r = rwl.readLock();
    private Lock w = rwl.writeLock();

    public Object get(String key){
        try {
            r.lock();
            System.out.println(Thread.currentThread().getName()+"read 操作执行");
            Thread.sleep(500);
            return map.get(key);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        } finally {
            r.unlock();
            System.out.println(Thread.currentThread().getName()+"read 操作结束");
        }

    }

    public void put(String key,Object value){
        try {
            w.lock();
            System.out.println(Thread.currentThread().getName()+"write 操作执行");
            Thread.sleep(500);
            map.put(key,value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            w.unlock();
            System.out.println(Thread.currentThread().getName()+"write 操作结束");
        }
    }
}

测试读读共享(不互斥)
/**
 * Created by Fant.J.
 */
public class Test {
    public static void main(String[] args) {

        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        myReadWriteLock.put("a","fantj_a");
        //读读不互斥(共享)
        //读写互斥
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();

    }
}



mainwrite 操作执行
mainwrite 操作结束
Thread-1read 操作执行
Thread-2read 操作执行
Thread-0read 操作执行
Thread-3read 操作执行
Thread-4read 操作执行
Thread-5read 操作执行
Thread-1read 操作结束
Thread-0read 操作结束
Thread-2read 操作结束
Thread-3read 操作结束
fantj_a
fantj_a
fantj_a
fantj_a
Thread-4read 操作结束
fantj_a
Thread-5read 操作结束
fantj_a

可以看出,中间有很多read操作是并发进行的。

那么我们再看下写写是否有互斥性:
/**
 * 测试 写-写 模式  是互斥的
 * Created by Fant.J.
 */
public class TestWriteWrite {
    public static void main(String[] args) {

        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.put("b","fantj_b");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.put("b","fantj_b");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.put("b","fantj_b");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.put("b","fantj_b");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.put("b","fantj_b");
            }
        }).start();
    }
}




Thread-0write 操作执行
Thread-1write 操作执行
Thread-0write 操作结束
Thread-1write 操作结束
Thread-2write 操作执行
Thread-2write 操作结束
Thread-3write 操作执行
Thread-3write 操作结束
Thread-4write 操作执行
Thread-4write 操作结束

控制台能明显感觉到线程休息的时间。所以它的写-写操作肯定是互斥的。

最后再看看,写-读 操作是否互斥。

写-读互斥 测试
/**
 * 测试  写-读模式 互斥
 * Created by Fant.J.
 */
public class TestWriteRead {
    public static void main(String[] args) {

        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        //读读不互斥(共享)
        //读写互斥
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.put("a","fantj_a");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(myReadWriteLock.get("a"));
            }
        }).start();


    }
}



Thread-0write 操作执行
Thread-1read 操作执行
Thread-0write 操作结束
Thread-1read 操作结束
fantj_a

控制台可以看到write操作执行后线程被阻塞。直到写释放了锁。

问题分析

问题1:仔细想了想,如果有一种场景,就是用户一直再读,写获取不到锁,那么不就造成脏读吗?

上一章我介绍了公平锁/非公平锁,资源的抢占不就是非公平锁造成的吗,那我们用公平锁把它包装下不就能避免了吗,我做了个简单的实现:(不知道公平锁的可以翻我上章教程)

/**
 * 测试  读写锁 的公平锁 实现
 * Created by Fant.J.
 */
public class TestReadWriteRead {
    public static void main(String[] args) {
         ReentrantLock fairLock = new ReentrantLock(true);
        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        myReadWriteLock.put("a","fantj_a");
        new Thread(new Runnable() {
            @Override
            public void run() {
                fairLock.lock();
                System.out.println(myReadWriteLock.get("a"));
                System.out.println("fair队列长度"+fairLock.getQueueLength());
                fairLock.unlock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fairLock.lock();
                System.out.println(myReadWriteLock.get("a"));
                System.out.println("fair队列长度"+fairLock.getQueueLength());

                fairLock.unlock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fairLock.lock();
                System.out.println(myReadWriteLock.get("a"));
                System.out.println("fair队列长度"+fairLock.getQueueLength());
                fairLock.unlock();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                fairLock.lock();
                myReadWriteLock.put("a","fantj_a_update");
                System.out.println("fair队列长度"+fairLock.getQueueLength());
                fairLock.unlock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fairLock.lock();
                System.out.println(myReadWriteLock.get("a"));
                System.out.println("fair队列长度"+fairLock.getQueueLength());
                fairLock.unlock();
            }
        }).start();
    }
}


mainwrite 操作执行
mainwrite 操作结束
Thread-0read 操作执行
Thread-0read 操作结束
fantj_a
fair队列长度4
Thread-1read 操作执行
Thread-1read 操作结束
fantj_a
fair队列长度3
Thread-2read 操作执行
Thread-2read 操作结束
fantj_a
fair队列长度2
Thread-3write 操作执行
Thread-3write 操作结束
fair队列长度1
Thread-4read 操作执行
Thread-4read 操作结束
fantj_a_update
fair队列长度0

如果谁有更好的实现方式(或者java有现成的实现工具类/包),可在评论区留言,我在百度上没有找到读写锁的公平锁实现~~谢谢!

相关文章
|
2月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
46 2
|
15天前
|
缓存 Java
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
1月前
|
Java
Java 中锁的主要类型
【10月更文挑战第10天】
|
1月前
|
XML JavaScript Java
java与XML文件的读写
java与XML文件的读写
26 3
|
2月前
|
算法 Java 关系型数据库
Java中到底有哪些锁
【9月更文挑战第24天】在Java中,锁主要分为乐观锁与悲观锁、自旋锁与自适应自旋锁、公平锁与非公平锁、可重入锁以及独享锁与共享锁。乐观锁适用于读多写少场景,通过版本号或CAS算法实现;悲观锁适用于写多读少场景,通过加锁保证数据一致性。自旋锁与自适应自旋锁通过循环等待减少线程挂起和恢复的开销,适用于锁持有时间短的场景。公平锁按请求顺序获取锁,适合等待敏感场景;非公平锁性能更高,适合频繁加解锁场景。可重入锁支持同一线程多次获取,避免死锁;独享锁与共享锁分别用于独占和并发读场景。
|
1月前
|
安全 Java 开发者
java的synchronized有几种加锁方式
Java的 `synchronized`通过上述三种加锁方式,为开发者提供了从粗粒度到细粒度的并发控制能力,满足了不同场景下的线程安全需求。合理选择加锁方式对于提升程序的并发性能和正确性至关重要,开发者应根据实际应用场景的特性和性能要求来决定使用哪种加锁策略。
20 0
|
1月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
38 0
|
1月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
5天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
下一篇
无影云桌面