Java语言提供了synchronized关键字,可以给方法或代码块进行加锁,从而实现同步。
synchronized关键字取的锁都是对象锁,而不是把代码块或方法当做锁。主要有以下几种场景:
(1) 同步化类的非静态方法,取的调用该方法的对象上的对象锁;
(2) 同步化类的静态方法,取的是类的Class对象上的对象锁;
(3) 同步化代码块,synchronized(obj){...},取的是obj上的对象锁;
(4) 同步化代码块,synchronized(this),取的是当前对象上的对象锁;
(5) 同步化代码块,synchronized(类名.class),取的是类的Class对象上的对象锁。
synchronized关键字拥有锁重入功能,也就是说在使用synchronized时,当一个线程得到对象锁后,它再次请求该对象锁也是可以再次取得的。因此,在一个同步化方法/块的内部调用本类的其他同步化方法/块,是永远可以得到锁的。
当一个线程执行的代码出现异常时,其所持有的锁会自动释放,一个示例如下:
public class Main { public static void main(String[] args) { Service s = new Service(); Thread ta = new DispatcherThread("a", s); Thread tb = new DispatcherThread("b", s); ta.start(); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } tb.start(); } public static class DispatcherThread extends Thread { Service service; public DispatcherThread(String name, Service service) { super(name); this.service = service; } @Override public void run() { service.f(); } } public static class Service { public synchronized void f() { System.out.println(Thread.currentThread().getName() + " start to run."); g(); } public synchronized void g() { if (Thread.currentThread().getName().equals("a")) { throw new RuntimeException(); } } } }
Exception in thread "a" a start to run. java.lang.RuntimeException at com.huawei.nlz.multithread.Main$Service.g(Main.java:42) at com.huawei.nlz.multithread.Main$Service.f(Main.java:37) at com.huawei.nlz.multithread.Main$DispatcherThread.run(Main.java:29) b start to run.
可以看到,在a抛出异常后,线程b顺利进入同步化方法中。
子类覆盖父类的同步方法时,需要手动指明同步性,否则则不会被同步化。
用String常量做同步锁时,要注意字符串的常量池特性。
只要锁对象不变,只改变锁对象的属性,对同步就不会有影响。
synchronized关键字的底层实现原理:参见zejian_专家的https://blog.csdn.net/javazejian/article/details/72828483。