谈谈 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语言中,有 ...
随机推荐
- @SuppressWarnings注解用法详解(转)
原文连接https://blog.csdn.net/sysware_carol/article/details/52100580 今天来谈谈@SuppressWarnings注解的作用. J2SE 提 ...
- Linux kernel 模块 hello 测试
原文链接:https://www.cnblogs.com/nerohwang/p/3621316.html hello.c 文件: #include <linux/kernel.h> /* ...
- Robot Framework(1)——环境搭建及安装
一.了解Robot Framework Robot Framework不是一个测试工具,准确来说,它是一个自动化测试框架,或者说它是一个自动化测试平台 特性如下: 1.支持关键字驱动.数据驱动和行为驱 ...
- SLS案例中心
今日PV nginx日志查看今日的PV和昨日的对比,先通过count函数计算总的pv,再用compare函数得出今日的pv和昨日的同比. 通过单值图进行展示,显示值为20.381Mil,对比值为-2% ...
- CSP 201312-1 出现次数最多的数
思路 由于输入的数组元素是 1 ≤ si ≤ 10000,所以开一个 10001 的数组.输入的过程中使用 num 记录出现的最大次数: 在所有输入结束后,遍历数组元素,由于题目要求出现多个众数输出最 ...
- 快速幂 (C++)
typedef long long LL; using namespace std; //求a^b%m,递归写法 LL binaryPow(LL a,LL b,LL m){ if(b==){ //如果 ...
- Idea创建maven加载过慢解决方法
添加标签:archetypeCataloginternal问题解决!
- 《spring源码解读》 - IoC 之解析 import 标签
在上一文中我们分析了注册 BeanDefinition 的过程,在其中我们了解到在解析跟节点和子节点时分两种情况,对于默认名称空间的标签我们通过 DefaultBeanDefinitionDocume ...
- Labview学习之路(三)前面板数值控件
首先看一下前面板都有什么数值控件(我用的labview是17年的,其他版本可能会有不同) 我个人将他们分成了六个部分 第一部分 这个部分大家很好理解,数值输入数值输出,时间输入和时间输出,这里我们讲一 ...
- c++ binding code generator based on clang
google it http://www.swig.org/Doc3.0/CSharp.html http://samanbarghi.com/blog/2016/12/06/generate-c-i ...