说到synchronized可以说是java 多线程第一节课了。每java程序员都知道这个关键字。这里就这个关键字的原理和使用在介绍一次,一方面巩固理解,一方面和大家讨论一下。
这个关键词使用起来很简单,就是将代码段,或者方法,加锁实现串行执行,可是锁是什么?这个就是一个初学者常见的坑。
锁有3种:1.成员属性锁。2,对象锁(也就是this关键字;或者方法加上synchronized,静态方法除外)3.类锁(Test.class;静态方法加上synchronize也是)。
什么时候用什么锁,只要保持一个原则,可见原则,解释一下,就是你同步的代码中是否看到的都是一个锁。
比如说:静态方法,就要用类锁。因为静态方法所属于类。对象还不存在,不能用对象锁。
普通方法,可以用对象锁。如果是成员属性锁,那么这个必须在方法调用前初始化,同时不能改变。
这是时候就有一个疑问,我都用类锁,所有人都可以看见都是一个,不就行了吗。理论上使可行的,但实际行不通。因为锁的方法,串行的,其他人都要等待,这会让你的程序只有一个用户可以访问,其他人等着。(在实际中通常行不通,除非你就是想玩用户)
有了多用户的需求,我们就尽量减少同步时间,就产生了以下原则:
1.尽量使用低阶别的锁,成员属性锁<对象锁<类锁。
2.尽量使用同步块代替同步方法,减少同步代码。
3 耗时的代码进行不要在同步块中。(这里强调一下,不要把sql放到同步块中,你可能想这样就不会发生数据不一致问题,但是如果是集群呢,数据库还要靠事务保证,不是程序同步块)
下面用代码说一下使用:
package com.ldh.syn;
public class SynTest1 {
private int i = 0;
public synchronized void run(){
System.out.println(++i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final SynTest1 t = new SynTest1();
for(int i=0;i<20;i++){
new Thread(()->{
t.run();
}).start();;
}
}
}
这段代码实现一次计数,计数后休息3秒,模仿慢代码执行。20次计数就要花费60s时间。所以尽量减少同步块大小。
package com.ldh.syn;
import java.util.Date;
public class SynTest extends Thread {
private static int i = 0;
private Date startDate ;
public SynTest(Date startDate) {
this.startDate = startDate;
}
@Override
public synchronized void run() {
System.out.println(++i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Date date = new Date();
System.out.println(date.getTime()-startDate.getTime());
}
public static void main(String[] args) {
final Date startDate = new Date();
for(int i=0;i<20;i++){
new SynTest(startDate).start();
}
}
}
这段代码看上去没什么问题,其实是错的并不能实现同步功能,你运行的时候可会出现打印1-20 。那是因为start(),需要时间,先启动的一定先执行。
错误的原因是,每次都new 一个 synTest的对象。普通同步方法是对象锁,观察的不是一个对象(锁),所以不能实现同步效果。
简单说一下底层实现:
synchronize 同步后,会发生阻塞,阻塞的线程进入阻塞池(相当于一个队列),线程执行完成后,队列中的线程进行抢夺,抢到时间片的进行执行代码。
jdk1.8中,jvm开发人员发现其实大部分时候不会发生阻塞强占,都是一个线程在自己抢夺自己(你可能问自己强自己为什么,因为分时操作系统,一个时间片,你不一定能执行完同步块中的代码。)这时候设计了3中所结构: 偏向锁,轻量级锁,重量级锁。
偏向锁,具有自己的引用,如果多个线程冲突,那么就自动让自己通过。
轻量级锁,如果多个线程同时进行强占,那么将偏向锁升级为轻量级锁。这时候会有自旋,就是不断的抢夺。
重量级锁,如果有更多线程同时进行强占,将轻量级锁升级为重量级锁。大家进行等待,可以减少自旋。
总结一下,以上3种锁为了更快的让一个线程执行代码,减少阻塞所带来的等待时间。如果是单线程无冲突的情况下,加锁比不加锁效率已经差距不大了。
参考文章: http://ifeve.com/java-synchronized/