谈谈 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语言中,有 ...
随机推荐
- Java8中list.sort的lamba表达式
最近写代码,需要对list集合排序,IDEA总是黄色警告: Reports calls to Collections.sort(list, comparator) which could be rep ...
- Eclipse导入项目后JSP页面出现报红
Multiple annotations found at this line:- javax.servlet.jsp.JspException cannot be resolved to a typ ...
- Alpha阶段项目复审(小菜鸡联盟)
Alpha项目复审 小队:小菜鸡联盟 团队名称 项目名称 评价 排名 『S.L.N』 OnTime 优点:团队分工合理明确,每个成员有一定的开发经验,能用到自己较为熟悉的技术进行开发:在开发初期制定了 ...
- Docker 学习笔记(二)
进入当前正在运行的容器 # 我们通常容器都是使用后台方式运行的,需要进入容器,修改一些配置 # 命令: docker exec -it 容器 id bashshell 测试 我们通常容器都是使用后台方 ...
- vscode 安装go插件失败后,最简单的方法
vscode 安装go插件 参考: https://github.com/goproxy/goproxy.cn/blob/master/README.zh-CN.md https://goproxy. ...
- jmeter中jdbc连接数据库——(一)
所有jmeter基本组件功能本文不做介绍.jmeter要链接mysql数据库,首先得下载mysql jdbc驱动包 (注:驱动包的版本一定要与你数据库的版本匹配,驱动版本低于mysql版本有可能会导致 ...
- discuz论坛替换logo之后不显示该怎么办
http://www.wocaoseo.com/thread-2-1-1.html 这个虽然不算是seo的问题,但是在平时的操作之中经常性的遇到,常常是我用FTP已经上传替换了原来的logo,但是前台 ...
- Database4.exe用来导入excel
从ACCESS数据库导出的EXCEL表格,可以通过database4.exe来连接,并导出sql脚本,再用database4.exe来连接ACCESS并先创建于脚本结构一致的表,然后复制脚本,从新生成 ...
- react native 常用学习或查资料网址
react-native facebook官网:http://facebook.github.io/react-native/中文网:http://reactnative.cn/ react 官网地址 ...
- 【转】Android 音量键+电源键 截屏代码小结
http://104zz.iteye.com/blog/1752961 原文地址:http://blog.csdn.net/hk_256/article/details/7306590 ,转载请注明出 ...