Java并发编程常见的问题总结

本篇文章会对并发编程中常遇到的Java技术点进行全面深入的总结,帮助我们在使用中更加得心应手,不怎么用的同学也可以借此机会梳理一下自己的知识体系,进行查漏补缺(阅读本文需要有一定的Java基础)。本文的问题列表来自于B站:给同学们带来全新的Java300集课程啦!java零基础小白自学Java必备优质教程_手把手图解学习Java,让学习成为一种享受_哔哩哔哩_bilibili尚学堂给同学们带来全新的Java300集课程啦本课程为Java300集2022版第一季,配合最新版的Java课程,所有视频重新录制,课件所有图形做了重新绘制和配色,图解学习Java,让学习成为一种享受本套教程专门为零基础学员而制,适合准备入行Java开发的零基础学员,视频中穿插多个实战项目。每一个知识点都讲解的通俗易懂,由浅入深。不仅适用于零基础的初学者,有经验的程序员也可做巩固学习。后续课, 视频播放量 374816、弹幕量 3686、点赞数 14713、投硬币枚数 9647、收藏人数 22028、转发人数 3759, 视频作者 尚学堂官方, 作者简介 需要课件、源码、安装包等资料,可以直接私信留言哦,相关视频:JAVA全套教程160节(小白变大神必学),千锋教育java教程专为小白零基础入门java基础倾心打造 一听就会java技术开发(从java安装到java项目实战),Java学到什么水平能够出去找工作!!,成功上岸进入华为,把之前花了4w买的Java课程,整整480集 拿出来分享给大家!拿走不谢!手把手教学,学完即可就业!冲呀!,Java入门基础视频教程,java零基础自学首选黑马程序员Java入门教程(含Java项目和Java真题),学Java还是C++,这事很简单!,java基础到高级_零基础自学Java--尚硅谷--宋红康,【英语语法速成】英语零基础语法入门班,系列自学课程之初高中英语语法,【Java毕设项目合集】20款Java毕设项目合集(附源码课件)手把手教你学,助你快速毕业。,java下载与安装(最新最全2022)https://www.bilibili.com/video/BV1qL411u7eE

1、synchronized修饰普通方法和静态方法的区别?什么是可见性?

对象锁是用于对象实例方法,或者一个对象实例上的, 类锁 是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class对象。类锁和对象锁之间也是互不干扰的。

对象锁:

/**
* 对象锁 synchronized修饰普通方法 锁的是当前类实例对象
*/
public synchronized void syncMethod1() {
    //do working...
}
复制代码

private final Object o = new Object();

/**
* 对象锁 锁的是Object对象实例
*/
public void syncMethod2() {
    synchronized (o) {
        //do working...
    }
}
复制代码

类锁:

/**
* 类锁 synchronized修饰静态方法 锁的是当前类唯一的.class对象,也就是SynchronizedType.class,等同于下面的syncMethod3()
*/
public static synchronized void syncMethod3() {
    //do working...
}
复制代码

/**
* 类锁 锁的是当前类唯一的.class对象。
*/
public void syncMethod4() {
    synchronized (SynchronizedType.class) {
        //do working...
    }
}
复制代码

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

由于线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量,那么对于共享变量V,它们首先是在自己的工作内存,之后再同步到主内存。可是并不会及时的刷到主存中,而是会有一定时间差。很明显,这个时候线程A对变量V的操作对于线程B而言就不具备可见性了 。

要解决共享对象可见性这个问题,我们可以使用 volatile 关键字或者是加锁。

 

3、CAS无锁编程的原理。

使用当前的处理器基本都支持CAS()的指令,只不过每个厂家所实现的算法并不一样,每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。

CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。循环CAS就是在一个循环里不断的做CAS操作,直到成功为止。

还可以说说CAS的三大问题。

4、ReentrantLock的实现原理。

线程可以重复进入任何一个它已经拥有的锁所同步着的代码块, synchronized 、 ReentrantLock 都是可重入的锁。在实现上,就是线程每次获取锁时判定如果获得锁的线程是它自己时,简单将计数器累积即可,每 释放一次锁,进行计数器累减,直到计算器归零,表示线程已经彻底释放锁。

底层则是利用了JUC中的AQS来实现的。

5、AQS原理。

是用来构建锁或者其他同步组件的基础框架,比如 ReentrantLock 、 ReentrantReadWriteLock 和 CountDownLatch 就是基于AQS实现的。它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。它是CLH队列锁的一种变体实现。它可以实现2种同步方式:独占式,共享式。

