Java 基础 进程与线程

1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏【多线程访问和修改】。
  
 2、线程同步方法是通过锁(监视者Mintor)来实现,每个对象都有且仅有一个锁,这个
 锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访
 问该对象的同步方法(可以访问静态同步方法)。
  
  3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态
  方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法
  时,会获取这两个对象锁。
 
 4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
 
 5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的
 判断,对需要具有原子操作的步骤做出分析,并保证原子操作期间别的线程无法访问竞争
 资源(加锁处理)。
        StringBuffer线程安全,StringBuilder线程不安全
 
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
 
 7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁
 程序,不一定好使。但是,一旦程序发生死锁,程序将死掉。
 
public class 线程同步synchronized小结 {
   
                 常见问题
     
     1、为什么调用 Object 的 wait/notify/notifyAll 方法,需要加 synchronized 锁
     
     因为这3个方法都会操作锁对象,所以需要先获取锁对象,而加 synchronized 锁可以让
     我们获取到锁对象


     
     2、synchronize 底层维护了几个列表存放被阻塞的线程
     synchronized 底层对应的 JVM 模型为objectMonitor,使用了3个双向链表来存放被阻塞
    的线程:`_cxq(Contention queue)`、`_EntryList(EntryList)`、`_WaitSet(WaitSet)`。
     当线程获取锁失败进入阻塞后,首先会被加入到_cxq链表,_cxq链表的节点会在某个
     时刻被进一步转移到_EntryList链表。
     当持有锁的线程释放锁后,_EntryList链表头结点的线程会被唤醒,该线程称为 
     successor(假定继承者),然后该线程会尝试抢占锁。
     当我们调用wait() 时,线程会被放入_WaitSet,直到调用了notify()/notifyAll()后,线程才
     被重新放入_cxq或_EntryList,默认放入_cxq链表头部。
     
     3、为什么释放锁时被唤醒的线程会称为“假定继承者”?被唤醒的线程一定能获取到锁吗?
     因为被唤醒的线程并不是就一定获取到锁了,该线程仍然需要去竞争锁,而且可能会失败,所
     以该线程并不是就一定会成为锁的“继承者”,而只是有机会成为,所以我们称它为假定的。 
     这也是 synchronized 为什么是非公平锁的一个原因。
     
     4、synchronized 是公平锁还是非公平锁?
     非公平锁。
     
     5、synchronized 为什么是非公平锁?非公平体现在哪些地方?
     synchronized 的非公平其实在源码中应该有不少地方,因为设计者就没按公平锁来设计,
     核心有以下几个点: 
     1)当持有锁的线程释放锁时,该线程会执行以下两个重要操作:
             先将锁的持有者 owner 属性赋值为 null
             唤醒等待链表中的一个线程(假定继承者) 
         在1和2之间,如果有其他线程刚好在尝试获取锁(例如自旋),则可以马上获取到锁。 > >
     2)当线程尝试获取锁失败,进入阻塞时,放入链表的顺序,和最终被唤醒的顺序是不一致的
     ,也就是说你先进入链表,不代表你就会先被唤醒。
     
     6、如果有多个线程都进入wait状态,那某个线程调用notify唤醒线程时是否按照进入wait的
     顺序去唤醒?
     
     答案是否定的。 
     调用 wait 时,节点进入`_WaitSet`链表的尾部。调用 notify 时,根据不同的策略,节点
     可能被移动到 cxq头部、cxq 尾部、EntryList 头部、EntryList 尾部等多种情况。所以,
     唤醒的顺序并不一定是进入 wait 时的顺序。
     
     7、notifyAll 是怎么实现全唤起的?
     nofity 是获取 WaitSet 的头结点,执行唤起操作。
     nofityAll 的流程,可以简单的理解为就是循环遍历WaitSet的所有节点,对每个节点执行
     notify 操作。
 
     8、JVM 做了哪些锁优化?
     偏向锁、轻量级锁、自旋锁、自适应自旋、锁消除、锁粗化。
     
     9、为什么要引入偏向锁和轻量级锁?为什么重量级锁开销大?
      
     > 重量级锁底层依赖于系统的同步函数来实现,在 linux 中使用 pthread_mutex_t(互斥锁)来实现。 > >
     这些底层的同步函数操作会涉及到:操作系统用户态和内核态的切换、进程的上下文切换,而这些操作都是比较耗时的,因此重量级锁操作的开销比较大。而在很多情况下,
     可能获取锁时只有一个线程,或者是多个线程交替获取锁,在这种情况下,使用重量级锁就不划算了,因此引入了偏向锁和轻量级锁来降低没有并发竞争时的锁开销。
     
     10、偏向锁有撤销、膨胀,性能损耗这么大为什么要用呢?
     
     > 偏向锁的好处是在只有一个线程获取锁的情况下,只需要通过一次 CAS 操作修改 markword
     ,之后每次进行简单的判断即可,避免了轻量级锁每次获取释放锁时的 CAS 操作。 > > 如果确定同步代码块会被多个线程访问或者竞争较大,可以通过
     -XX:-UseBiasedLocking 参数关闭偏向锁。

临界资源,临界资源的数量一定是小于使用资源的线程数

        生产者消费者模式
 
 wait和notify/notifyAll的用法 
 
         为什么要使用生产者/消费者模式
 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发
 当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者
 处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费
 者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消
 费者模式。
 
          生产者/消费者模型优点
 1、解耦。因为多了一个缓冲区,所以生产者和消费者并不直接相互调用,这一点很容易
 想到,这样生产者和消费者的代码发生变化,都不会对对方产生影响,这样其实就把生产
 者和消费者之间的强耦合解开,变为了生产者和缓冲区/消费者和缓冲区之间的弱耦合
 2、通过平衡生产者和消费者的处理能力来提高整体处理数据的速度,这是生产者/消费者
 模型最重要的一个优点。如果消费者直接从生产者这里拿数据,如果生产者生产的速度很
 慢,但消费者消费的速度很快,那消费者就得占用CPU的时间片白白等在那边。有了生产
 者/消费者模型,生产者和消费者就是两个独立的并发体,生产者把生产出来的数据往缓
 冲区一丢就好了,不必管消费者;消费者也是,从缓冲区去拿数据就好了,也不必管生产
者,缓冲区满了就不生产,缓冲区空了就不消费,使生产者/消费者的处理能力达到一个
 动态的平衡

        生产者/消费者模式的作用
 - 支持并发
 - 解耦
 - 支持忙闲不均
 调用wait/notify之类的方法要求必须在当前线程对象内部,例如synchronized方法中
  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值