线程锁和线程同步

 线程锁和线程同步

线程锁的概念

线程锁是一种用于控制多个线程对共享资源访问的机制,目的是确保在同一时刻,只有一个线程能够访问共享资源,避免出现数据不一致、竞态条件等问题。就像在生活中,一把钥匙对应一扇门,同一时间只有拿到钥匙的人能进入门内。

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修饰的方法或代码块时:

  1. 首先会检查该对象的锁标志位。如果锁标志位为 0,表示没有线程持有锁,那么该线程会获取锁,将锁标志位设置为 1,并进入同步代码。
  2. 如果锁标志位为 1,表示已经有其他线程持有锁,当前线程会被阻塞,进入该对象的等待队列,直到持有锁的线程释放锁(执行完同步代码块或方法,或者发生异常),然后被唤醒并重新尝试获取锁。

注意事项

  • 性能开销:虽然synchronized能有效解决线程安全问题,但它会带来一定的性能开销,因为线程的阻塞和唤醒都需要消耗系统资源。因此,要避免过度使用,尽量只对关键代码进行同步。
  • 死锁问题:在使用synchronized修饰多个对象的代码块时,如果线程获取锁的顺序不一致,可能会导致死锁。比如线程 A 持有对象 X 的锁,等待获取对象 Y 的锁,而线程 B 持有对象 Y 的锁,等待获取对象 X 的锁,此时两个线程都无法继续执行。
  • 我们重点要理解,synchronized锁的是什么。两个线程竞争同⼀把锁,才会产生阻塞等待。 两个线程分别尝试获取两把不同的锁,不会产⽣竞争。

你可能感兴趣的:(java,算法,开发语言)