AQS的主要使用方式是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态,同步器的设计基于模板方法模式,所以如果要实现我们自己的同步工具类就需要覆盖其中几个可重写的方法,如 tryAcquire 、 tryReleaseShared 等等。

这样设计的目的是同步组件(比如锁)是面向使用者的,它定义了使用者与同步组件交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。这样就很好地隔离了使用者和实现者所需关注的领域。

在内部,AQS维护一个共享资源state,通过内置的FIFO来完成获取资源线程的排队工作。该队列由一个一个的Node结点组成,每个Node结点维护一个prev引用和next引用,分别指向自己的前驱和后继结点,构成一个双端双向链表。

6、synchronized的原理以及与ReentrantLock的区别。

synchronized (this) 原理:涉及两条指令: monitorenter , monitorexit ;再说同步方法,从同步方法反编译的结果来看,方法的同步并没有通过指令 monitorenter 和 monitorexit 来实现,相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED 标示符。

JVM就是根据该标示符来实现方法的同步的:当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

7、synchronized锁了那些优化?

引入如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁、逃逸分析等技术来减少锁操作的开销。

逃逸分析

如果证明一个对象不会逃逸方法外或者线程外,则可针对此变量进行优化:

同步消除 synchronization Elimination ,如果一个对象不会逃逸出线程,则对此变量的同步措施可消除。

锁消除和粗化

锁消除:虚拟机的运行时编译器在运行时如果检测到一些要求同步的代码上不可能发生共享数据竞争,则会去掉这些锁。

锁粗化:将临近的代码块用同一个锁合并起来。消除无意义的锁获取和释放,可以提高程序运行性能。

8、synchronized static与非static锁的区别和范围。

对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class对象。类锁和对象锁之间也是互不干扰的。

 

9、volatile 能否保证线程安全?在DCL上的作用是什么?

不能保证,在DCL的作用是: volatile 是会保证被修饰的变量的可见性和 有序性,保证了单例模式下,保证在创建对象的时候的执行顺序一定是

1.分配内存空间

2.实例化对象instance

3.把instance引用指向已分配的内存空间,此时instance有了内存地址,不再为null了的步骤, 从而保证了instance要么为null 要么是已经完全初始化好的对象。

10、volatile和synchronize有什么区别?

volatile 是最轻量的同步机制。

volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。但是 volatile 不能保证操作的原子性,因此多线程下的写复合操作会导致线程安全问题。

关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

11、什么是守护线程?你是如何退出一个线程的?

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用 Thread.setDaemon(true) 将线程设置为Daemon线程。我们一般用不上,比如垃圾回收线程就是Daemon线程。

线程的中止:

要么是 run() 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

暂停、恢复和停止操作对应在线程 Thread 的API就是 suspend() 、 resume() 和 stop() 。但是这些API是过期的,也就是不建议使用的。因为会导致程序可能工作在不确定状态下。

安全的中止则是其他线程通过调用某个线程A的 interrupt() 方法对其进行中断操作,被中断的线程则是通过线程通过方法 isInterrupted() 来进行判断是否被中断,也可以调用静态方法 Thread.interrupted() 来进行判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为false。

12、sleep 、wait、yield 的区别,wait 的线程如何唤醒它?

yield() 方法:使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源。所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。

yield() 、 sleep() 被调用后,都不会释放当前线程所持有的锁。

调用 wait() 方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行 wait() 方法后面的代码。

wait通常被用于线程间交互, sleep() 通常被用于暂停执行, yield() 方法使当前线程让出CPU占有权。wait的线程使用 notify/notifyAll() 进行唤醒。

13、sleep是可中断的么?

sleep 本身就支持中断,如果线程在 sleep 期间被中断,则会抛出一个中断异常。

14、线程生命周期。

Java中线程的状态分为6种:

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

3. 阻塞(BLOCKED):表示线程阻塞于锁。

4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

  1. 终止(TERMINATED):表示该线程已经执行完毕。

 

15、ThreadLocal是什么?

ThreadLocal 是Java里一种特殊的变量。 ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。

在内部实现上,每个线程内部都有一个 ThreadLocalMap ,用来保存每个线程所拥有的变量副本。

 

16、线程池基本原理。

在开发过程中,合理地使用线程池能够带来3个好处。

第一:降低资源消耗。

第二:提高响应速度。

第三:提高线程的可管理性。

 

  • 如果当前运行的线程少于 corePoolSize ,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
  • 如果运行的线程等于或多于 corePoolSize ,则将任务加入 BlockingQueue 。
  • 如果无法将任务加入 BlockingQueue (队列已满),则创建新的线程来处理任务。
  • 如果创建新线程将使当前运行的线程超出 maximumPoolSize ,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution() 方法。

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值