有序性的概念
有序性即是程序按一定规则进行顺序的执行,而且,是指指令按照顺序执行!
测试这个代码:
public class TestThreadOrder1 {
private static int a=0 , b=0;
private static int x=0 , y=0;
public static void main(String[] args) throws InterruptedException {
for (long i = 0; i < Long.MAX_VALUE; i++) {
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(2);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
latch.countDown();
}
});
Thread thread2 = new Thread(() -> {
b = 1;
y = a;
latch.countDown();
});
thread1.start();
thread2.start();
latch.await();
String resout = "第" + i + "次==>x:" + x + ",y:" + y;
if (x == 0 && y == 0) {
System.err.println(resout);
break;
}
}
}
}
也许多等一会儿就能遇见,x=0,y=0的情况
这说明: 线程1和线程2在执行自己的代码的时候同时发生了 x=b 和 y=a
比 a=1 和 b=1
的情况才有可能x=0且y=0
这说明,指令并没有真的按照顺序一行一行执行,而是有可能发生指令重排的情况
可见性的概念
指的就是当某个线程修改了其内存中共享变量的值时,其他线程能立刻感知到其值的变化,这既是可见性,对于串行线程来说,这个可见性现象是不存在的。
通过加volatile
来保障可见性:
执行下面两个代码,看区别
public class TestThreadVisibility1 {
private static boolean running = true;
private static void function(){
System.out.println("function start");
while (running){
}
System.out.println("function finished");
}
public static void main(String[] args) throws InterruptedException {
new Thread(TestThreadVisibility1::function,"t1").start();
Thread.sleep(1000);
running=false;
}
}
public class TestThreadVisibility2 {
private static volatile boolean running = true; // 添加volatile保证可见性
private static void function(){
System.out.println("function start");
while (running){
}
System.out.println("function finished");
}
public static void main(String[] args) throws InterruptedException {
new Thread(TestThreadVisibility2::function,"t1").start();
Thread.sleep(1000);
running=false;
}
}
第一种情况下:
t1线程在运行时会读取内存中running的值,并copy一份保存到线程本地,main线程修改的也是从内存中读取的running的值,修改了running的值并没有影响t1线程的值
第二种情况下:
使用volatile实现线程的可见性,用volatile修饰的部分在另一个线程中修改后立即可见
原子性的概念
这里有一段小程序:
public class TestThreadAtomicity {
private static long n = 0l; // race condition 竞争条件
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
CountDownLatch countDownLatch = new CountDownLatch(threads.length);
for (int j = 0; j < threads.length; j++) {
threads[j]= new Thread(()->{
for (int i = 0; i < 10000; i++) {
n++;
}
countDownLatch.countDown();
});
}
for (Thread thread : threads) {
thread.start();
}
countDownLatch.await();
System.out.println(n);
}
}
理论上在最终执行结果应该是10个线程,分别加1w次,所以最终执行的n结果应该是10w:
运行几次都是这样,差距挺大的,一点都不符合预期
看如下几个图:
-
线程1从内存中读取了n的值为0
-
线程1在修改n的值时,线程2也从内存中读取了n的值为0
-
线程1运算完++操作将n=1写回内存的时候,线程2正在将原来读取到的n=0计算为n=1
-
线程1已经将n=1写回,并且内存的值已经变成了n=1,而此时线程2也将自己的n运算完写回
-
最终,n明明执行了两次加法运算,但最终的值是 1
因此,说明了,多线程访问同一个数据(竞争条件)会产生竞争,导致出现数据不一致的情况
并发访问之时出现的不期望的结果
那么为了保证这个数据尽量的一致,就需要保证线程同步
如何保证线程同步?那么就需要保证操作的原子性
所谓原子操作,就是指不会被线程的调度机制所打断的操作,操作一旦开始就一直运行到结束
以上图的例子来讲,原子性就是线程1从内存读取n,并修改n的值并写回到内存这一整个操作不会被其他线程打断
例如: n++是原子性的吗?
很显然不是,具体的n++翻译成字节码会如何?
n++并不是简简单单地加1,而是经过了几个步骤,先加载,再加1,再写回等几个步骤,那么这几个步骤之中有没有可能被打断呢??
肯定可以啊,所以n++并不是一个原子性操作
到此,你应该清楚了什么是原子性了
Java语言中的原子操作指令:
在 《The Java® Language Specification Java SE 8 Edition》这本书中有相关说明:
也就是其中的同步操作:
- Volatile 读
- Volatile 写
- Lock
- Unlock
- 综合性线程的第一个和最后一个动作
- 启动线程或检测到线程已终止的操作
也就是说具体的相关操作如下( 虚拟机级别 ):
- lock: 主存, 标识资源被线程独占
- unlock: 主存, 标识资源被线程释放
- read: 主存, 读取内存到线程本地缓存(工作内存)
- load: 工作内存,read后的值放入线程本地变量副本
- use: 工作内存,传值给执行引擎
- assign: 工作内存,执行引擎结果赋值给线程本地变量
- store: 工作内存,存值到主内存给write备用
- write: 主内存,写变量值
如何保障操作的原子性
既然知道了要保障操作的原子性,那么如何保障呢?
具体操作: 上锁!
什么是上锁?
上锁的本质,就是将并发编程变成同步执行
下面看一小段测试代码:
public class TestRunnable {
public static void main(String[] args) {
Runnable r = ()->{
System.out.println(Thread.currentThread().getName()+" start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished...");
};
for (int i = 0; i < 4; i++) {
new Thread(r).start();
}
}
}
这是没有上锁的情况,测试结果如下,执行时间差不多1s左右:
通过synchronized进行上锁
注意,synchronized实际上是对对象进行上锁,然后只能获取到这个锁之后才能执行代码
public class TestRunnable {
public static void main(String[] args) {
Runnable r = ()->{
synchronized (TestRunnable.class){
System.out.println(Thread.currentThread().getName()+" start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished...");
}
};
for (int i = 0; i < 4; i++) {
new Thread(r).start();
}
}
}
这是将多线程操作变成了,线程1执行完才能执行下一个线程…最终执行时间4s左右
从原来的并发执行变成了序列化执行
synchronized不同的对象
想要保证线程的同步性,那么必须锁定同一个对象
public class Test1 {
private static Object o1 = new Object();
private static Object o2 = new Object();
private static Object o3 = new Object();
public static void main(String[] args) {
Runnable r0 = ()->{
synchronized (o1){
System.out.println(Thread.currentThread().getName()+" start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished...");
}
};
Runnable r1 = ()->{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+" start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished...");
}
};
Runnable r2 = ()->{
synchronized (o3){
System.out.println(Thread.currentThread().getName()+" start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished...");
}
};
new Thread(r0).start();
new Thread(r1).start();
new Thread(r2).start();
}
}
执行时间大致为1s
synchronized能保证的特性:
- 可见性 : 同步操作执行完之后要将数据写回,那么肯定是要让其他线程感知到的,那么肯定是可以保证其他线程能看见的
- 原子性
不能保证有序性!!
因为在synchronized中的代码内部完全有可能出先指令错乱的情况
一些同步的概念
synchronized锁住的那个对象称之为monitor(监视器)
而当持有这把锁执行的代码区称之为Critical Section(临界区)
如果这个临界区很大,代码量很多,称之为锁粒度粗,反之称之为锁粒度细
Java的锁
- 悲观锁: 悲观地认为,这个操作一定会被别的线程打断==>(synchronized)
- 乐观锁: 乐观地认为,这个操作不会被别的线程打断 (又称之为自旋锁,或者无锁) ==> CAS锁(Compare And Swap/Compare And Set/Compare And Exchange)
悲观锁
也就是我们常用的synchronized,线程上来直接先锁了再说
其他的线程想要获取这把锁需要在队列中等待前一个线程释放该锁
CAS锁
所谓CAS,就是比较并交换的意思
从内存中取出来值,运算完后在写回前检查一下是否数据发生了改变,发生改变了就拿新的值再运算,如果没有发生改变,则写回到内存里
CAS中的ABA问题
在比较n的值时,发现没有变动过,但真的确实没有变动过吗??
也许在A线程取值运算n++的时候,被这个n=0被其他线程拿去n+=100后又被另外一个线程n-=100,但是在A线程看来,并没有发生变化
这个问题对于一个基本数据类型来讲,确实没有任何变化,但是对于引用数据来讲,由于n指向的是一个对象的首地址,在线程A对其中某个属性修改完写回的时候,引用没有发生改变,但是这个对象也许在线程B中被修改了某个属性,你这时写回的时候,原来的引用没有发生改变,但是其中的某个属性改变了,线程A这时写回,也许就会覆盖掉原来线程B写的属性,这时会出现问题
cas中的ABA问题解决:
在取出来进行运算的时候,给对象头中添加版本号,任何改变都会影响这个版本号,如果没有发生改变,则可以确定当前这个对象前后都没有发生改变
cas中原子性问题:
CAS肯定会发生两步: 比较 + 交换 但是,若在比较之后交换之前来了另外一个线程修改了…怎么办呢?
怎么保证CAS操作的原子性呢?
使用Atomic*
类
例如:
package com.xjk.TestJUC.CASAtomicity;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: XJK
*/
public class TestThreadCASAtomic {
AtomicInteger integer = new AtomicInteger();
void fun(){
for (int i = 0; i < 10000; i++) {
integer.incrementAndGet(); // integer++;
}
}
public static void main(String[] args) {
TestThreadCASAtomic casAtomic = new TestThreadCASAtomic();
ArrayList<Thread> threadArrayList = new ArrayList<>();
for (int i = 0; i < 10; i++) { // 10个线程
threadArrayList.add(new Thread(casAtomic::fun,"thread-"+i));
}
threadArrayList.forEach(thread -> {
thread.start();
});
threadArrayList.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(casAtomic.integer);
}
}
运行结果:
CAS锁的分析
以AtomicInteger举例:
上面的代码中有个方法是使integer自增,我们来源码上分析分析到底是怎么上锁保证原子性的?
incrementAndGet()
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
通过Unsafe类中的getAndAddInt()方法实现
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = this.getIntVolatile(o, offset);
} while(!this.compareAndSwapInt(o, offset, v, v + delta));
return v;
}
此时看到了compareAndSwapInt()方法,但是好像还是没有看到锁相关的东西
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
native本地的方法
那么我们移步进入Hotspot代码中:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
看到了一个方法cmpxchg()方法
看到了内联汇编代码中有LOCK_IF_MP(%4)
,表示在汇编层面如果是多核CPU就上锁,单核完全不需要添加锁,cpu层面的不会被打断
这说明cmpxchg中就是cpu给定的CAS操作指令
cmpxchg
并没有保证原子性
所以综上所述,Java中的CAS操作到底层编译成汇编的时候会使用CPU提供的CAS指令cmpxchg
并在执行前给这个指令上锁从而保证原子性操作
悲观锁和乐观锁的效率比较
悲观锁是的其他线程在获取到锁之前会在队列中等待
乐观锁的其他的线程会不断轮询,直到获得锁资源,而其他线程不停地执行,会占用cpu资源
所以具体什么时候用悲观锁,什么时候用乐观锁?
当线程执行时间很长,等待的线程数很多的时候,使用悲观锁,反之使用乐观锁,具体看业务情况
markword实现表
锁升级的过程(偏向锁,轻量级锁,重量级锁):
非重量级锁(偏向锁+轻量级锁)
所谓非重量级锁,即不通过OS进入内核态进行上锁,只需要在当前用户态进行上锁的状态
偏向锁: 多数时间synchronized代码段在执行时只有一个线程,将当前线程的指针(线程id)存储在markword中,那么不需要任何竞争机制, 一般在程序启动4s后开启,这个时候创建的对象是属于匿名偏向对象(使用-XX:BiasedLockingstartupDelay=0
来设置这个时延)
public class TestJOL {
public static void main(String[] args) throws InterruptedException {
// 延时可产生偏向对象
Thread.sleep(5000);
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("===================================================================");
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
轻量级锁: 当出现有很少的竞争时,markword中的线程id会取消,转而使用Lock Record指向线程栈中用来标志当前线程的一个区域,而另外的线程会在用户态不断轮询当前这个锁上的Lock Record,前一个线程释放锁,删除这个Lock Record,将自己的Lock Record设置到这个锁中
public class TestJOL {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("===================================================================");
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
什么时候可能出现从普通对象变成偏向锁?
这与批量重偏向和批量锁撤消有关
什么时候使用轻量级?
在明确知道会出现锁竞争的时候,就不适用偏向锁,直接使用轻量级锁,JVM启动会有很多线程竞争,所以默认情况下不启动偏向锁,会在启动后过一段时间才会打开
为什么需要从轻量级锁升级到重量级锁
自旋是消耗CPU资源的,如果锁的时间长,或者自旋线程多,CPU会被大量消耗
重量级锁有等待队列,所有拿不到锁的进入等待队列,不需要消耗CPU资源
重量级锁
需要通过向OS申请内核态锁
synchronized是可重入锁
重入次数必须要记录,因为需要上锁几次就得解锁几次,那么记录在哪呢?
偏向锁或自旋锁: 记录在线程栈中
重量级锁: C++的一个ObjectMonitor字段中
自旋锁什么时候升级为重量级锁?
竞争加剧:有线程超过10次自旋,-XX:PreBlockSpin,或者自旋线程数超过CPU核数的一半,1.6之后,加入自适应自旋Adapative Self Spinning , JVM自己控制
升级重量级锁: 向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间
异常跟锁
**程序在执行过程中,如果出现异常,默认情况锁会被释放。**所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
比如,在一个web程序处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。因此要非常小心的处理同步业务逻辑中的异常
public class Test4 {
int num = 0;
synchronized void fun() {
System.out.println(Thread.currentThread().getName()+"开始启动");
for(;;){
num++;
System.out.println(Thread.currentThread().getName()+" 此时num的值为:" + num);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num==10){
int tt = 1/0; // 抛出异常,锁会立马被释放,想要锁不被释放,可以通过try catch捕获并让循环继续
System.out.println(tt);
}
}
}
public static void main(String[] args) {
Test4 test4 = new Test4();
Runnable run = ()->{
test4.fun();
};
new Thread(run,"线程1").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(run,"线程2").start();
}
}
两个关键字
synchronized
-
锁静态对象:
public class Test2 { private static Object o = new Object(); public static void main(String[] args) { synchronized (o){ // doing someting... } } }
-
锁普通方法 等价于 synchronized(this)
public synchronized void fun(){ // doing something... }
public void fun(){ synchronized (this){ // doing something... } }
-
锁静态方法 等价于 synchronized(Test2.class) Test2是静态方法所在的类
public static synchronized void fun(){ // doing something }
public static void fun(){ synchronized (Test2.class){ // doing something... } }
三者本质上都是使用对象作为锁1.object对象 2.this 3.Test2.class对象
可重入属性
一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁, 也就是说synchronized获得的锁是可重入的
public class Test3 {
public static void main(String[] args) {
new Test3().fun1();
}
void fun1() {
synchronized (this) {
System.out.println("fun1 start running...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
fun2();
System.out.println("fun1 end");
}
}
void fun2() {
synchronized (this) {
System.out.println("fun2 start running...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun2 end");
}
}
}
优化
减少锁的粒度,同步代码块应越小越好
public class TestOptimizeSynchronized {
private static int num = 0;
public synchronized void fun1(){
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
// 非同步其他业务代码
num++;
// 非同步其他业务代码
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
}
public void fun2(){
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
// 非同步其他业务代码
synchronized (this){
num++;
}
// 非同步其他业务代码
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
System.out.println("==============");
}
}
fun2方法显然要比fun1方法要好
以上便是锁细化
但是如果出现一个业务代码中存在很多很多小锁,而非同步代码又很少,那就直接在函数上添加锁 (锁粗化)
package hahaha.xjk.TestJUC;
/**
* @author: XJK
*/
public class TestOptimizeSynchronized {
private static int num = 0;
public synchronized void fun1(){
num++;
num++;
num++;
num++;
num++;
num++;
num++;
num++;
num++;
// 非同步其他业务代码就一行
System.out.println("==============");
}
public void fun2(){
synchronized (this){
num++;
}
synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}synchronized (this){
num++;
}
// 非同步其他业务代码就一行
System.out.println("==============");
}
}
此时的fun1显然要比fun2更好
问题,当使用的object引用发生了改变,那么其他的线程会永远拿不到这把锁
所以一般将用于做锁的对象 设置为 finial
底层实现
将Test2.java编译成字节码会发现在main方法中的字节码指令如下:
这个时候引入了一个叫做monitor的概念
monitor翻译过来表示监视器
通过这个字节码指令我们知道了synchronized主要是通过monitorenter指令进行加锁
既然这样,我们在Java层面是不能看明白monitorenter的实现了,我们需要去看jvm的代码
在看jvm代码之前,先查看《The Java® Virtual Machine Specification Java SE 8 Edition》中对monitorenter的要求
通过Description:我们可以知道:想要实现这个monitorenter指令必须满足下面三个要求:
- 如果与对象引用关联的监视器的条目计数为零,则该线程将进入监视器,并将其条目计数设置为1, 监视器锁定当前线程
- 如果线程已经拥有与对象引用关联的监视器,那么它将重新进入监视器,并增加其条目计数 (可重入)
- 如果另一个线程已经拥有与对象引用关联的监视器,则该线程将阻塞,直到监视器的条目计数为零,然后再次尝试获得所有权
总之,就必须满足这三个要求,那么我们看看hotspot是如何实现这个指令的?
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
if (PrintBiasedLockingStatistics) { // 打印偏向锁统计数据
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
// 重试快速条目,如果偏向锁被撤销,以避免不必要的膨胀(进入轻量级锁)
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
那么这里就有两种走向:
- fast_enter: (进入偏向锁)
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) { // 使用偏向锁
if (!SafepointSynchronize::is_at_safepoint()) { // 如果不在线程安全点
// 撤销偏向锁并重新添加偏向锁
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
BiasedLocking::revoke_at_safepoint(obj); // 在线程安全点撤销偏向锁
}
}
slow_enter (obj, lock, THREAD) ;
}
进入revoke_and_rebias中:
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
markOop mark = obj->mark();
if (mark->is_biased_anonymously() && !attempt_rebias) { // 标识对象为匿名偏向对象,并且不进行重添偏向
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED;
}
} else if (mark->has_bias_pattern()) { // 偏向标志位存在
Klass* k = obj->klass();
markOop prototype_header = k->prototype_header();
if (!prototype_header->has_bias_pattern()) {
markOop biased_value = mark;
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
return BIAS_REVOKED;
} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
if (attempt_rebias) {
markOop biased_value = mark;
markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED_AND_REBIASED;
}
} else {
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED;
}
}
}
}
- slow_enter(自旋锁或重量级锁)
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
if (mark->is_neutral()) { // 进入CAS
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
} else
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
lock->set_displaced_header(NULL);
return;
}
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);// enter进入重量级锁 // 这里面会用到OS线程争用状态调用操作系统
}
volatile
volatile是java虚拟机提供的轻量级同步机制
可以保证如下特性:
保证可见性
查看可见性概念中的demo
保证有序性
能够禁止指令重排序
不能保证原子性
public class Test6 {
volatile int num = 0;
void fun(){
for (int i = 0; i < 10000; i++) {
num++;
}
}
public static void main(String[] args) {
Test6 test6 = new Test6();
ArrayList<Thread> arrayList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
arrayList.add(new Thread(test6::fun,"thread"+i));
}
arrayList.forEach(thread -> {
thread.start();
});
arrayList.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(test6.num);
}
}
运行结果如下:
volatile能保证num的值是可见的,但是num++是非原子性操作,所以即便对于其他线程可见,也不会导致其他线程对此数值wait操作
测试 synchronized、AtomicLong、LongAdder性能差别
package hahaha.xjk.TestJUC.visibility;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* @author: XJK
*/
public class Test7 {
private static long num1=0;
private static AtomicLong num2= new AtomicLong(0);
private static LongAdder num3 = new LongAdder();
static final Object o = new Object();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[1000];
for (int i = 0; i < threads.length; i++) {
threads[i]=new Thread(
new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronized (o){
num1++;
}
}
}
});
}
long start = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long end = System.currentTimeMillis();
System.out.println("使用sync:运行时间: "+(end-start));
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(
new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
num2.incrementAndGet();
}
}
});
}
start = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
end = System.currentTimeMillis();
System.out.println("使用AtomicLong:运行时间: "+(end-start));
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
for (int j = 0; j < 10000; j++) {
num3.increment();
}
});
}
start = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
end = System.currentTimeMillis();
System.out.println("使用LongAdder:运行时间: "+(end-start));
}
}
注意,如果是计算次数过多,会出现sync更好的情况,这个要根据情况进行选择
java.util.concurrent下的一些锁
Lock 接口
定义了子类实现锁的相关基本方法
Lock 代替了 synchronized 方法和语句的使用
public interface Lock {
// 获取锁
void lock();
// 当前线程没有中断的情况下获得锁,一旦调用了线程中的interrupt,线程直接中断且锁抛出异常
void lockInterruptibly() throws InterruptedException;
// 如果在给定的等待时间内空闲并且当前线程没有被中断,则获取锁。
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 解锁
void unlock();
Condition newCondition();
}
Condition接口
Condition 代替了 Object 监视器方法的使用
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
ReentrantLock 可重入锁
什么是可重入锁? 我们的synchronized就属于可重入锁
可重入互斥 Lock 具有与使用 synchronized 方法和语句访问的隐式监视器锁相同的基本行为和语义,但具有扩展功能
package hahaha.xjk.TestJUC.TestNewCAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: XJK
*/
public class TestReentrantLock1 {
Lock lock = new ReentrantLock();
void function1(){
try {
lock.lock(); // 替换synchronized
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("经过"+i+"秒");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock(); // 执行结束必须解锁
}
}
void function2(){
try{
lock.lock();
System.out.println("function2方法执行...");
}
finally {
lock.unlock();
}
}
void function3(){
boolean locked= false;
try {
locked = lock.tryLock(5,TimeUnit.SECONDS); // 尝试获取锁,5秒后还获取不到就返回false
System.out.println("function3方法执行,是否获取到锁?"+locked);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(locked)lock.unlock();
}
}
public static void main(String[] args) {
TestReentrantLock1 testReentrantLock1 = new TestReentrantLock1();
new Thread(testReentrantLock1::function1).start();
new Thread(testReentrantLock1::function2).start();
new Thread(testReentrantLock1::function3).start();
}
}
ReentrantLock可以被线程中断
package hahaha.xjk.TestJUC.TestNewCAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: XJK
*/
public class TestReentrantLock1 {
Lock lock = new ReentrantLock();
void function4(){
try {
lock.lockInterruptibly();
System.out.println("f4 start");
TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
System.out.println("f4 end");
} catch (InterruptedException e) {
System.out.println("f4被中断了");
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestReentrantLock1 testReentrantLock1 = new TestReentrantLock1();
Thread thread4 = new Thread(testReentrantLock1::function4);
thread4.start();
thread4.interrupt(); // 可以被强制中断
}
}
ReentrantLock可以设置为是一把公平锁
通过Lock lock = new ReentrantLock(true);
进行设置
什么是公平锁呢? 当第一个线程已经获取到锁之后,另外的线程进来不会直接向第一个线程争抢锁,而是会先看等待队列中是否存在其他线程,如果存在则直接进入等待队列进行等待,若队列中不存在才会和线程1进行争抢锁
ReentrantLock和synchronized的区别:
- ReentrantLock是CAS锁 , synchronized存在锁升级的概念
- ReentrantLock可以通过tryLock返回是否获取到锁
- ReentrantLock可以通过lockInterruptibly对线程的interrupt方法做出响应
- ReentrantLock可以选择公平锁和非公平锁
CountDownLatch 倒计时门闩锁
package hahaha.xjk.TestJUC.TestNewCAS;
import java.util.concurrent.CountDownLatch;
/**
* @author: XJK
*/
public class TestCountDownLatch {
public static void main(String[] args) {
testCountDownLactch();
}
private static void testCountDownLactch(){
Thread[] threads = new Thread[100];
CountDownLatch countDownLatch = new CountDownLatch(threads.length); // 创建门闩,用来计算线程运行结束的个数
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
int r = 0;
for (int j = 0; j < 10000; j++) {
r+=j;
}
System.out.println("线程"+Thread.currentThread().getName());
countDownLatch.countDown(); // 每次start就减少countDownLatch的值
});
}
for (Thread thread : threads) {
thread.start();
}
try {
countDownLatch.await(); // 等待countDownLatch计时到0
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier 循环栅栏锁
package hahaha.xjk.TestJUC.TestNewCAS;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author: XJK
*/
public class TestCyclicBarrier {
public static void main(String[] args) {
// 当达到10个线程数,调用指定的Runnable动作
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
@Override
public void run() {
System.out.println("线程数满,通过");
}
});
for (int i = 0; i < 23; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName());
cyclicBarrier.await(); // 当达到10个线程数后,执行指定的动作,若不满10个则等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
使用场景:
相当于等到多个线程齐了才能进行某个操作:
线程1访问网关
线程2访问数据库
线程3访问文件
线程4执行某个计算
…
必须等到这几个线程都执行完了,才能进行某一项保存工作
Phaser 相位器
线程可以按照阶段执行, 在某个阶段需要多个线程共同作用或者某些阶段某些线程就停止执行等…
一般用不到
ReadWriteLock 读写锁(接口,具体实现为ReentrantReadWriteLock)
共享锁和排他锁
public class TestRWLock {
static Lock reentrantLock= new ReentrantLock();
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
private static int num =0;
public static void readFunc(Lock lock){
try {
lock.lock();
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"读数据完成~~~~");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void writeFunc(Lock lock,int newNum){
try{
lock.lock();
TimeUnit.SECONDS.sleep(1);
num = newNum;
System.out.println(Thread.currentThread().getName()+"写数据完成!!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
}
}
测试排他锁,测试两个方法传入的均为ReentrantLock
public static void main(String[] args) {
Runnable readRunnable = ()-> {
readFunc(reentrantLock);
};
Runnable writeRunnable = ()-> {
writeFunc(reentrantLock,100);
};
for (int i = 0; i < 10; i++) {
new Thread(readRunnable).start();
}
for (int i = 0; i < 5; i++) {
new Thread(writeRunnable).start();
}
}
测试结果如下:
测试共享锁,分别传入ReadLock和WriteLock
public static void main(String[] args) {
Runnable readRunnable = ()-> {
readFunc(readLock);
};
Runnable writeRunnable = ()-> {
writeFunc(writeLock,100);
};
for (int i = 0; i < 10; i++) {
new Thread(readRunnable).start();
}
for (int i = 0; i < 5; i++) {
new Thread(writeRunnable).start();
}
}
测试结果:
Semaphore 信号灯
设置n个信号灯,当信号灯被acquire的时候,其他线程被阻塞,无法执行,当信号灯被release之后,其他线程才可运行
测试1: 当信号灯只有一个许可的时候
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
new Thread(()->{
try {
semaphore.acquire();
System.out.println("t1 开始...");
TimeUnit.SECONDS.sleep(1);// 代表业务方法
System.out.println("t1 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();
}
}).start();
new Thread(()->{
try {
semaphore.acquire();
System.out.println("t2 开始...");
TimeUnit.SECONDS.sleep(1);// 代表业务方法
System.out.println("t2 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();
}
}).start();
}
}
执行结果如下:
测试2: 当信号灯中信号等于或多于当前可运行的线程个数时
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
new Thread(()->{
try {
semaphore.acquire();
System.out.println("t1 开始...");
TimeUnit.SECONDS.sleep(1);// 代表业务方法
System.out.println("t1 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();
}
}).start();
new Thread(()->{
try {
semaphore.acquire();
System.out.println("t2 开始...");
TimeUnit.SECONDS.sleep(1);// 代表业务方法
System.out.println("t2 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();
}
}).start();
}
}
执行结果如下:
这个信号量锁同样是可以设置为公平锁
Exchanger 交换器
用于线程数据交换用的
public class TestExchanger {
public static void main(String[] args) {
Exchanger<Integer> exchanger =new Exchanger<>();
new Thread(()->{
Integer num = 111;
try {
num = exchanger.exchange(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1,原本的值为111,交换后num="+num);
}).start();
new Thread(()->{
Integer num = 999;
try {
num = exchanger.exchange(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2,原本的值为999,交换后num="+num);
}).start();
}
}
LockSupport
用于创建锁和其他同步类的基本线程阻塞原语
public class TestLockSupport {
public static void main(String[] args) {
Thread thread = new Thread(()->{
try{
for (int i = 0; i < 10; i++) {
System.out.println(i);
if(i==5){
System.out.println("i="+i+"时,线程封印");
LockSupport.park();
}
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程8秒后解开thread封印");
LockSupport.unpark(thread);
}
}
测试结果如下:
我们可以通过这个LockSupport进行指定业务逻辑的暂停执行
以上便是我对Java锁相关的学习以及探究,排版可能不太好看,不过希望能对大家有所帮助,若有更深的理解或有误的地方请大佬们指出,谢谢~