谈谈 Java 中的那些“琐”事
一、公平锁&非公平锁
是什么
公平锁:线程按照申请锁的顺序来获取锁;在并发环境中,每个线程都会被加到等待队列中,按照 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 中的那些“琐”事的更多相关文章
- 谈谈JAVA中的安全发布
谈谈JAVA中的安全发布 昨天看到一篇文章阐述技术类资料的"等级",看完之后很有共鸣.再加上最近在工作中越发觉得线程安全性的重要性和难以捉摸,又掏出了<Java并发编程实战& ...
- 谈谈java中静态变量与静态方法在有继承关系的两个类中调用
谈谈java中静态变量与静态方法在有继承关系的两个类中调用 学习的中如果遇到不明白或者不清楚的的时候,就是自己做些测试,自己去试试,这次我就做一个关于静态变量和静态方法在有继承关系的两个类中的问题测试 ...
- 谈谈java中成员变量与成员方法继承的问题
谈谈java中成员变量与成员方法继承的问题 关于成员变量和成员方法的的继承问题,我也可以做一个小测试,来看看结果. 首先我们先创建一个父类:
- 谈谈java中的WeakReference
Java语言中为对象的引用分为了四个级别,分别为 强引用 .软引用.弱引用.虚引用. 本文只针对java中的弱引用进行一些分析,如有出入还请多指正. 在分析弱引用之前,先阐述一个概念:什么是对象可到达 ...
- 谈谈java中遍历Map的几种方法
java中的map遍历有多种方法,从最早的Iterator,到java5支持的foreach,再到java8 Lambda,让我们一起来看下具体的用法以及各自的优缺点 先初始化一个map public ...
- java基础(五):谈谈java中的多线程
1.多线程 1.1.多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一 ...
- java基础(四):谈谈java中的IO流
1.字节流 1.1.字节输出流output 1.1.1.数据写入文件中 通过api查找output.找到很多,其中java.io.OutputStream,OutputStream: 输出字节流的超类 ...
- 谈谈Java中的代理模式
首先来看一下代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与 ...
- 浅拷贝和深拷贝(谈谈java中的clone)
clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有 ...
随机推荐
- java如何简单的将一个三位正整数分解成三个数
public class Leet { public static void main(String[] args) { Scanner scanner = new Scanner(System.in ...
- Jmeter(二十二) - 从入门到精通 - JMeter断言 - 下篇(详解教程)
1.简介 断言组件用来对服务器的响应数据做验证,常用的断言是响应断言,其支持正则表达式.虽然我们的通过响应断言能够完成绝大多数的结果验证工作,但是JMeter还是为我们提供了适合多个场景的断言元件,辅 ...
- Jmeter 常用函数(9)- 详解 __UUID
如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 返回 伪随机类型4 通用唯一标识符 语 ...
- 在laravel中遇到并发的解决方案
1,在mysql中创建唯一索引,在代码中try catch mysql的1062错误 2.将存在并发的代码丢给队列异步处理.这种解决方案的问题是,接下来的代码不能依赖队列的处理结果 3.使用mysql ...
- maatwebsite lost precision when export long integer data
Maatwebsite would lost precision when export long integer data, no matter string or int storaged in ...
- 访问github太慢?我写了一个开源小工具一键变快
前言 GitHub应该是广大开发者最常去的站点,这里面有大量的优秀项目,是广大开发者寻找资源,交友学习的好地方.尤其是前段时间GitHub公布了一项代码存档计划--Arctic Code Vault, ...
- javascript 查找属性的过程
当执行 一个对象赋值操作的时候 js引擎会怎样处理呢??? 例如 有个foo对象 ,要进行这个操作 foo.a=2 1, 首先会在foo对象中查找,如果不存在a属性,就会去原型链上面找,如果原 ...
- [CSP-S2019]括号树 题解
CSP-S2 2019 D1T2 刚开考的时候先大概浏览了一遍题目,闻到一股浓浓的stack气息 调了差不多1h才调完,加上T1用了1.5h+ 然而T3还是没写出来,滚粗 思路分析 很容易想到的常规操 ...
- 焦大:seo思维光年(上)检索的价值观
http://www.wocaoseo.com/thread-55-1-1.html 检索的价值观是什么?最近很多人咨询我这个问题,因为在百度上根本找不到相关的资料,其实这个东西也是我自己总结的,比如 ...
- 单元测试利器Mockito框架
什么是Mock Mock 的中文译为仿制的,模拟的,虚假的.对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去. Mock 测试就是在测试过程中,对于某些 不容易构造(如 Ht ...