在 Java 中,synchronized
关键字是一种用于实现线程同步的机制,它可以确保在同一时刻只有一个线程能够访问被synchronized
修饰的代码块或方法。
一、作用和原理
-
互斥访问:
synchronized
关键字通过对共享资源加锁来实现互斥访问。当一个线程进入synchronized
代码块或方法时,它会获取相应的锁,其他线程如果也想进入相同的synchronized
代码块或方法,就必须等待直到锁被释放。- 这样可以防止多个线程同时访问共享资源,从而避免数据不一致和竞争条件等问题。
-
锁的类型:
- 在 Java 中,每个对象都有一个内置的锁(也称为监视器锁)。当一个线程进入
synchronized
代码块或方法时,它实际上是获取了对象的内置锁。 - 对于
synchronized
方法,锁是当前对象实例(对于静态synchronized
方法,锁是类的Class
对象)。对于synchronized
代码块,可以指定一个对象作为锁。
- 在 Java 中,每个对象都有一个内置的锁(也称为监视器锁)。当一个线程进入
二、使用方法
synchronized
方法:
可以将一个方法声明为synchronized
,这样在同一时刻只有一个线程能够执行这个方法。
示例:
public class SynchronizedMethodExample {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public synchronized int getCounter() {
return counter;
}
}
synchronized
代码块:
可以在代码块上使用synchronized
关键字,并指定一个对象作为锁。
示例:
public class SynchronizedBlockExample {
private Object lock = new Object();
private int counter = 0;
public void increment() {
synchronized (lock) {
counter++;
}
}
public int getCounter() {
synchronized (lock) {
return counter;
}
}
}
三、可重入性
1.概念
synchronized
具有可重入性,这意味着一个线程可以多次获取同一个对象的锁。例如,如果一个synchronized
方法调用了另一个synchronized
方法,而这两个方法都在同一个对象上,那么同一个线程可以无需等待地进入第二个方法,因为它已经持有了对象的锁。
示例:
public class ReentrantSynchronizedExample {
public synchronized void method1() {
System.out.println("Inside method1.");
method2();
}
public synchronized void method2() {
System.out.println("Inside method2.");
}
}
四、注意事项和局限性
性能开销:
使用synchronized
会带来一定的性能开销,因为线程获取和释放锁需要进行一些系统调用和上下文切换。在高并发场景下,如果锁的竞争非常激烈,可能会导致性能下降。
死锁风险:
如果多个线程以不正确的顺序获取多个锁,可能会导致死锁。例如,线程 A 持有锁 1 并等待锁 2,而线程 B 持有锁 2 并等待锁 1,这样两个线程就会相互等待,导致死锁。
粒度问题:
如果synchronized
代码块或方法的粒度太大,可能会导致不必要的线程阻塞,降低并发性能。相反,如果粒度太小,可能会增加锁的获取和释放次数,也会影响性能。
总之,synchronized
关键字是 Java 中实现线程同步的一种重要机制,但在使用时需要注意性能开销、死锁风险和粒度问题等。在高并发场景下,可以考虑使用其他更高级的同步机制,如ReentrantLock
、Semaphore
等,它们提供了更多的功能和灵活性。