synchronized的作用
示例
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
new Thread(()->{
incrment();
}).start();
}
Thread.sleep(3000);
System.out.println(num);
}
private static int num = 0;
public static void incrment(){
for (int i = 0; i < 1000; i++) {
num++;
}
}
运行结果
9448
Process finished with exit code 0
incrment()是对变量num进行1000词的++操作,示例中使用10个线程去执行incrment()方法,期望的结果是num值为10000,但运行出来的结果却小于10000。这是为什么呢?
当对num进行++操作,多个线程同时访问时,第一个线程将num读出来,假设读到的是0,然后把它+1,但在这个还没有写回去的时候,第二个线程来了读到它还是0,也对其+1,之后写回去,发现两个线程都是读到的0,写回去的是1,本来应该加两次变成2,结果却是1,这就出现了少加的现象
加锁可以有效的解决这种现象
加锁后的代码
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
new Thread(()->{
incrment();
}).start();
}
Thread.sleep(3000);
System.out.println(num);
}
private static int num = 0;
public synchronized static void incrment(){
for (int i = 0; i < 1000; i++) {
num++;
}
}
运行结果
10000
Process finished with exit code 0
使用synchronized加锁后得到所期望的结果,当第一线程执行incrment()结束之前不允许其他线程也执行此方法
synchronized锁的是什么
当锁的方法为普通方法时,锁的是当前对象,也就this
当锁的方法为静态方法时,由于静态方法没有this,所有锁的是当前类的class
注:synchronized是可重入的,如果代码出现了异常,锁会被释放
synchronized的底层实现
JDK早期的时候,synchronized是重量级的,需要找操作系统去申请锁,这是synchronized性能很低;后来synchronized改进之后有了锁升级的概念,当使用synchronized时在对象头(markword)记录这个线程,只有一个线程访问时内部只记录了这个线程的ID并没有加锁,如果有线程争用时才会升级为轻量级锁,线程数多了之后会升级到重量级锁
锁的升级
不加锁:顾名思义,不使用synchronized加锁
偏向锁:在线程无竞争时消除同步操作,如果该锁一直没有被其他线程获取,那么这个线程不进行同步操作;一旦出现另一个线程去尝试获取这把锁,偏向锁会立刻转换为轻量级锁
轻量级锁:JVM使用CAS的方式更改markword中的锁状态来实现的,它在没有多线程竞争的前提下,减少系统互斥量操作产生的性能消耗
重量级锁:JVM调用了操作系统提供的同步机制,也就是硬件层面的一条指令。lock cmpxchg xxx