zoukankan      html  css  js  c++  java
  • 05.java多线程问题

    目录介绍

    • 5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程,会降低程序的性能,那么该如何做才能降低性能?
    • 5.0.0.3 线程中start和run方法有什么区别?wait和sleep方法的不同?sleep() 、join()、yield()有什么区别?
    • 5.0.0.4 用Java手写一个会导致死锁的程序,遇到这种问题解决方案是什么?那些场景用到了死锁机制?
    • 5.0.0.5 ThreadLocal(线程变量副本)这个类的作用是什么?
    • 5.0.0.6 什么是线程安全?线程安全有那几个级别?保障线程安全有哪些手段?ReentrantLock和synchronized的区别?
    • 5.0.0.7 Volatile和Synchronized各自用途是什么?有哪些不同点?Synchronize在编译时如何实现锁机制?
    • 5.0.0.8 wait()和sleep()的区别?各自有哪些使用场景?怎么唤醒一个阻塞的线程?Thread.sleep(0)的作用是啥?
    • 5.0.0.9 同步和非同步、阻塞和非阻塞的概念?分别有哪些使用场景?
    • 5.0.1.0 线程的有哪些状态?请绘制该状态的流程图?讲一下线程的执行生命周期流程?线程如果出现了运行时异常会怎么样?
    • 5.0.1.1 synchronized锁什么?synchronized同步代码块还有同步方法本质上锁住的是谁?为什么?
    • 5.0.1.2 Volatile实现原理?一个int变量,用volatile修饰,多线程去操作++,线程安全吗?那如何才能保证i++线程安全?
    • 5.0.1.3 CAS原理是什么?CAS实现原子操作会出现什么问题?
    • 5.0.1.4 假如有n个网络线程,需要当n个网络线程完成之后,再去做数据处理,你会怎么解决?
    • 5.0.1.5 Runnable接口和Callable接口的区别?
    • 5.0.1.6 如果提交任务时,线程池队列已满,这时会发生什么?线程调度算法是什么?
    • 5.0.1.7 什么是乐观锁和悲观锁?
    • 5.0.1.8 线程类的构造方法、静态块是被哪个线程调用的?同步方法和同步块,哪个是更好的选择?同步的范围越少越好吗?
    • 5.0.1.9 synchonized(this)和synchonized(object)区别?Synchronize作用于方法和静态方法区别?

    好消息

    • 博客笔记大汇总【15年10月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计500篇[近100万字],将会陆续发表到网上,转载请注明出处,谢谢!
    • 链接地址:https://github.com/yangchong211/YCBlogs
    • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有博客将陆续开源到GitHub!

    5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程,会降低程序的性能,那么该如何做才能降低性能?

    • 线程池好处:
      • 1)降低资源消耗;
      • 2)提高相应速度;
      • 3)提高线程的可管理性。技术博客大总结
    • 线程池的实现原理:
      • 当提交一个新任务到线程池时,判断核心线程池里的线程是否都在执行。如果不是,则创建一个新的线程执行任务。如果核心线程池的线程都在执行任务,则进入下个流程。
      • 判断工作队列是否已满。如果未满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
      • 判断线程池是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了,则交给饱和策略来处理这个任务。

    5.0.0.3 线程中start和run方法有什么区别?wait和sleep方法的不同?sleep() 、join()、yield()有什么区别?

    • 线程中start和run方法有什么区别
      • 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?这是一个非常经典的java多线程面试问题。当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。
    • wait和sleep方法的不同
      • 最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
    • 1、sleep()方法
      • 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常
      • 比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
      • 总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
    • 2、yield()方法技术博客大总结
      • yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。
    • 3、join()方法
      • Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。
      • Thread t = new MyThread(); t.start(); t.join();保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
    • Thread的join()有什么作用?
      • Thread的join()的含义是等待该线程终止,即将挂起调用线程的执行,直到被调用的对象完成它的执行。比如存在两个线程t1和t2,下述代码表示先启动t1,直到t1的任务结束,才轮到t2启动。
      t1.start();
      t1.join(); 
      t2.start();
      

    5.0.0.4 用Java手写一个会导致死锁的程序,遇到这种问题解决方案是什么?那些场景用到了死锁机制?

    • 死锁是怎么一回事
      • 线程A和线程B相互等待对方持有的锁导致程序无限死循环下去。
    • 深入理解死锁的原理
      • 两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock作为同步代码块的锁;
      • 线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不需要太多,50毫秒差不多了,然后接着获取lock2的对象锁。这么做主要是为了防止线程1启动一下子就连续获得了lock1和lock2两个对象的对象锁
      • 线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的

    5.0.0.5 ThreadLocal(线程变量副本)这个类的作用是什么?

    • ThreadLocal即线程变量
      • ThreadLocal为每个线程维护一个本地变量。
      • 采用空间换时间,它用于线程间的数据隔离,它为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。ThreadLocal的实现是以ThreadLocal对象为键。任意对象为值得存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
    • ThreadLocal类是一个Map
      • ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。
      • ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。
      • Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
    • 更多详细参考博客:深入研究java.lang.ThreadLocal类

    5.0.0.6 什么是线程安全?线程安全有那几个级别?保障线程安全有哪些手段?ReentrantLock和synchronized的区别?

    • 什么是线程安全
      • 线程安全就是当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
    • 线程安全也是有几个级别
      • 技术博客大总结
      • 不可变:
        • 像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
      • 绝对线程安全
        • 不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet
      • 相对线程安全
        • 相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
      • 线程非安全技术博客大总结
        • ArrayList、LinkedList、HashMap等都是线程非安全的类.
    • 保障线程安全有哪些手段。保证线程安全可从多线程三特性出发:
      • 原子性(Atomicity):单个或多个操作是要么全部执行,要么都不执行
        • Lock:保证同时只有一个线程能拿到锁,并执行申请锁和释放锁的代码
        • synchronized:对线程加独占锁,被它修饰的类/方法/变量只允许一个线程访问
      • 可见性(Visibility):当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
        • volatile:保证新值能立即同步到主内存,且每次使用前立即从主内存刷新;
        • synchronized:在释放锁之前会将工作内存新值更新到主存中
      • 有序性(Ordering):程序代码按照指令顺序执行
        • volatile: 本身就包含了禁止指令重排序的语义
        • synchronized:保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入
    • ReentrantLock和synchronized的区别
      • ReentrantLock与synchronized的不同在于ReentrantLock:
        • 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
        • 公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。而synchronized是非公平的,即在锁被释放时,任何一个等待锁的线程都有机会获得锁。ReentrantLock默认情况下也是非公平的,但可以通过带布尔值的构造函数改用公平锁。
        • 锁绑定多个条件:一个ReentrantLock对象可以通过多次调用newCondition()同时绑定多个Condition对象。而在synchronized中,锁对象wait()和notify()或notifyAl()只能实现一个隐含的条件,若要和多于一个的条件关联不得不额外地添加一个锁。
      • Synchronized是悲观锁机制,独占锁。而Locks.ReentrantLock是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
        • ReentrantLock适用场景
        • 某个线程在等待一个锁的控制权的这段时间需要中断
        • 需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程,锁可以绑定多个条件。
        • 具有公平锁功能,每个到来的线程都将排队等候。
      • 更多详细参考博客:Lock与synchronized 的区别

    5.0.0.7 Volatile和Synchronized各自用途是什么?有哪些不同点?Synchronize在编译时如何实现锁机制?

    • Volatile和Synchronized各自用途是什么?有哪些不同点?
      • 1 粒度不同,前者针对变量 ,后者锁对象和类
      • 2 syn阻塞,volatile线程不阻塞
      • 3 syn保证三大特性,volatile不保证原子性
      • 4 syn编译器优化,volatile不优化 volatile具备两种特性:
        • 1.保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。
        • 2.禁止指令重排序优化。
      • Volatile如何保证内存可见性:
        • 1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
        • 2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
      • 同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。
      • 异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来。(异步的特点就是通知)。 打电话和发短信来比喻同步和异步操作。
      • 阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作。
      • 非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作,等这个慢的完成后,CPU才会接着完成后续的操作。
      • 非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑。
    • Synchronize在编译时如何实现锁机制?
      • Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。

    5.0.0.8 wait()和sleep()的区别?各自有哪些使用场景?怎么唤醒一个阻塞的线程?Thread.sleep(0)的作用是啥?

    • sleep来自Thread类,和wait来自Object类
      • 调用sleep()方法的过程中,线程不会释放对象锁。而调用wait方法线程会释放对象锁
      • sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
      • sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒
    • 通俗解释
      • Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
    • 怎么唤醒一个阻塞的线程?
      • 如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
      • 技术博客大总结
    • Thread.sleep(0)的作用是啥?
      • 由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

    5.0.0.9 同步和非同步、阻塞和非阻塞的概念?分别有哪些使用场景?

    • 同步和非同步
      • 同步和异步体现的是消息的通知机制:所谓同步,方法A调用方法B后必须等到方法B返回结果才能继续后面的操作;所谓异步,方法A调用方法B后可让方法B在调用结束后通过回调等方式通知方法A
    • 阻塞和非阻塞
      • 阻塞和非阻塞侧重于等待消息时的状态:所谓阻塞,就是在结果返回之前让当前线程挂起;所谓非阻塞,就是在等待时可做其他事情,通过轮询去询问是否已返回结果

    5.0.1.0 线程的有哪些状态?请绘制该状态的流程图?讲一下线程的执行生命周期流程?线程如果出现了运行时异常会怎么样?

    • 在任意一个时间点,一个线程只能有且只有其中的一种状态
      • 新建(New):线程创建后尚未启动
      • 技术博客大总结
      • 运行(Runable):包括正在执行(Running)和等待着CPU为它分配执行时间(Ready)两种 无限期等待(Waiting):该线程不会被分配CPU执行时间,要等待被其他线程显式地唤醒。以下方法会让线程陷入无限期等待状态:
      没有设置Timeout参数的Object.wait()
      没有设置Timeout参数的Thread.join()
      LockSupport.park()
      
      • 限期等待(Timed Waiting):该线程不会被分配CPU执行时间,但在一定时间后会被系统自动唤醒。以下方法会让线程进入限期等待状态:
      Thread.sleep()
      设置了Timeout参数的Object.wai()
      设置了Timeout参数的Thread.join()
      LockSupport.parkNanos()
      LockSupport.parkUntil()
      
      • 阻塞(Blocked):线程被阻塞。和等待状态不同的是,阻塞状态表示在等待获取到一个排他锁,在另外一个线程放弃这个锁的时候发生;而等待状态表示在等待一段时间或者唤醒动作的发生,在程序等待进入同步区域的时候发生。
      • 结束(Terminated):线程已经结束执行
    • 绘制该状态的流程图
    • 线程如果出现了运行时异常会怎么样?
      • 如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

    5.0.1.1 synchronized锁什么?synchronized同步代码块还有同步方法本质上锁住的是谁?为什么?

    • synchronized锁什么
      • 对于普通同步方法,锁是当前实例对象;
      • 对于静态同步方法,锁是当前类的Class对象;
      • 对于同步方法块,锁是括号中配置的对象;
      • 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。synchronized用的锁是存在Java对象头里的MarkWord,通常是32bit或者64bit,其中最后2bit表示锁标志位。
    • 本质上锁住的是对象。
      • 在java虚拟机中,每个对象和类在逻辑上都和一个监视器相关联,synchronized本质上是对一个对象监视器的获取。当执行同步代码块或同步方法时,执行方法的线程必须先获得该对象的监视器,才能进入同步代码块或同步方法;而没有获取到的线程将会进入阻塞队列,直到成功获取对象监视器的线程执行结束并释放锁后,才会唤醒阻塞队列的线程,使其重新尝试对对象监视器的获取。

    5.0.1.2 Volatile实现原理?一个int变量,用volatile修饰,多线程去操作++,线程安全吗?那如何才能保证i++线程安全?

    • volatile的作用和原理
      • Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行。
      • volatile是轻量级的synchronized(volatile不会引起线程上下文的切换和调度),它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
      • 由于内存访问速度远不及CPU处理速度,为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后在进行操作,但操作完不知道何时会写到内存。普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。如果对声明了volatile的变量进行写操作,JVM就会想处理器发送一条Lock前缀的指令,表示将当前处理器缓存行的数据写回到系统内存。
    • 一个int变量,用volatile修饰,多线程去操作++,线程安全吗
      • 技术博客大总结
      • 不安全
      • 案例代码,至于打印结果就不展示呢
      • volatile只能保证可见性,并不能保证原子性。
      • i++实际上会被分成多步完成:
        • 1)获取i的值;
        • 2)执行i+1;
        • 3)将结果赋值给i。
      • volatile只能保证这3步不被重排序,多线程情况下,可能两个线程同时获取i,执行i+1,然后都赋值结果2,实际上应该进行两次+1操作。
      private volatile int a = 0;
      for (int x=0 ; x<=100 ; x++){
          new Thread(new Runnable() {
              @Override
              public void run() {
                  a++;
                  Log.e("小杨逗比Thread-------------",""+a);
              }
          }).start();
      }
      
    • 如何才能保证i++线程安全
      • 可以使用java.util.concurrent.atomic包下的原子类,如AtomicInteger。其实现原理是采用CAS自旋操作更新值。
      for (int x=0 ; x<=100 ; x++){
          new Thread(new Runnable() {
              @Override
              public void run() {
                  AtomicInteger atomicInteger = new AtomicInteger(a++);
                  int i = atomicInteger.get();
                  Log.e("小杨逗比Thread-------------",""+i);
              }
          }).start();
      }
      

    5.0.1.3 CAS原理是什么?CAS实现原子操作会出现什么问题?

    • CAS原理是什么
      • CAS即compare and swap的缩写,中文翻译成比较并交换。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。自旋就是不断尝试CAS操作直到成功为止。
    • CAS实现原子操作会出现什么问题
      • ABA问题。因为CAS需要在操作之的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成,有变成A,那么使用CAS进行检查时会发现它的值没有发生变化,但实际上发生了变化。ABA问题可以通过添加版本号来解决。Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
      • 循环时间长开销大。pause指令优化。
      • 只能保证一个共享变量的原子操作。可以合并成一个对象进行CAS操作。

    5.0.1.4 假如有n个网络线程,需要当n个网络线程完成之后,再去做数据处理,你会怎么解决?

    • 多线程同步的问题。这种情况可以可以使用thread.join();join方法会阻塞直到thread线程终止才返回。更复杂一点的情况也可以使用CountDownLatch,CountDownLatch的构造接收一个int参数作为计数器,每次调用countDown方法计数器减一。做数据处理的线程调用await方法阻塞直到计数器为0时。

    5.0.1.5 Runnable接口和Callable接口的区别?

    • Runnable接口和Callable接口的区别
      • Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
      • 这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。

    5.0.1.6 如果提交任务时,线程池队列已满,这时会发生什么?线程调度算法是什么?

    • 如果提交任务时,线程池队列已满,这时会发生什么?
      • 如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务
      • 技术博客大总结
      • 如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy
    • 线程调度算法是什么?
      • 抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

    5.0.1.7 什么是乐观锁和悲观锁?

    • 什么是乐观锁和悲观锁?
      • 乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
      • 悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,直接上了锁就操作资源。

    5.0.1.8 线程类的构造方法、静态块是被哪个线程调用的?同步方法和同步块,哪个是更好的选择?同步的范围越少越好吗?

    • 线程类的构造方法、静态块是被哪个线程调用的?
      • 线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
    • 举个例子
      • 假设Thread2中new了Thread1,main函数中new了Thread2,那么:
        • Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
        • Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的
    • 同步方法和同步块,哪个是更好的选择?
      • 同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
      • 技术博客大总结
    • 同步的范围越少越好吗?
      • 是的。虽说同步的范围越少越好,但是在Java虚拟机中还是存在着一种叫做锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味着要进行反复的加锁->解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁-->解锁的次数,有效地提升了代码执行的效率。

    5.0.1.9 synchonized(this)和synchonized(object)区别?Synchronize作用于方法和静态方法区别?

    • synchonized(this)和synchonized(object)区别技术博客大总结
      • 其实并没有很大的区别,synchonized(object)本身就包含synchonized(this)这种情况,使用的场景都是对一个代码块进行加锁,效率比直接在方法名上加synchonized高一些(下面分析),唯一的区别就是对象的不同。
      • 对synchronized(this)的一些理解
        • 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
        • 二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
        • 三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
        • 四、当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
    • Synchronize作用于方法和静态方法区别
      • 测试代码如下所示
      private void test() {
          final TestSynchronized test1 = new TestSynchronized();
          final TestSynchronized test2 = new TestSynchronized();
          Thread t1 = new Thread(new Runnable() {
      
              @Override
              public void run() {
                  test1.method01("a");
                  //test1.method02("a");
              }
          });
          Thread t2 = new Thread(new Runnable() {
      
              @Override
              public void run() {
                  test2.method01("b");
                  //test2.method02("a");
              }
          });
          t1.start();
          t2.start();
      }
      
      private static class TestSynchronized{
          private int num1;
          public synchronized void method01(String arg) {
              try {
                  if("a".equals(arg)){
                      num1 = 100;
                      System.out.println("tag a set number over");
                      Thread.sleep(1000);
                  }else{
                      num1 = 200;
                      System.out.println("tag b set number over");
                  }
                  System.out.println("tag = "+ arg + ";num ="+ num1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
          private static int  num2;
          public static synchronized void method02(String arg) {
              try {
                  if("a".equals(arg)){
                      num2 = 100;
                      System.out.println("tag a set number over");
                      Thread.sleep(1000);
                  }else{
                      num2 = 200;
                      System.out.println("tag b set number over");
                  }
                  System.out.println("tag = "+ arg + ";num ="+ num2);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
      
      //调用method01方法打印日志【普通方法】
      tag a set number over
      tag b set number over
      tag = b;num =200
      tag = a;num =100
      
      
      //调用method02方法打印日志【static静态方法】
      tag a set number over
      tag = a;num =100
      tag b set number over
      tag = b;num =200
      
      • 在static方法前加synchronized:静态方法属于类方法,它属于这个类,获取到的锁,是属于类的锁。
      • 在普通方法前加synchronized:非static方法获取到的锁,是属于当前对象的锁。 技术博客大总结
      • 结论:类锁和对象锁不同,synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。

    其他介绍

    01.关于博客汇总链接

    02.关于我的博客

  • 相关阅读:
    深入理解计算机系统 第六章 存储器层次结构 第二遍
    深入理解计算机系统 第六章 存储器层次结构
    深入理解计算机系统 第八章 异常控制流 Part2 第二遍
    深入理解计算机系统 第八章 异常控制流 part2
    深入理解计算机系统 第八章 异常控制流 Part1 第二遍
    深入理解计算机系统 第八章 异常控制流 part1
    深入理解计算机系统 第三章 程序的机器级表示 Part2 第二遍
    深入理解计算机系统 第三章 程序的机器级表示 part2
    深入理解计算机系统 第三章 程序的机器级表示 Part1 第二遍
    深入理解计算机系统 第三章 程序的机器级表示 part1
  • 原文地址:https://www.cnblogs.com/yc211/p/10170503.html
Copyright © 2011-2022 走看看