关于多线程


1. synchronized 关键字是怎么起作用?

synchronized 关键字是 Java 中用于实现线程同步的重要机制。它可以应用于方法、代码块和静态方法,用于确保多个线程对共享资源的访问具有互斥性和可见性。

synchronized 关键字被应用于方法时,它锁定的是当前对象(或类的实例)的锁。以下是 synchronized 关键字的工作方式:

  1. 对象锁:当一个线程进入一个被 synchronized 修饰的实例方法时,它会尝试获取当前对象的锁。如果锁没有被其他线程占用,该线程将获得锁并执行方法体。如果锁已经被其他线程占用,该线程将被阻塞,直到锁被释放。
  2. 类锁:当一个线程进入一个被 synchronized 修饰的静态方法时,它会尝试获取该类的锁。类锁是在类级别上存在的,因此对同一个类的不同实例对象的静态方法调用仍然会被同步。只有当类锁没有被其他线程占用时,该线程才能获得锁并执行方法体。
  3. 代码块锁:synchronized 还可以应用于代码块级别的同步。当一个线程进入一个被 synchronized 修饰的代码块时,它会尝试获取指定对象的锁。只有获得了该对象的锁,该线程才能执行代码块中的内容。其他线程对同一个对象的 synchronized 代码块访问将被阻塞,直到锁被释放。

synchronized 关键字的作用如下:

  1. 互斥性:同一时刻只能有一个线程获得锁,进入 synchronized 代码块或方法,确保对共享资源的互斥访问。
  2. 可见性:当一个线程获得锁并修改了共享资源时,在释放锁之前,对其他线程是可见的。这样可以确保共享资源的修改对其他线程是可见的,避免了线程之间的数据不一致性。
  3. 共享资源保护:通过锁定共享资源的访问,避免了多个线程同时修改共享资源而导致的数据损坏或竞态条件。

需要注意的是,synchronized 关键字虽然提供了简单的线程同步机制,但在某些情况下可能会导致性能问题。因此,Java 还提供了其他更灵活的同步机制,如 Lock 接口和 ReentrantLock 类,以满足更复杂的同步需求。


2. synchronized 的颗粒度(或者说作用域)如何?是针对某个类还是针对某个类对象实例?

synchronized 关键字在Java中可以用于不同的级别,包括方法、代码块以及对象锁。它的作用范围可以根据这些不同级别而变化。

同步方法: 当你在一个方法前面加上synchronized关键字时,这个方法就被同步了。这意味着在任何时候只能有一个线程访问这个方法。如果多个线程试图同时访问这个方法,那么它们将会被阻塞,直到当前线程完成这个方法的执行。这个同步是针对类的,因为方法是属于类的。例如:

public synchronized void synchronizedMethod() {    
// ... method body ...
}

在这种情况下,所有线程在访问synchronizedMethod时都需要获得同一个锁,即这个类的Class对象。

同步代码块: 你可以使用synchronized关键字来同步一个代码块,而不仅仅是整个方法。这可以在某个特定的对象上同步,也可以在类上同步。例如:

  • 对象同步:
Object lock = new Object();
synchronized(lock) {    
// ... code block ...
}

在这个例子中,锁是特定的Object实例lock,任何线程在进入此同步代码块时都必须获得lock的锁。

  • 类同步:
synchronized(MyClass.class) {    
// ... code block ...
}

在这种情况下,锁是类的Class对象,任何线程在进入此同步代码块时都必须获得该类的Class对象的锁。

同步块和同步方法的比较:

同步方法和同步块的主要区别在于,同步方法是在进入方法时获得锁,而在退出方法时释放锁,而同步块则是在进入块时获得锁,在退出块时释放锁。因此,如果一个线程在等待进入一个被锁定的方法时,其他线程可以进入该方法中的其他代码块,但在等待进入一个被锁定的代码块时,其他线程不能进入该代码块中的其他代码块。

