一、公平锁&非公平锁

是什么

  • 公平锁:线程按照申请锁的顺序来获取锁;在并发环境中,每个线程都会被加到等待队列中,按照 FIFO 的顺序获取锁。

  • 非公平锁:线程不按照申请锁的顺序来获取锁;一上来就尝试占有锁,如果占有失败,则按照公平锁的方式等待。

通俗来讲,公平锁就相当于现实中的排队,先来后到;非公平锁就是无秩序,谁抢到是谁的;

优缺点

公平锁

  • 优:线程按照顺序获取锁,不会出现饿死现象(注:饿死现象是指一个线程的CPU执行时间都被其他线程占用,导致得不到CPU执行)。
  • 缺:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU 唤醒线程的开销比非公平锁要大。

非公平锁

  • 优:可以减少唤起线程上下文切换的消耗,整体吞吐量比公平锁高。
  • 缺:在高并发环境下可能造成线程优先级反转和饿死现象。

Java中的公平&非公平锁

在 Java 中,synchronized 是典型的非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁,可以在初始化的时候指定。

查看 ReentrantLock 的源码会发现,初始化时可以传入 true 或 false,来得到公平或非公平锁。

//源码
//默认为非公平
public ReentrantLock() {
sync = new NonfairSync();
} public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public class FairLockDemo {
public static void main(String[] args) {
//公平锁
Lock fairLock = new ReentrantLock(true);
//非公平锁
Lock unFairLock = new ReentrantLock(false);
}
}

二、可重入锁

是什么

可重入锁也叫递归锁,是指线程可以进入任何一个它已经拥有的锁所同步的代码块。

通俗来讲,就好比你打开了你家的大门,就可以随意的进入客厅、厨房、卫生间......

优缺点

  • 优:可以一定程度上避免死锁
  • 缺:暂时不知道

Java中的可重入锁

synchronized和ReentrantLock都是典型的可重入锁

synchronized

public class ReentrantDemo1 {
public static void main(String[] args) {
Phone phone = new Phone(); new Thread(() -> {
phone.sendSMS();
}).start(); new Thread(() -> {
phone.sendSMS();
}).start();
}
}
class Phone {
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getId() + ":sendSMS()");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
sendEmail();
} public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getId() + ":sendEmail()");
}
}

ReentrantLock

