在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
等)来提供更细粒度的控制。