对象锁(实例方法和代码块): 当 synchronized 修饰实例方法或代码块时,它锁定的是当前对象的锁。每个对象实例都有自己的锁,因此对于同一个类的不同对象实例,它们的实例方法和代码块锁是相互独立的,互不影响。这意味着不同的对象实例可以并发地调用它们自己的 synchronized 方法或代码块,不会相互阻塞。
类锁(静态方法和代码块): 当 synchronized 修饰静态方法或代码块时,它锁定的是类对象的锁。类对象是在类级别上共享的,因此对于同一个类的不同对象实例,它们的静态方法或代码块共享同一个类锁。这意味着当一个线程获得了类锁并执行静态方法或代码块时,其他线程无法同时执行该类的静态方法或代码块,它们需要等待锁的释放。

总结而言,synchronized 关键字的颗粒度可以是对象级别(实例方法和代码块)或类级别(静态方法和代码块)。在对象级别上,每个对象实例都有自己的锁,互不干扰;而在类级别上,所有对象实例共享同一个类锁。根据具体的需求,可以选择适当的颗粒度来进行线程同步。


3. synchronized 对性能有没有影响?为什么?

synchronized 是 Java 中用于实现同步的关键字,它可以确保在同一时刻,只有一个线程可以执行由 synchronized 保护的代码块或方法。这对于避免并发问题(如多线程同时修改共享数据导致的数据不一致)非常重要。

然而,使用 synchronized 确实可能对性能产生一定的影响,原因如下:

  • 线程阻塞: 当一个线程正在执行一个 synchronized 代码块或方法时,其他尝试执行同一代码块或方法的线程将被阻塞,直到当前线程释放锁。这种阻塞可能导致线程等待,从而降低了程序的并行性,增加了执行时间。
  • 资源消耗: 使用 synchronized 还需要额外的资源来维护锁状态。锁的创建、销毁、获取和释放都需要额外的时间和计算资源。
  • 降低代码效率: 由于 synchronized 导致的阻塞和资源消耗,它可能会降低代码的执行效率。

但是,这些性能开销并不是绝对的,它们取决于具体的应用场景和并发压力。在一些情况下,使用 synchronized 是必要的,例如在修改共享资源(如数据库或文件)时。在这些情况下,性能开销是必要的,以避免并发问题。

为了尽可能减少 synchronized 对性能的影响,可以考虑以下几点:

  • 避免不必要的同步: 只在确实需要同步以避免并发问题的地方使用 synchronized。
  • 使用更细粒度的锁: 如果可能的话,使用更小的锁范围(即同步块),以减少线程阻塞的范围。
  • 使用并发库: Java 提供了许多高效的并发库(如 java.util.concurrent 包中的类),它们可能比使用 synchronized 更加高效。
  • 考虑无锁数据结构: 对于不需要修改共享资源的情况,使用无锁数据结构(如 java.util.concurrent.ConcurrentHashMap)可能更加高效。

总的来说,synchronized 对性能有一定的影响,但在许多情况下,这是为了确保程序的正确性和稳定性所必需的。然而,通过谨慎使用和优化,可以尽量减小这些影响。


4. volatile 关键字有啥用?啥时候需要用这个关键字?

volatile 关键字是Java虚拟机提供的最轻量级的同步机制。

在并发编程中,为了确保多个线程可以正确地共享数据,通常需要使用一些同步机制来保证数据的一致性和线程的安全性。volatile关键字是其中一种同步机制,它提供了一种轻量级的方式来保证共享变量的可见性和禁止指令重排。

当一个变量被声明为volatile时,它可以确保以下两点:

  • 可见性: 当一个线程修改了一个volatile变量的值后,其他线程可以立即看到这个变量的最新值。这是因为volatile关键字会强制刷新变量的值到主内存中,并且每次读取变量时都会从主内存中刷新。

  • 禁止指令重排: Java编译器和处理器可能会对指令进行重排序,以优化程序的执行。但是,对于volatile变量,编译器和处理器会保证不会对涉及volatile变量的读写操作进行重排序。

需要使用volatile关键字的情况通常包括:

  • 当多个线程需要共享一个变量,并且至少有一个线程会修改这个变量的值时,可以将这个变量声明为volatile。这样可以确保其他线程可以实时地看到修改后的值。
  • 当需要确保一个代码块在多线程环境下只被一个线程执行时,可以将这个代码块声明为volatile。这样可以禁止指令重排,避免多个线程同时执行这个代码块造成数据不一致的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值