public class ReentrantDemo2 {
public static void main(String[] args) {
User user = new User(); new Thread(() -> {
user.getName();
}).start(); new Thread(() -> {
user.getName();
}).start();
}
} class User {
Lock lock = new ReentrantLock(); public void getName() {
lock.lock();
try {
System.out.println(Thread.currentThread().getId() + ":getName()");
TimeUnit.SECONDS.sleep(1);
getAge();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public void getAge() {
lock.lock();
try {
System.out.println(Thread.currentThread().getId() + ":getAge()");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

八锁问题

点击查看我之前的博客 多线程之8锁问题,搞懂八锁问题,可以更深刻的理解 synchronized 锁的范围

实现一个不可重入锁

public class UnReentrantLockDemo {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
Thread current = Thread.currentThread();
//自旋
while(!atomicReference.compareAndSet(null, current)) { }
} public void unlock() {
Thread current = Thread.currentThread();
atomicReference.compareAndSet(current, null);
}
}

三、自旋锁

是什么

尝试获取锁的线程不会立即阻塞,而是以循环的方式不断尝试获取锁

优缺点

  • 优:减少线程上下文切换的消耗
  • 缺:循环消耗CPU

Java中的自旋锁

CAS:CompareAndSwap,比较并交换,它是一种乐观锁。

CAS 中有三个参数:内存值V、旧的预期值A、要修改的新值B;只有当预期值A与内存值V相等时,才会将内存值V修改为新值B,否则什么都不做

public class CASTest {
public static void main(String[] args) {
AtomicInteger a1 = new AtomicInteger(1);
//V=1, A=1, B=2
//V=A,所以修改成功,此时V=2
System.out.println(a1.compareAndSet(1, 2) + "," + a1.get());
//V=2, A=1, B=2
//V!=A,修改失败,返回false
System.out.println(a1.compareAndSet(1, 2) + "," + a1.get());
}
}

源码解析:以 AtomicInteger 中的 getAndIncrement() 方法为例

//获取并增加,相当于i++操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
} //调用UnSafe类中的getAndAddInt()方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取当前内存值
var5 = this.getIntVolatile(var1, var2);
//循环比较内存值和预期值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
}

CAS 也存在一些问题:

  • 如果一直交换不成功,会一直循环,开销大
  • 只能保证一个共享变量的原子操作
  • ABA 问题:即 A 被修改为 B,又被改为 A,虽然值没发生变化,但这种操作还是存在一定风险的

可以通过加时间戳或版本号的方式解决 ABA 问题:

public class ABATest {
public static void main(String[] args) {
showABA();
} /**
* 重现ABA问题
*/
private static void showABA() {
AtomicReference<String> atomicReference = new AtomicReference<>("A");
//线程X,模拟ABA问题
new Thread(() -> {
atomicReference.compareAndSet("A", "B");
atomicReference.compareAndSet("B", "A");
}, "线程X").start(); //线程Y睡眠一会儿,等待X执行完
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet("A", "C");
System.out.println("最终结果:" + atomicReference.get());
}, "线程Y").start();
} /**
* 解决ABA问题
*/
private static void solveABA() {
//初始版本号为1
AtomicStampedReference<String> asr = new AtomicStampedReference<>("A", 1); new Thread(() -> {
asr.compareAndSet("A", "B", 1, 2);
asr.compareAndSet("B", "A", 2, 3);
}, "线程X").start(); new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
asr.compareAndSet("A", "C", 1, 2);
System.out.println(asr.getReference() + ":" + asr.getStamp());
}, "线程Y").start();
}
}

动手实现一个自旋锁

public class SpinLockDemo {
/**
* 初始值为 null
*/
AtomicReference<Thread> atomicReference = new AtomicReference<>(null); public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo(); new Thread(() -> {
spinLockDemo.lock();
spinLockDemo.unLock();
}, "线程A").start(); new Thread(() -> {
spinLockDemo.lock();
spinLockDemo.unLock();
}, "线程B").start();
} public void lock() {
//获取当前线程对象
Thread thread = Thread.currentThread();
do {
System.out.println(thread.getName() + "尝试获取锁...");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当赋值成功才会跳出循环
} while (!atomicReference.compareAndSet(null, thread));
} public void unLock() {
//获取当前线程对象
Thread thread = Thread.currentThread();
//置为null,相当于释放锁
atomicReference.compareAndSet(thread, null);
System.out.println(thread.getName() + "释放锁...");
}
}

四、共享锁&独占锁

是什么

  • 共享锁:也可称为读锁,可被多个线程持有
  • 独占锁:也可称为写锁,只能被一个线程持有,synchronized和ReentrantLock都是独占锁
  • 互斥:读读共享、读写互斥、写写互斥

优缺点

读写分离,适用于大量读、少量写的场景,效率高

java中的共享锁&独占锁

ReentrantReadWriteLock 中的读锁是共享锁、写锁是独占锁

class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); /**
* 写锁控制写入
*/
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入...");
//睡一会儿
TimeUnit.SECONDS.sleep(1);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成...");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
} /**
* 读锁控制读取
*/
public Object get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读取...");
//睡一会儿
TimeUnit.SECONDS.sleep(1);
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取结束...value=" + value);
return value;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return null;
} public void clear() {
map.clear();
}
}
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyCache cache = new MyCache();
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
cache.put(String.valueOf(finalI), String.valueOf(finalI));
cache.get(String.valueOf(finalI));
}, "线程" + i).start();
}
cache.clear();
}
}

