公平锁
是指多个线程按照申请的顺序来获取锁,类似于排队买车票;先来后到的原则。
非公平锁
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能是后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象
可重入锁(也叫递归锁)
指的是同一线程外层函数获得所之后,内层递归函数任然能获取该锁的代码,在同一个线程外层方法获取的时候,在进入内层方法会自动获取锁
也就是说线程可以进入任何一个它已经拥有的锁所同步着的代码块(ReentrantLock和Synchronize 就是一个典型的非公平重入锁)
ReentrantLock和Synchronize 就是一个典型的非公平重入锁,上代码:
1 package com.company.lock.demo; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 /** 8 * (ReentrantLock和 Synchronize 就是一个典型的非公平重入锁) 9 * <p> 10 * Synchronize 就是一个典型的非公平重入锁 11 * <p> 12 * t1 发送短信 13 * t1 发送邮件(发送邮件的方法也是加锁的,但 t1 线程直接获取,而且是同一把锁,说明 Synchronize 就是一个典型的重入锁 ) 14 * t2 发送短信 15 * t2 发送邮件(发送邮件的方法也是加锁的,但 t2 线程直接获取,而且是同一把锁,说明 Synchronize 就是一个典型的重入锁 ) 16 * <p> 17 * <p> 18 * ReentrantLock 也是一个典型的非公平重入锁 19 * <p> 20 * t3 get() 发送短信 21 * t3 set() 发送邮件(发送邮件的方法也是加锁的,但 t3 线程直接获取,而且是同一把锁,说明 ReentrantLock 就是一个典型的重入锁 ) 22 * t4 get() 发送短信 23 * t4 set() 发送邮件(发送邮件的方法也是加锁的,但 t4 线程直接获取,而且是同一把锁,说明 ReentrantLock 就是一个典型的重入锁 ) 24 * <p> 25 * <p> 26 * 可重入锁的最大作用: 避免死锁 27 */ 28 29 class Phone implements Runnable { 30 /** 31 * Synchronize 就是一个典型的非公平重入锁 32 * 33 * @throws Exception 34 */ 35 public synchronized void sendMessages() throws Exception { 36 System.out.println(Thread.currentThread().getName() + "\t" + "发送短信"); 37 sendEmail(); 38 } 39 40 public synchronized void sendEmail() throws Exception { 41 System.out.println(Thread.currentThread().getName() + "\t" + "发送邮件"); 42 } 43 44 45 /** 46 * ReentrantLock 也是一个典型的非公平重入锁 47 */ 48 49 Lock lock = new ReentrantLock();//默认参数为 false ,即非公平重入锁 50 51 @Override 52 public void run() { 53 get(); 54 } 55 56 private void get() { 57 lock.lock(); 58 try { 59 System.out.println(Thread.currentThread().getName() + "\t" + "get() 发送短信"); 60 set(); 61 } catch (Exception e) { 62 e.printStackTrace(); 63 } finally { 64 lock.unlock(); 65 } 66 } 67 68 private void set() { 69 lock.lock(); 70 try { 71 System.out.println(Thread.currentThread().getName() + "\t" + "set() 发送邮件"); 72 call(); 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } finally { 76 lock.unlock(); 77 } 78 } 79 80 private void call() { 81 lock.lock(); 82 try { 83 System.out.println(Thread.currentThread().getName() + "\t" + "call() 打个电话"); 84 } catch (Exception e) { 85 e.printStackTrace(); 86 } finally { 87 lock.unlock(); 88 } 89 } 90 } 91 92 public class ReentrantLockDemo { 93 public static void main(String[] args) { 94 Phone phone = new Phone(); 95 new Thread(() -> { 96 try { 97 phone.sendMessages(); 98 } catch (Exception e) { 99 e.printStackTrace(); 100 } 101 }, "t1").start(); 102 new Thread(() -> { 103 try { 104 phone.sendMessages(); 105 } catch (Exception e) { 106 e.printStackTrace(); 107 } 108 }, "t2").start(); 109 110 new Thread(() -> { 111 try { 112 phone.sendMessages(); 113 } catch (Exception e) { 114 e.printStackTrace(); 115 } finally { 116 } 117 }, "t5").start(); 118 try { 119 TimeUnit.SECONDS.sleep(1); 120 } catch (InterruptedException e) { 121 e.printStackTrace(); 122 } 123 124 System.out.println("-------------------------------"); 125 System.out.println("-------------------------------"); 126 System.out.println("-------------------------------"); 127 System.out.println("-------------------------------"); 128 129 Thread t3 = new Thread(phone, "t3"); 130 Thread t4 = new Thread(phone, "t4"); 131 t3.start(); 132 t4.start(); 133 } 134 }
自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU
1 package com.company.lock.demo; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.atomic.AtomicReference; 5 6 /** 7 * 自旋锁: 8 * <p> 9 * 是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU 10 * <p> 11 * 通过CAS操作完成自旋锁,A线程先进来调用myLock()方法,自己持有锁5秒钟,B线程随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A线程释放锁后 12 * B线程随后抢到锁。 13 */ 14 public class SpinLockDemo { 15 AtomicReference<Thread> atomicReference = new AtomicReference<>();//引用类型,默认参数为null 16 17 public void myLock() { 18 //当前线程 19 Thread thread = Thread.currentThread(); 20 System.out.println(Thread.currentThread().getName() + "\t com in O(∩_∩)O~" + "我进去啦!"); 21 while (!atomicReference.compareAndSet(null, thread)) {//CAS自旋,若当前值和主内存值一致,才能更换主内存值,为 true 取反结束循环。 22 } 23 } 24 25 public void myUnLock() { 26 Thread thread = Thread.currentThread(); 27 atomicReference.compareAndSet(thread, null); 28 System.out.println(Thread.currentThread().getName() + "\t come out O(∩_∩)O ~" + "我出来啦!"); 29 } 30 31 public static void main(String[] args) { 32 SpinLockDemo spinLockDemo = new SpinLockDemo(); 33 new Thread(() -> { 34 spinLockDemo.myLock(); 35 try { 36 TimeUnit.SECONDS.sleep(5); 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 spinLockDemo.myUnLock(); 41 }, "AA").start(); 42 43 try { 44 TimeUnit.SECONDS.sleep(1); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 } 48 49 new Thread(() -> { 50 spinLockDemo.myLock(); 51 try { 52 TimeUnit.SECONDS.sleep(2); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } 56 spinLockDemo.myUnLock(); 57 }, "BB").start(); 58 59 while (Thread.activeCount() > 2) { 60 Thread.yield(); 61 } 62 System.out.println("自旋锁....小case"); 63 64 } 65 }
多线程并发环境下读写锁资源同时进行的方案设计
多线程同时读一个资源没有任何问题,为了满足并发量,读取共享资源是可以同时进行的,但如果有一个线程想要修改共享资源,就不应该在有其它线程可以对该资源进行读或写。即:
读 -- 读 能共存
读 -- 写 不能共存
写 -- 写 不能共存
写操作:原子+独占,整个过程必须是一个完整的统一体,中间不能被分割或者被打断
代码举例:
1 package com.company.lock.demo; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.concurrent.TimeUnit; 6 import java.util.concurrent.locks.ReentrantReadWriteLock; 7 8 /** 9 * 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。 10 * 但是 11 * 如果有一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写 12 * <p> 13 * <p> 14 * 小总结: 15 * <p> 16 * 读-读 能共存 17 * 读-写 不能共存 18 * 写-写 不能共存 19 * <p> 20 * 写操作: 原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断 21 */ 22 23 //手写一个缓存类 24 class MyCache { 25 //加 volatile 高并发环境下保证可见性 26 private volatile Map<String, Object> map = new HashMap<>();//不加锁的情况下会出现加塞、打断等结果 27 //读写分离锁,既可以保证高并发的读,又可以保证写操作的原子性 28 private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 29 30 public void setMap(String key, Object value) { 31 readWriteLock.writeLock().lock(); 32 try { 33 System.out.println(Thread.currentThread().getName() + "\t 开始写入缓存" + "\t" + key); 34 try { 35 TimeUnit.MICROSECONDS.sleep(300); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 map.put(key, value); 40 System.out.println(Thread.currentThread().getName() + "\t 写入完成....."); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } finally { 44 readWriteLock.writeLock().unlock(); 45 } 46 } 47 48 public void get(String key) { 49 readWriteLock.readLock().lock(); 50 try { 51 System.out.println(Thread.currentThread().getName() + "\t 开始读取资源"); 52 try { 53 TimeUnit.MICROSECONDS.sleep(300); 54 } catch (InterruptedException e) { 55 e.printStackTrace(); 56 } 57 Object result = map.get(key); 58 System.out.println(Thread.currentThread().getName() + "\t 读取完成....." + result); 59 } catch (Exception e) { 60 e.printStackTrace(); 61 } finally { 62 readWriteLock.readLock().unlock(); 63 } 64 } 65 66 /* public void clean() {//清空缓存 67 readWriteLock.writeLock().lock(); 68 try { 69 map.clear(); 70 } catch (Exception e) { 71 e.printStackTrace(); 72 } finally { 73 readWriteLock.writeLock().unlock(); 74 System.out.println("清空完成......."); 75 } 76 }*/ 77 } 78 79 public class ReentrantReadWriteLockDemo { 80 public static void main(String[] args) { 81 MyCache myCache = new MyCache(); 82 for (int i = 1; i <= 5; i++) { 83 final int tempInt = i; 84 new Thread(() -> { 85 myCache.setMap(tempInt + "", tempInt + ""); 86 }, String.valueOf(i)).start(); 87 } 88 for (int i = 1; i <= 5; i++) { 89 final int tempInt = i; 90 new Thread(() -> { 91 myCache.get(tempInt + ""); 92 }, String.valueOf(i)).start(); 93 } 94 /*for (int i = 1; i <= 5; i++) {//清空缓存的线程 95 new Thread(() -> { 96 try { 97 TimeUnit.SECONDS.sleep(5); 98 } catch (InterruptedException e) { 99 e.printStackTrace(); 100 } 101 myCache.clean(); 102 }, String.valueOf(i)).start(); 103 }*/ 104 } 105 }
人生本来就是一场漫长的旅行,我们无时无刻不在旅途的路上。
浙公网安备 33010602011771号