synchronized 详细介绍
synchronized是Java语言中的一个关键字,代表同步锁,用于解决多线程环境下的线程安全问题。
它可以确保在同一时刻,只有一个线程可以执行被synchronized修饰的代码块或方法,从而避免多线程同时访问共享资源时可能出现的数据不一致问题。
一、synchronized的作用
-
原子性:保证一个操作或多个操作要么全部执行,要么全部不执行,中间不会被其他线程打断。
-
可见性:确保线程在释放锁之前对共享变量的修改对其他线程可见。
-
有序性:保证程序按照代码的顺序执行,防止指令重排导致的问题。
二、synchronized的用法
synchronized主要有三种用法:
-
修饰实例方法:
-
在方法声明时使用,放在范围操作符(如public)之后,返回类型声明(如void)之前。此时,线程获得的是成员锁,即一次只能有一个线程进入该方法。
-
示例:
public synchronized void method() { ... }
-
-
修饰静态方法:
-
作用于类的所有对象实例,进入同步代码前要获得当前类的Class对象的锁。
-
示例:
public static synchronized void staticMethod() { ... }
-
-
修饰代码块:
-
可以指定加锁对象,对给定对象或类加锁。
-
使用方式:
synchronized(lockObject) { ... }
,其中lockObject可以是任意对象或类的Class对象。 -
示例:
synchronized(this) { ... }
或synchronized(SomeClass.class) { ... }
-
三、synchronized的实现原理
-
Monitor机制:
-
在Java虚拟机(HotSpot)中,每个对象都与一个监视器(Monitor)关联,synchronized通过Monitor来实现同步。
-
当线程进入synchronized代码块时,会尝试获取对象的Monitor锁;获取成功后,线程进入同步代码块执行;执行完毕后,线程释放Monitor锁。
-
-
锁升级过程:
-
Java SE 1.6及以后版本中,synchronized引入了锁升级机制,包括无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。
-
锁升级的目的是为了减少线程获得锁和释放锁带来的性能消耗,提高程序的并发性能。
-
四、注意事项
-
避免死锁:在多线程环境下使用synchronized时,要注意避免死锁的发生。例如,两个线程分别持有对方需要的锁,并等待对方释放锁,从而导致双方都无法继续执行。
-
性能考虑:虽然synchronized提供了线程安全的保证,但在高并发场景下,其性能可能会受到影响。因此,在性能要求较高的场景下,可以考虑使用其他并发工具类(如ReentrantLock)来替代synchronized。
-
锁的粒度:锁的粒度越细,系统的并发性能就越好。因此,在使用synchronized时,应尽量避免对整个对象加锁,而是只对需要同步的代码块加锁。
示例
示例一:synchronized修饰实例方法
假设有一个银行账户类BankAccount
,其中包含存款和取款的方法,我们需要确保这些方法在多线程环境下是线程安全的。
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
// synchronized修饰实例方法,确保同一时间只有一个线程能执行该方法
public synchronized void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println(Thread.currentThread().getName() + " Deposited: " + amount);
}
}
// synchronized修饰另一个实例方法,同样确保线程安全
public synchronized void withdraw(double amount) {
if (balance >= amount && amount > 0) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " Withdrew: " + amount);
} else {
System.out.println("Insufficient funds");
}
}
// 假设还有其他非同步的方法...
}
在这个例子中,deposit
和withdraw
方法都被synchronized
修饰,这意味着当一个线程正在执行其中一个方法时,其他线程将无法执行这两个方法中的任何一个,直到当前线程执行完毕并释放锁。这里的锁是当前对象的实例锁。
示例二:synchronized修饰静态方法
考虑一个静态工具类Counter
,它用于统计某个事件的次数。
public class Counter {
private static int count = 0;
// synchronized修饰静态方法,确保同一时间只有一个线程能执行该方法
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
在这个例子中,increment
和getCount
方法都被synchronized
修饰,但它们修饰的是静态方法。
这意味着锁是类锁(Class Lock),而不是实例锁。因此,当一个线程正在执行这两个方法中的任何一个时,其他线程将无法执行这两个方法中的任何一个,直到当前线程执行完毕并释放锁。这确保了count
变量的线程安全性。
示例三:synchronized修饰代码块
有时候,我们可能只需要同步方法中的一小部分代码,而不是整个方法。这时,可以使用synchronized
修饰代码块。
public class DataStructure {
private final Object lock = new Object(); // 专门的锁对象
private int data = 0;
public void updateData(int newData) {
// 只同步需要同步的代码块
synchronized (lock) {
// 假设这里有一些复杂的逻辑需要同步
data = newData;
// ...
}
// 其他不需要同步的代码
System.out.println("Data updated by " + Thread.currentThread().getName());
}
}
在这个例子中,updateData
方法内部使用synchronized
修饰了一个代码块,并使用了一个专门的锁对象lock
。这意味着只有持有lock
锁的线程才能执行该代码块中的代码。这种方式比整个方法同步更高效,因为它减少了锁的持有时间,从而减少了线程等待锁的时间。
总结
-
synchronized修饰实例方法:锁是当前对象的实例锁。
-
synchronized修饰静态方法:锁是当前类的类锁。
-
synchronized修饰代码块:可以指定锁对象,更灵活地控制同步范围。
在使用synchronized时,需要注意避免死锁、减少锁的粒度以提高性能等问题。
这块实际操作过,还是没理解。