synchronized 关键字作为同步锁时有哪些用法?请举例说明。
参考回答
synchronized
关键字是 Java 提供的一种内置同步机制,用于解决多线程环境下的线程安全问题。它可以用来修饰方法或代码块,保证同一时间只有一个线程可以访问被同步的代码。
synchronized
的主要用法包括:
- 修饰实例方法:对当前实例对象加锁。
- 修饰静态方法:对类对象加锁。
- 修饰代码块:对指定对象加锁。
详细讲解与示例
1. 修饰实例方法
作用:
- 同步方法对当前实例对象加锁,多个线程访问同一实例的同步方法时会发生互斥。
示例:
public class SynchronizedInstanceMethod {
public synchronized void syncMethod() {
System.out.println(Thread.currentThread().getName() + " 开始执行");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行结束");
}
public static void main(String[] args) {
SynchronizedInstanceMethod obj = new SynchronizedInstanceMethod();
Thread t1 = new Thread(() -> obj.syncMethod(), "线程1");
Thread t2 = new Thread(() -> obj.syncMethod(), "线程2");
t1.start();
t2.start();
}
}
输出(线程互斥执行):
线程1 开始执行
线程1 执行结束
线程2 开始执行
线程2 执行结束
特点:
syncMethod()
方法锁住的是当前实例对象obj
,同一时间只有一个线程可以执行方法。
2. 修饰静态方法
作用:
- 同步静态方法对类对象(
Class
)加锁,无论多少线程访问该类的实例,静态方法都只能被一个线程访问。
示例:
public class SynchronizedStaticMethod {
public static synchronized void syncStaticMethod() {
System.out.println(Thread.currentThread().getName() + " 开始执行");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> SynchronizedStaticMethod.syncStaticMethod(), "线程1");
Thread t2 = new Thread(() -> SynchronizedStaticMethod.syncStaticMethod(), "线程2");
t1.start();
t2.start();
}
}
输出(静态方法锁类对象):
线程1 开始执行
线程1 执行结束
线程2 开始执行
线程2 执行结束
特点:
syncStaticMethod()
方法锁住的是类对象SynchronizedStaticMethod.class
。- 无论是否使用同一实例调用静态方法,线程都会互斥。
3. 修饰代码块
作用:
synchronized
代码块对指定的对象加锁,而不是方法或类。灵活性更高,可用于精确控制锁的范围。
示例:
public class SynchronizedBlock {
private final Object lock = new Object();
public void syncBlock() {
System.out.println(Thread.currentThread().getName() + " 尝试进入同步块");
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 开始执行同步块");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行结束");
}
}
public static void main(String[] args) {
SynchronizedBlock obj = new SynchronizedBlock();
Thread t1 = new Thread(() -> obj.syncBlock(), "线程1");
Thread t2 = new Thread(() -> obj.syncBlock(), "线程2");
t1.start();
t2.start();
}
}
输出(代码块锁对象):
线程1 尝试进入同步块
线程1 开始执行同步块
线程1 执行结束
线程2 尝试进入同步块
线程2 开始执行同步块
线程2 执行结束
特点:
- 只对指定的代码块加锁,锁住的对象是
lock
,线程必须持有lock
的锁才能进入同步块。 - 相比同步方法,锁的粒度更小,性能更高。
注意事项
- 对象锁与类锁不同
- 实例方法加锁(
synchronized
修饰实例方法)锁的是当前对象实例。 - 静态方法加锁(
synchronized
修饰静态方法)锁的是类对象。 - 两者互不影响。
- 实例方法加锁(
示例:
public class LockDifference {
public synchronized void instanceMethod() {
System.out.println(Thread.currentThread().getName() + " 执行实例方法");
}
public static synchronized void staticMethod() {
System.out.println(Thread.currentThread().getName() + " 执行静态方法");
}
public static void main(String[] args) {
LockDifference obj = new LockDifference();
Thread t1 = new Thread(() -> obj.instanceMethod(), "线程1");
Thread t2 = new Thread(() -> LockDifference.staticMethod(), "线程2");
t1.start();
t2.start();
}
}
输出:
- 实例方法和静态方法分别锁住不同对象,可以并发执行:
线程1 执行实例方法
线程2 执行静态方法
- 避免死锁
- 当多个线程同时请求多个锁时,可能会导致死锁。需要保证加锁顺序一致。
示例(容易死锁):
public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 获得锁1,等待锁2");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 获得锁2");
}
}
}
public void method2() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 获得锁2,等待锁1");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 获得锁1");
}
}
}
public static void main(String[] args) {
DeadlockExample obj = new DeadlockExample();
Thread t1 = new Thread(() -> obj.method1(), "线程1");
Thread t2 = new Thread(() -> obj.method2(), "线程2");
t1.start();
t2.start();
}
}
总结
synchronized
关键字的用法:
- 修饰实例方法:锁住当前实例对象。
- 修饰静态方法:锁住类对象。
- 修饰代码块:锁住指定对象,灵活性更高。
注意事项:
- 理解对象锁与类锁的区别。
- 注意锁的使用顺序,避免死锁。
- 选择合适的锁粒度,保证线程安全的同时优化性能。