为什么要锁
当多个线程要对同一个目标进行修改的时候,为了保证数据的一致性,有序性,所以进行加锁。
通过对一个对象进行加锁,只有一个线程拿到这个对象,才能执行一段代码。
Synchronized的特性
- 原子性
- 可见性
-
“对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
-
- 但是不保证有序性
synchronized的基本知识点
-
synchronized方法的变型
synchronized关键字实际修饰的是一个Object 类,类似于synchronized(Object o), 如果直接修饰synchronized 方法,则等同于
锁的是该类的this对象。
-
package juc.sync; public class T1 { private int count = 0; private static int count1 = 0; private Object obj = new Object(); public void m() { synchronized(obj) { count++; System.out.println(Thread.currentThread().getName() + "count="+count); } } public synchronized void m1() { count++; System.out.println("等同于synchronized(this)"); } public synchronized static void m2() { count1++; System.out.println("等同于synchronized(T1.class)"); } public static void main(String[] args) { } }
-
同步方法和非同步方法可否一起调用?
答案是可以,非同步方法没有任何锁,当然这段代码可以被多个线程随时访问。
package juc.sync;
//同步方法和不同步的方法可否一起调用
public class T3 {
public synchronized void m1() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "synchronized");
}
public void m2() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "not synchronized");
}
public static void main(String[] args) {
T3 t = new T3();
// new Thread(()-> {
// t.m1();
// }).start();
new Thread(t::m1,"t1").start();
// new Thread(()-> {
// t.m2();
// }).start();
new Thread(t::m2,"t2").start();
}
}
-
Synchronized可重入?
可重入的意思是一个同步方法是否可以调用另一个同步方法?可以,因为第一个方法拿的是一个对象锁,到第二方法的时候发现它也是同一把锁的话,是允许调用第二个方法的代码块的。
package juc.sync;
public class T4 {
synchronized void m1() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
m2();
System.out.println(Thread.currentThread().getName() + "调用完m2");
}
synchronized void m2() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m2 end");
}
public static void main(String[] args) {
T4 t = new T4();
t.m1();
}
}
-
该线程发生议程会释放锁
- 同步队列和等待队列的区别
- 同步队列指得是在runnable的线程,正在准备竞争锁的队列,如果锁被占用了,就会一直在同步队列里
- 等待队列指的是在wait的线程,比如线程正在wait的,sleep的都会放到等待队列,等到被notify或者时间到了,就可以进入同步队列去竞争锁了。
JVM的锁的底层实现
hotspot的实现,是锁的对象上面有一个64位的markword,64位中的2位用来判断对象是否被锁。
JDK早期都是重量级,就是直接对OS去申请这个锁,效率非常低。
现在采取了锁升级的方法。
比如我们sync(Object o)
o上有一个markword
锁升级的过程
1)最开始Object o的时候是无锁态。
2)然后有线程A要进去,就在这个锁(门)上打上一个标记A,偏向锁,告诉这样下次线程A再进来的时候看到标记A,就可以直接进去了。也就是在markword上记录这个线程ID
3) 如果这个时候线程B再来,就要和线程A竞争锁(门),这个通过CAS的过程来竞争,就是读门上的标签,如果为空,就去把线程B的地址写到门上,但写的过程发现标签变了,就自旋等待。
4)如果自旋的线程超过一定数量,就会触发锁升级到重量级/系统锁,就全部进入锁的队列中,不再自旋等待了。
自旋锁和系统锁的选择
自旋锁是消耗CPU, 而系统锁会引起阻塞。
线程的执行时间短且线程少可以用自旋锁,否则用重量级锁
无锁CAS-> 锁的优化
CAS并不是通过加OS锁,
为了解决syncronize的性能问题,使用CAS来解决,不需要加同步锁
CAS(value, expected,NewValue)
先是判断当前读出来的value值与expected进行比较,如果相当则更新为新值V, 如果不相等,就要重新读取value值,一直等到相等的时候,更新为新值V。所以这个线程会一直自旋在那里等待着两值相等。
- Atomic类
- AtomicInteger类