谈谈 Java 中的那些“琐”事的更多相关文章

  1. 谈谈JAVA中的安全发布

    谈谈JAVA中的安全发布 昨天看到一篇文章阐述技术类资料的"等级",看完之后很有共鸣.再加上最近在工作中越发觉得线程安全性的重要性和难以捉摸,又掏出了<Java并发编程实战& ...

  2. 谈谈java中静态变量与静态方法在有继承关系的两个类中调用

    谈谈java中静态变量与静态方法在有继承关系的两个类中调用 学习的中如果遇到不明白或者不清楚的的时候,就是自己做些测试,自己去试试,这次我就做一个关于静态变量和静态方法在有继承关系的两个类中的问题测试 ...

  3. 谈谈java中成员变量与成员方法继承的问题

    谈谈java中成员变量与成员方法继承的问题 关于成员变量和成员方法的的继承问题,我也可以做一个小测试,来看看结果. 首先我们先创建一个父类:

  4. 谈谈java中的WeakReference

    Java语言中为对象的引用分为了四个级别,分别为 强引用 .软引用.弱引用.虚引用. 本文只针对java中的弱引用进行一些分析,如有出入还请多指正. 在分析弱引用之前,先阐述一个概念:什么是对象可到达 ...

  5. 谈谈java中遍历Map的几种方法

    java中的map遍历有多种方法,从最早的Iterator,到java5支持的foreach,再到java8 Lambda,让我们一起来看下具体的用法以及各自的优缺点 先初始化一个map public ...

  6. java基础(五):谈谈java中的多线程

    1.多线程 1.1.多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一 ...

  7. java基础(四):谈谈java中的IO流

    1.字节流 1.1.字节输出流output 1.1.1.数据写入文件中 通过api查找output.找到很多,其中java.io.OutputStream,OutputStream: 输出字节流的超类 ...

  8. 谈谈Java中的代理模式

    首先来看一下代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与 ...

  9. 浅拷贝和深拷贝(谈谈java中的clone)

    clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有 ...

随机推荐

  1. Java不可重入锁和可重入锁的简单理解

    基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出Ille ...

  2. Magento add product attribute and assign to all group

    $attributes = array( 'product_type' => array( 'type' => 'int', 'input' => 'select', 'source ...

  3. pandas - 异常值处理

    异常值概念:是指那些远离正常值的观测,即“不合群”观测.异常值的出现一般是人为的记录错误或者是设备的故障等,异常值的出现会对模型的创建和预测产生 严重的后果.当然异常值也不一定是坏事,有些情况下,通过 ...

  4. 以vue+TreeSelect为例,如何将扁平数据转为tree形数据

    // 目标:将后台返回的扁平数据,根据parentId转为下拉tree <el-form-item label='下拉选择数据'> <tree-select v-model='tre ...

  5. vue 在模板template中变量和字符串拼接

    例子:  :post-action="'/api/v1/reportPage/'+this.selectedPagerId+'/saveimg/'"

  6. php实现无限极分类(多维数组 / 二维数组)形式

    <?php // 测试数组数据$array = array( array('id'=>'1','title'=>'父级分类1','pid'=>'0'), array('id'= ...

  7. oa项目面试准备

    熟悉项目在ssm框架下的编程流程,了解mysql html spring springmvc mybatis技术.了解过springboot编程. 在上个寒假跟着培训机构用springboot框架编写 ...

  8. Fitness - 06.01

    倒计时213天 久违的瑜伽课,却发现生疏了很多,倒地不起TAT 要加强锻炼,不要松懈啊~~~! 期待黄金周的到来!!

  9. 什么是Nginx -九五小庞

  10. UEFI、BIOS、GPT、MBR等概念的辨析

    (本文转移自本人的旧博客) 从各个地方包括知乎,Wiki,CSDN搜索到的一些整理,这些概念极易混淆. 先说互相的关系 BIOS和UEFI是两种固件接口标准 MBR和GPT是两种分区表 Legacy模 ...