在Java中,synchronized
关键字提供了内置的支持来实现同步访问共享资源,以避免并发问题。synchronized
主要有三种加锁方式:
1.同步实例方法
当一个实例方法被声明为synchronized
时,该方法将同一时间只能被一个线程访问。锁是当前对象实例(即this
)。
public class SynchronizedInstanceMethod {
public synchronized void doSomething() {
// 同步代码块
// 同一时间只能有一个线程执行这里的代码
}
public static void main(String[] args) {
SynchronizedInstanceMethod obj = new SynchronizedInstanceMethod();
// 创建多个线程访问obj的doSomething方法,它们将串行执行
// ...
}
}
当我们创建了两个线程来并发地增加计数器,由于我们使用了synchronized
,因此计数器的增加线程是安全的,即使两个线程都在尝试修改同一个共享变量。在同步实例方法中,锁分别是实例对象和类对象。
public class SynchronizedInstanceMethodExample {
private int count = 0;
// 同步实例方法,锁定的是当前对象的实例(this)
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " incremented count to " + count);
}
public static void main(String[] args) {
final SynchronizedInstanceMethodExample example = new SynchronizedInstanceMethodExample();
// 创建两个线程来增加计数器
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
}, "Thread-1");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
}, "Thread-2");
// 启动线程
thread1.start();
thread2.start();
// 等待线程完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终计数
System.out.println("Final count: " + example.count);
}
}
2.同步静态方法
当一个静态方法被声明为synchronized
时,该方法将同一时间只能被一个线程访问。锁是Class对象,而不是实例对象。
public class SynchronizedStaticMethod {
public static synchronized void doSomethingStatic() {
// 同步代码块
// 同一时间只能有一个线程执行这里的代码
}
public static void main(String[] args) {
// 创建多个线程访问SynchronizedStaticMethod的doSomethingStatic方法,它们将串行执行
// ...
}
}
当我们创建了两个线程来并发地增加计数器,由于我们使用了synchronized
,因此计数器的增加线程是安全的,即使两个线程都在尝试修改同一个共享变量。在同步静态方法中,锁分别也是实例对象和类对象。
public class SynchronizedStaticMethodExample {
private static int count = 0;
// 同步静态方法,锁定的是当前对象的类(Class)对象
public static synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " incremented static count to " + count);
}
public static void main(String[] args) {
// 创建两个线程来增加计数器
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
}, "Thread-1");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
}, "Thread-2");
// 启动线程...
// 等待线程完成...
// 输出最终计数...
// (与上面的例子类似,但省略了重复的代码)
}
}
3.同步代码块
我们可以使用synchronized
关键字来定义一个代码块,而不是整个方法。在这种情况下,你可以指定要获取的锁对象。这提供了更细粒度的同步控制。
public class SynchronizedBlock {
private final Object lock = new Object(); // 用于同步的锁对象
public void doSomething() {
synchronized (lock) {
// 同步代码块
// 同一时间只有一个线程能够执行这里的代码
}
// 这里的代码不受同步代码块的约束
}
public static void main(String[] args) {
SynchronizedBlock obj = new SynchronizedBlock();
// 创建多个线程访问obj的doSomething方法,但只有在synchronized块中的代码将串行执行
// ...
}
}
在上面的SynchronizedBlock
类中,我们创建了一个私有的Object
实例lock
作为锁对象。当线程进入synchronized (lock)
块时,它会尝试获取lock
对象的锁。如果锁已经被其他线程持有,那么该线程将被阻塞,直到锁被释放。
当我们创建了两个线程来并发地增加计数器,同步代码块的例子中,我们显式地指定了一个对象作为锁。
public class SynchronizedBlockExample {
private final Object lock = new Object(); // 用于同步的锁对象
private int count = 0;
// 同步代码块,指定了锁对象
public void increment() {
synchronized (lock) {
count++;
System.out.println(Thread.currentThread().getName() + " incremented count to " + count);
}
}
public static void main(String[] args) {
// 类似于上面的例子,但使用SynchronizedBlockExample的increment方法
// ...(省略了重复的代码)
}
}
注意:使用synchronized
时应该尽量避免在持有锁的情况下执行耗时的操作,因为这会导致其他等待锁的线程长时间阻塞。同时,过度使用synchronized
可能会导致性能下降,因为它会引入线程间的竞争和可能的上下文切换。在设计并发程序时,应该仔细考虑同步的粒度,并可能使用其他并发工具(如ReentrantLock
、Semaphore
、CountDownLatch
等)来提供更细粒度的控制。