线程同步是多线程编程中的一个重要概念,用于解决多个线程并发访问共享资源时可能出现的数据不一致性问题。在Java中,线程同步主要通过关键字`synchronized`来实现,它可以确保在同一时间只有一个线程能够执行特定的代码块或方法。
1. 对象实例的锁:
当在实例方法前加上`synchronized`关键字时,该方法就成为同步方法。例如,`synchronized void f1()`等价于`void f2() { synchronized(this) { ... }}`。这里的锁是对象实例本身,即调用同步方法的对象。如果多个线程同时尝试访问同一对象的同步方法,只有一个线程能获取到对象锁并执行,其他线程必须等待。然而,对于不同对象实例来说,它们的锁是相互独立的,因此一个对象的同步方法不会阻止其他对象的相同方法被并发执行。这意味着同步方法只对同一个实例具有同步效果,无法跨实例同步。
2. 类的锁:
当在静态方法前使用`synchronized`关键字,或者同步一个类的静态变量,如`synchronized static void f1()`,实际上是锁定该类的Class对象。这与对象实例锁不同,因为它影响的是所有该类的实例。所有线程在调用此类的静态同步方法时,都需要获取到类的Class对象锁,因此,无论多少个对象实例,只要调用此类的静态同步方法,都会进行同步。这种锁机制在实现单例模式的懒汉式初始化时特别有用,确保了在多线程环境下只有一个实例被创建。
在实现类锁时,有时候我们会自定义一个同步对象,例如`final static Object o = new Object()`,然后在同步代码块中使用它,如`synchronized(o) { ... }`。这种方法与直接同步类的静态方法效果相同,只是使用了一个特定的对象作为锁。
为了更高效地实现类锁,有时会使用零长度的`byte[]`数组作为锁对象,因为它的创建和内存占用相对较小。例如,`private byte[] lock = new byte[0];`。这样做的好处是在创建和垃圾回收时节省资源,但基本原理仍然是通过对象锁实现同步。
线程同步在Java中主要用于保护共享资源的安全,防止数据竞争和不一致。对象实例锁确保了同一时刻只有一个线程能访问特定实例的方法,而类锁则确保所有线程在调用类的静态方法时按照一定的顺序执行,避免了数据混乱。理解并合理运用这些同步机制是编写安全、高效的多线程Java程序的关键。