网站首页 包含标签 线程 的所有文章

  • java面试题:为什么JDK 15要废弃偏向锁?

    为什么JDK 15要废弃偏向锁? 要想说清楚这个问题,你得先知道什么是偏向锁,它是在哪里使用的。 这就不得不提到Synchronized的锁升级过程了。 在JDK 1.6及之前的版本中,Synchronized关键字,它可以让一个对象只能被一个线程使用,这样就可以避免多个线程同时修改同一个对象造成的混乱。 但是,这种方式也有一个缺点,就是每次一个线程要使用一个对象时,都要先检查这个对象是否被别的线程占用了,如果是的话,就要等待别的线程用完才能继续。 这个过程会消耗很多时间和资源,影响程序的效率。 为了解决这个问题,Java在后来的1.7 之后对 Synchronized 的实现做了一些改进,让它可以根据不同的情况,采用不同的方式来控制对象的使用。 具体来说,有四种方式: 无锁状态:这是最简单的一种方式,就是没有任何限制,任何线程都可以随时使用对象。 偏向锁状态:这是一种优化的方式,就是假设一个对象只会被一个线程使用,所以当第一个线程使用对象时,就会把自己的标记放在对象上,表示这个对象属于自己。这样,下次这个线程再使用对象时,就不用检查了,直接就可以用。但是如果有别的线程也想用这个对象,就要先把标记去掉,然后再竞争。 轻量级锁状态:这是一种折中的方式,就是当有多个线程想用同一个对象时,不会立刻让它们等待,而是先让它们在自己的内存里复制一份对象的信息,然后各自修改。最后再比较一下谁修改得最快,谁就可以用对象。这样可以减少等待的时间,提高效率。 重量级锁状态:这是最原始的一种方式,就是当有多个线程想用同一个对象时,只能让其中一个线程用,其他的线程都要等待。这样可以保证对象的安全性,但是会降低效率。 在Java中,每个对象都有一个特殊的区域叫做mark word(标记字),它可以记录对象的一些信息。其中有两位或三位是用来表示对象现在处于哪种状态的。具体来说: 如果两位是“01”,表示无锁状态或偏向锁状态。如果第三位是“0”,表示无锁状态; 如果第三位是“1”,表示偏向锁状态。如果两位是“00”,表示轻量级锁状态。 如果两位是“10”,表示重量级锁状态。 但是,在Java 15中 ,偏向锁被取消了 ,所以现在只有三种状态:无锁状态、轻量级锁状态和重量级锁状态。 回到最开始的问题:为什么JDK 15要废弃偏向锁? 取消偏向锁状态的原因是,偏向锁状态在现代的应用场景下已经不再有明显的性能优势,反而会增加虚拟机的复杂度和维护成本。 具体的原因是: 1. 偏向锁状态是一种优化技术,用于减少无竞争情况下的同步开销。 它假设一个对象只会被一个线程使用,所以当第一个线程使用对象时,就会把自己的标记放在对象上,表示这个对象属于自己。 这样,下次这个线程再使用对象时,就不用检查了,直接就可以用。 但是如果有别的线程也想用这个对象,就要先把标记去掉,然后再竞争。 2. 偏向锁状态在过去能够带来很大的性能提升,是因为有些应用会使用很多不必要的同步操作。 比如早期的Java集合类(如Hashtable和Vector),它们每次访问都要同步。 现在的应用一般会使用非同步的集合类(如HashMap和ArrayList)或者更高效的并发数据结构(如ConcurrentHashMap、CopyOnWriteArrayList等)来处理单线程或多线程的情况。 这意味着如果更新代码使用这些新的类,就可以获得更好的性能,而不需要依赖偏向锁状态。 3. 偏向锁状态也有一个缺点,就是当有竞争发生时,需要进行一次代价很高的撤销操作。 所以只有当应用有大量的无竞争的同步操作时,偏向锁状态才有优势。 但是随着硬件和软件的发展,原子操作的代价已经降低了很多,而且现在的应用往往使用线程池和工作队列来处理并发任务,这些场景下偏向锁状态反而会降低性能。 4. 偏向锁状态还给虚拟机带来了很多复杂度和维护难度,只有少数几个最有经验的工程师能够理解和修改它。它也影响了一些新特性的设计和实现。 综上所述,偏向锁状态已经不再适合现代的Java应用,所以在Java 15 之后被废弃。 ...

    2023-10-25 219
  • Java面试题:不使用锁如何实现线程安全的单例?

    面试官问: 不使用锁,如何实现线程安全的单例? 如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。 饿汉: 其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。 // 单例模式 // 饿汉式(静态变量) class Singleton { // 1. 构造器私有化 private Singleton() {} // 2. 本类内部创建对象实例 private final static Singleton instance = new Singleton(); // 静态变量 // 3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } } 但是,如果从始至终未使用过这个实例,会造成内存浪费 静态内部类: 这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。 // 静态内部类实现 class Singleton { // 构造器私有化 private Singleton() {} // 写一个静态内部类,含一个静态属性Singleton private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } // 提供一个静态的公有方法,直接返回SingleInstance.INSTANCE public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 原理和饿汉一样。这种方式是Singleton类被装载了,INSTANCE 对象不一定被初始化。 因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance 枚举: 借助了 JDK1.5 中添加的枚举来实现单例模式,不仅避免了多线程同步问题,而且还防止反序列化重新创建新的对象 // 枚举实现单例 enum Singleton { INSTANCE; // 属性 } 其实,如果把枚举类进行反编译,你会发现他也是使用了static final来修饰每一个枚举项。 final class Singleton extends Enum { public static final Singleton INSTANCE; private static final Singleton $VALUES[]; public static Singleton[] values() { return (Singleton[])$VALUES.clone(); } public static Singleton valueOf(String name) { return (Singleton)Enum.valueOf(cn/itsource/logweb/utils/Singleton, name); } private Singleton(String s, int i) { super(s, i); } static { INSTANCE = new Singleton("INSTANCE", 0); $VALUES = (new Singleton[] { INSTANCE }); } } 其实,上面三种方式,都是依赖静态数据在类初始化的过程中被实例化这一机制的。 但是,如果真要较真的话,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。 也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。 那么,除了上面这三种,还有一种无锁的实现方式,那就是CAS。 public class Singleton { // 使用了 AtomicReference 封装单例对象 private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { while (true) { // 使用 AtomicReference.get 获取 Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } // 使用 CAS 乐观锁进行非阻塞更新 singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } } 用CAS的好处在于不需要使用传统的锁机制来保证线程安全。  但是我们的实现方式中,用了一个while循环一直在进行重试,所以,这种方式有一个比较大的缺点在于,如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。 ...

    2023-10-25 198

联系我们

在线咨询:点击这里给我发消息

QQ交流群:KirinBlog

工作日:8:00-23:00,节假日休息

扫码关注