线程锁和线程同步
线程锁和线程同步
线程锁的概念
线程锁是一种用于控制多个线程对共享资源访问的机制,目的是确保在同一时刻,只有一个线程能够访问共享资源,避免出现数据不一致、竞态条件等问题。就像在生活中,一把钥匙对应一扇门,同一时间只有拿到钥匙的人能进入门内。
synchronized
关键字
synchronized
是 Java 内置的用于实现线程同步的关键字,它可以应用在以下几个方面:
1. 修饰实例方法
当synchronized
修饰一个实例方法时,锁对象是当前对象(this
)。这意味着在同一时刻,只有一个线程能够进入该实例方法进行操作。
class Counter {private int count = 0;// 修饰实例方法,锁对象是当前Counter实例public synchronized void increment() {count++;}public int getCount() {return count;}
}public class Main1 {public static void main(String[] args) {Counter counter = new Counter();Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终计数: " + counter.getCount());}
}
修饰静态方法
当synchronized
修饰静态方法时,锁对象是该类的Class
对象,因为静态方法属于类,所有该类的实例共享同一个Class
对象锁。
class StaticCounter {private static int count = 0;// 修饰静态方法,锁对象是StaticCounter类的Class对象public static synchronized void increment() {count++;}public static int getCount() {return count;}
}public class Main2 {public static void main(String[] args) {Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {StaticCounter.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {StaticCounter.increment();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终计数: " + StaticCounter.getCount());}
}
修饰代码块
可以使用synchronized
关键字修饰代码块,显式指定锁对象。这比修饰方法更加灵活,可以只对关键代码部分进行同步,提高程序性能。
class BankAccount {private int balance = 1000;public void transfer(BankAccount other, int amount) {// 这里使用this和other作为锁对象,保证转账操作的原子性synchronized (this) {synchronized (other) {if (this.balance >= amount) {this.balance -= amount;other.balance += amount;}}}}public int getBalance() {return balance;}
}public class Main3 {public static void main(String[] args) {BankAccount account1 = new BankAccount();BankAccount account2 = new BankAccount();Thread thread = new Thread(() -> {account1.transfer(account2, 500);});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("account1余额: " + account1.getBalance());System.out.println("account2余额: " + account2.getBalance());}
}
synchronized
的工作原理
当一个线程访问被synchronized
修饰的方法或代码块时:
- 首先会检查该对象的锁标志位。如果锁标志位为 0,表示没有线程持有锁,那么该线程会获取锁,将锁标志位设置为 1,并进入同步代码。
- 如果锁标志位为 1,表示已经有其他线程持有锁,当前线程会被阻塞,进入该对象的等待队列,直到持有锁的线程释放锁(执行完同步代码块或方法,或者发生异常),然后被唤醒并重新尝试获取锁。
注意事项
- 性能开销:虽然
synchronized
能有效解决线程安全问题,但它会带来一定的性能开销,因为线程的阻塞和唤醒都需要消耗系统资源。因此,要避免过度使用,尽量只对关键代码进行同步。 - 死锁问题:在使用
synchronized
修饰多个对象的代码块时,如果线程获取锁的顺序不一致,可能会导致死锁。比如线程 A 持有对象 X 的锁,等待获取对象 Y 的锁,而线程 B 持有对象 Y 的锁,等待获取对象 X 的锁,此时两个线程都无法继续执行。 - 我们重点要理解,synchronized锁的是什么。两个线程竞争同⼀把锁,才会产生阻塞等待。 两个线程分别尝试获取两把不同的锁,不会产⽣竞争。