JAVA中锁的介绍

    科技2022-08-19  94

    公平锁和非公平锁

    公平锁: 公平锁,就是很公平,在并发环境中每个线程在获取锁时首先会查看此锁维护的等待队列,如果是空说明自己是第一个就会获得到锁,如果不为空就会加入到队列中等待排队。

    非公平锁: 非公平锁上来就尝试占有锁,如果尝试失败,才会加入到队列中。

    多线程获取锁并不是按照申请锁顺序,有可能后申请锁的线程比先申请锁的线程优先获取锁,有可能会造成优先级反转或者饥饿现象(等了好久都没获得到锁非常饥饿)。

    java.util.concurrent包中的ReentrantLock类,可以在这个类中的构造方法中传入一个Boolean类型值,如果是true代表公平锁,如果false是非公平锁,什么也不传默认是非公平锁。

    非公平锁的优点在于比公平锁的吞吐量要大,并发效率要高。

    Synchronized就是典型的非公平锁。

    可重入锁(又名递归锁)

    理论:是指同一线程外层函数获得到锁之后,内层函数会自动获得到锁。 其实就是同步方法中调用另一个同步方法,线程获取到外层函数锁,内层锁自动获取到。 例如:代码如下

    public class Reentrant { public static synchronized void method(){ System.out.println(Thread.currentThread().getName()+"mthood.."); method01(); } public static synchronized void method01(){ System.out.println(Thread.currentThread().getName()+"method01."); } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { Reentrant.method(); } },"t1").start(); new Thread(new Runnable() { @Override public void run() { Reentrant.method(); } },"t2").start(); } } 输出结果: t1mthood...... t1method01..... t2mthood...... t2method01.....

    最典型的可重入锁除了Synchronized外还有ReentrantLock,上边代码是Synchronized重入锁的代码。

    ReentrantLock代码演示。

    public class Reentrant01 { static Lock lock = new ReentrantLock(); public static void set(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+"set..."); get(); } finally { lock.unlock(); } } public static void get(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+"get......"); } finally { lock.unlock(); } } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { Reentrant01.set(); } },"t3").start(); new Thread(new Runnable() { @Override public void run() { Reentrant01.set(); } },"t4").start(); } } 结果: t3set... t3get...... t4set... t4get......

    注意:lock.lock()几次就unlock()几次,只要匹配就不会出现死锁。 可重入锁避免了死锁的发生。

    自旋锁(CAS思想)

    尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁,减少了线程上下文切换所消耗的性能,但同样如果循环次数过度会占用CPU资源。

    循环+CAS(比较并交换)的思想。

    AtomicReference(原子引用)类 手写自旋锁代码如下:

    public class zixuan { AtomicReference<Thread> atomicReference = new AtomicReference<>(); public void Mylock(){ do{ Thread thread = Thread.currentThread(); //获得当前线程 }while(!atomicReference.compareAndSet(null,thread));//比较并交换 } public void UnMylock(){ //看内存中是不是当前线程对象,如果是把它设置为null atomicReference.compareAndSet(Thread.currentThread(),null); } }

    独占锁

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

    共享锁

    指该锁可以被多个线程所持有。 对ReentrantReadWriteLock其实就是共享锁,其写锁是独占锁,该锁的共享锁保证并发读是非常高效的。 读读能共存(读的时候线程可以争抢) 读写互斥 写写互斥

    代码如下:

    /** * 读写锁,读写共存,读写同时进行, * 读和写共用一把锁,写可以保证写的原子性,中间没有加塞的现象,都是一个线程一个线程执行。 * 读一起读,提高并发率 */ public class ReentrantReadWLock { public volatile Map<String,Object> map = new HashMap<>(); ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); /** * 写方法 */ public void put(String key,Object val){ reentrantReadWriteLock.writeLock().lock(); //加入写锁 try { System.out.println(Thread.currentThread().getName()+"正在写..."); map.put(key,val); System.out.println(Thread.currentThread().getName()+"写完成..."); }finally { reentrantReadWriteLock.writeLock().unlock(); } } /** * 读方法 */ public void get(String key){ reentrantReadWriteLock.readLock().lock(); //加入读锁 try { System.out.println(Thread.currentThread().getName()+"正在读..."); map.get(key); System.out.println(Thread.currentThread().getName()+"读完成..."); }finally { reentrantReadWriteLock.readLock().unlock(); } } public static void main(String[] args) { ReentrantReadWLock reentrantReadWLock = new ReentrantReadWLock(); for (int i=1;i<=5;i++){ int temp = i; new Thread(new Runnable() { @Override public void run() { reentrantReadWLock.put(temp+"",temp+""); } },String.valueOf(i)).start(); } for (int i=1;i<=5;i++){ int temp = i; new Thread(new Runnable() { @Override public void run() { reentrantReadWLock.get(temp+""); } },String.valueOf(i)).start(); } } } 结果: 1正在写... 1写完成... 2正在写... 2写完成... 3正在写... 3写完成... 4正在写... 4写完成... 5正在写... 5写完成... 1正在读... 2正在读... 3正在读... 5正在读... 4正在读... 1读完成... 2读完成... 3读完成... 5读完成... 4读完成...

    总结:

    ReentrantReadWriteLock是共享锁,这个类里面有ReadLock()方法获得读锁,WriteLock()方法获得写锁。写的时候只能一个线程一个线程获得锁,中间不能加塞,保证写的原子性。读的时候可以多线程一起读,提高并发率。

    举个例子: 写的时候为保证数据的原子性,在写的时候上锁只能由一个线程来写,写完了再由另一个线程写。不能正在写的时候被另一个线程加塞,破坏写的数据的原子性。而读就不需要,读可以多个线程一起读,就好比飞机场的公示牌如果只允许一个人一个人读岂不是太慢了,大家一起读提高效率,一个人表示一个线程。

    Processed: 0.010, SQL: 9