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(线程变量副本)这个类的做用是什么?ThreadLocal为什么要设计key存储当前的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.3 CAS是什么?CAS原理是什么?CAS实现原子操做会出现什么问题?对于多个共享变量CAS能够保证原子性吗?
  • 5.0.1.4 假若有n个网络线程,须要当n个网络线程完成以后,再去作数据处理,你会怎么解决这个问题?
  • 5.0.1.5 Runnable接口和Callable接口的区别?Callable中是如何处理线程异常的状况?如何监测runnable异常?
  • 5.0.1.6 若是提交任务时,线程池队列已满,这时会发生什么?线程调度算法是什么?
  • 5.0.1.7 什么是乐观锁和悲观锁?悲观锁机制存在哪些问题?乐观锁是如何实现冲突检测和数据更新?
  • 5.0.1.8 线程类的构造方法、静态块是被哪一个线程调用的?同步方法和同步块,哪一个是更好的选择?同步的范围越少越好吗?
  • 5.0.1.9 synchonized(this)和synchonized(object)区别?Synchronize做用于方法和静态方法区别?
  • 5.0.2.0 volatile是什么?volatile的用途是什么?线程在工做内存进行操做后什么时候会写到主内存中?
  • 5.0.2.1 被volatile修饰变量在多线程下如何获最新值?理解volatile的happens-before关系?多线程下执行volatile读写后的内存状态?
  • 5.0.2.2 Volatile实现原理?一个int变量,用volatile修饰,多线程去操做++,线程安全吗?那如何才能保证i++线程安全?
  • 5.0.2.3 在Java内存模型有哪些能够保证并发过程的原子性、可见性和有序性的措施?

好消息

  • 博客笔记大汇总【15年10月到至今】,包括Java基础及深刻知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,固然也在工做之余收集了大量的面试题,长期更新维护而且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计500篇[近100万字],将会陆续发表到网上,转载请注明出处,谢谢!
  • 连接地址:github.com/yangchong21…
  • 若是以为好,能够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一般被用于暂停执行。
  • 一、sleep()方法
    • 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响。 让其余线程有机会继续执行,但它并不释放对象锁。也就是若是有Synchronized同步块,其余线程仍然不能访问共享数据。注意该方法要捕获异常
    • 好比有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另外一个为MIN_PRIORITY,若是没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
    • 总之,sleep()可使低优先级的线程获得执行的机会,固然也可让同优先级、高优先级的线程有执行的机会。
  • 二、yield()方法技术博客大总结
    • yield()方法和sleep()方法相似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程从新回到可执行状态,因此执行yield()的线程有可能在进入到可执行状态后立刻又被执行,另外yield()方法只能使同优先级或者高优先级的线程获得执行机会,这也和sleep()方法不一样。
  • 三、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的对象锁的
  • 死锁的简单代码
    • 思路是建立两个字符串a和b,再建立两个线程A和B,让每一个线程都用synchronized锁住字符串(A先锁a,再去锁b;B先锁b,再锁a),若是A锁住a,B锁住b,A就没办法锁住b,B也没办法锁住a,这时就陷入了死锁。
    • 打印结果:能够看到,Lock1获取obj1,Lock2获取obj2,可是它们都没有办法再获取另一个obj,由于它们都在等待对方先释放锁,这时就是死锁。
    public class DeadLock {
        public static String obj1 = "obj1";
        public static String obj2 = "obj2";
        public static void main(String[] args){
            Thread a = new Thread(new Lock1());
            Thread b = new Thread(new Lock2());
            a.start();
            b.start();
        }    
    }
    class Lock1 implements Runnable{
        @Override
        public void run(){
            try{
                System.out.println("Lock1 running");
                while(true){
                    synchronized(DeadLock.obj1){
                        System.out.println("Lock1 lock obj1");
                        Thread.sleep(3000);//获取obj1后先等一下子,让Lock2有足够的时间锁住obj2
                        synchronized(DeadLock.obj2){
                            System.out.println("Lock1 lock obj2");
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    class Lock2 implements Runnable{
        @Override
        public void run(){
            try{
                System.out.println("Lock2 running");
                while(true){
                    synchronized(DeadLock.obj2){
                        System.out.println("Lock2 lock obj2");
                        Thread.sleep(3000);
                        synchronized(DeadLock.obj1){
                            System.out.println("Lock2 lock obj1");
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    复制代码
    • 若是咱们只运行Lock1呢?修改一下main函数,把线程b注释掉。
  • 如何预防死锁的产生?
    • 死锁发生时的四个必要条件,只要破坏这四个必要条件中的任意一个条件,死锁就不会发生。这就为咱们解决死锁问题提供了可能。通常地,解决死锁的方法分为死锁的预防,避免,检测[定位死锁的位置]与恢复三种(注意:死锁的检测与恢复是一个方法)。 锁的预防是保证系统不进入死锁状态的一种策略。它的基本思想是要求进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
    • 打破互斥条件。即容许进程同时访问某些资源。可是,有的资源是不容许被同时访问的,像打印机等等,这是由资源自己的属性所决定的。因此,这种办法并没有实用价值。
    • 打破不可抢占条件。即容许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能当即被知足时,它必须释放所占有的所有资源,之后再从新申请。它所释放的资源能够分配给其它进程。这就至关于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会下降系统性能。
    • 打破占有且申请条件。能够实行资源预先分配策略。即进程在运行前一次性地向系统申请它所须要的所有资源。若是某个进程所需的所有资源得不到知足,则不分配任何资源,此进程暂不运行。只有当系统可以知足当前进程的所有资源需求时,才一次性地将所申请的资源所有分配给该进程。因为运行的进程已占有了它所需的所有资源,因此不会发生占有资源又申请资源的现象,所以不会发生死锁。可是,这种策略也有以下缺点:
      • 在许多状况下,一个进程在执行以前不可能知道它所须要的所有资源。这是因为进程在执行时是动态的,不可预测的;
      • 资源利用率低。不管所分资源什么时候用到,一个进程只有在占有所需的所有资源后才能执行。即便有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,形成长期占着不用的情况。这显然是一种极大的资源浪费;
      • 下降了进程的并发性。由于资源有限,又加上存在浪费,能分配到所需所有资源的进程个数就必然少了。
    • 打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会造成环路。全部进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提升,可是也存在如下缺点:
      • 限制了进程对资源的请求,同时给系统中全部资源合理编号也是件困难事,并增长了系统开销;
      • 为了遵循按编号申请的次序,暂不使用的资源也须要提早申请,从而增长了进程对资源的占用时间。

5.0.0.5 ThreadLocal(线程变量副本)这个类的做用是什么?ThreadLocal为什么要设计key存储当前的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.3 CAS原理是什么?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接口的区别?Callable中是如何处理线程异常的状况?如何监测runnable异常?

  • 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)的一些理解
      • 1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
      • 2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
      • 3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
      • 4、当一个线程访问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的方法,锁是加载类上,这个类全部的对象竞争一把锁。

5.0.2.0 volatile是什么?volatile的用途是什么?线程在工做内存进行操做后什么时候会写到主内存中?

  • volatile是什么?
    • 轻量级锁。synchronized是阻塞式同步,在线程竞争激烈的状况下会升级为重量级锁。而volatile就能够说是java虚拟机提供的最轻量级的同步机制。
  • volatile的用途是什么?
    • 被volatile修饰的变量可以保证每一个线程可以获取该变量的最新值,从而避免出现数据脏读的现象。
  • 线程在工做内存进行操做后什么时候会写到主内存中?
    • Java内存模型告诉咱们,各个线程会将共享变量从主内存中拷贝到工做内存,而后执行引擎会基于工做内存中的数据进行操做处理。
    • 这个时机对普通变量是没有规定的,而针对volatile修饰的变量给java虚拟机特殊的约定,线程对volatile变量的修改会马上被其余线程所感知,即不会出现数据脏读的现象,从而保证数据的“可见性”。
  • 被volatile修饰变量生成汇编代码有何特色?
    • 在生成汇编代码时会在volatile修饰的共享变量进行写操做的时候会多出Lock前缀的指令
    • 这个Lock指令确定有神奇的地方,那么Lock前缀的指令在多核处理器下会发现什么事情了?主要有这两个方面的影响:
      • 1.将当前处理器缓存行的数据写回系统内存;
      • 2.这个写回内存的操做会使得其余CPU里缓存了该内存地址的数据无效

5.0.2.1 被volatile修饰变量在多线程下如何获最新值?理解volatile的happens-before关系?多线程下执行volatile读写后的内存状态?

  • 被volatile修饰变量在多线程下如何获取最新值?
    • 若是对声明了volatile的变量进行写操做,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。可是,就算写回到内存,若是其余处理器缓存的值仍是旧的,再执行计算操做就会有问题。因此,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每一个处理器经过嗅探在总线上传播的数据来检查本身缓存的值是否是过时了,当处理器发现本身缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操做的时候,会从新从系统内存中把数据读处处理器缓存里。
    • 所以,通过分析咱们能够得出以下结论:
      • 1.Lock前缀的指令会引发处理器缓存写回内存;
      • 2.一个处理器的缓存回写到内存会致使其余处理器的缓存失效;
      • 3.当处理器发现本地缓存失效后,就会从内存中重读该变量数据,便可以获取当前最新值。
    • 这样针对volatile变量经过这样的机制就使得每一个线程都能得到该变量的最新值。即知足数据的“可见性”。
  • 如何理解volatile的happens-before关系?
  • 先来看两个核心之一:volatile的happens-before关系。
    • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。下面咱们结合具体的代码,咱们利用这条规则推导下:
    private void test3() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                new VolatileExample().writer();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                new VolatileExample().reader();
            }
        });
        thread1.start();
        thread2.start();
    }
    
    
    public class VolatileExample {
        private int a = 0;
        private volatile boolean flag = false;
        public void writer(){
            a = 1;          //1
            LogUtils.e("测试volatile数据1--"+a);
            flag = true;   //2
            LogUtils.e("测试volatile数据2--"+flag);
        }
        public void reader(){
            LogUtils.e("测试volatile数据3--"+flag);
            if(flag){      //3
                int i = a; //4
                LogUtils.e("测试volatile数据4--"+i);
            }
        }
    }
    复制代码
    • 打印日志以下所示
    //第一种状况
    2019-03-07 17:17:30.294 25764-25882/com.ycbjie.other E/TestFirstActivity: │ 测试volatile数据3--false
    2019-03-07 17:17:30.294 25764-25881/com.ycbjie.other E/TestFirstActivity: │ 测试volatile数据1--1
    2019-03-07 17:17:30.295 25764-25881/com.ycbjie.other E/TestFirstActivity: │ 测试volatile数据2--true
    
    //第二种状况
    2019-03-07 17:18:01.965 25764-25901/com.ycbjie.other E/TestFirstActivity: │ 测试volatile数据1--1
    2019-03-07 17:18:01.965 25764-25902/com.ycbjie.other E/TestFirstActivity: │ 测试volatile数据3--false
    2019-03-07 17:18:01.966 25764-25901/com.ycbjie.other E/TestFirstActivity: │ 测试volatile数据2--true
    复制代码
    • 上面的实例代码对应的happens-before关系以下图所示:
      • VolatileExample的happens-before关系推导
    • 分析上面代码执行过程
      • 加锁线程A先执行writer方法,而后线程B执行reader方法图中每个箭头两个节点就代码一个happens-before关系,黑色的表明根据程序顺序规则推导出来,红色的是根据volatile变量的写happens-before 于任意后续对volatile变量的读,而蓝色的就是根据传递性规则推导出来的。
      • 这里的2 happen-before 3,一样根据happens-before规则定义:若是A happens-before B,则A的执行结果对B可见,而且A的执行顺序先于B的执行顺序,咱们能够知道操做2执行结果对操做3来讲是可见的,也就是说当线程A将volatile变量 flag更改成true后线程B就可以迅速感知。
  • 多线程下执行volatile读写后的内存状态?
    • 仍是按照两个核心的分析方式,分析完happens-before关系后咱们如今就来进一步分析volatile的内存语义。仍是以上面的代码为例,假设线程A先执行writer方法,线程B随后执行reader方法,初始时线程的本地内存中flag和a都是初始状态,下图是线程A执行volatile写后的状态图。
      • 线程A执行volatile写后的内存状态图
      • image
    • 当volatile变量写后,线程中本地内存中共享变量就会置为失效的状态,所以线程B再须要读取从主内存中去读取该变量的最新值。下图就展现了线程B读取同一个volatile变量的内存变化示意图。
      • 线程B读volatile后的内存状态图
      • image
    • 结果分析
      • 从横向来看,线程A和线程B之间进行了一次通讯,线程A在写volatile变量时,实际上就像是给B发送了一个消息告诉线程B你如今的值都是旧的了,而后线程B读这个volatile变量时就像是接收了线程A刚刚发送的消息。既然是旧的了,那线程B该怎么办了?天然而然就只能去主内存去取啦。

5.0.2.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.2.3 在Java内存模型有哪些能够保证并发过程的原子性、可见性和有序性的措施?

  • 原子性(Atomicity):一个操做要么都执行要么都不执行。
    • 可直接保证的原子性变量操做有:read、load、assign、use、store和write,所以可认为基本数据类型的访问读写是具有原子性的。
    • 若须要保证更大范围的原子性,可经过更高层次的字节码指令monitorenter和monitorexit来隐式地使用lock和unlock这两个操做,反映到Java代码中就是同步代码块synchronized关键字。
  • 可见性(Visibility):当一个线程修改了共享变量的值,其余线程可以当即得知这个修改。
    • 经过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存做为传递媒介的方式来实现。
    • 提供三个关键字保证可见性:volatile能保证新值能当即同步到主内存,且每次使用前当即从主内存刷新;synchronized对一个变量执行unlock操做以前能够先把此变量同步回主内存中;被final修饰的字段在构造器中一旦初始化完成且构造器没有把this的引用传递出去,就能够在其余线程中就能看见final字段的值。
  • 有序性(Ordering):程序代码按照指令顺序执行。
    • 若是在本线程内观察,全部的操做都是有序的,指“线程内表现为串行的语义”;若是在一个线程中观察另外一个线程,全部的操做都是无序的,指“指令重排序”现象和“工做内存与主内存同步延迟”现象。
    • 提供两个关键字保证有序性:volatile 自己就包含了禁止指令重排序的语义;synchronized保证一个变量在同一个时刻只容许一条线程对其进行lock操做,使得持有同一个锁的两个同步块只能串行地进入。

其余介绍

01.关于博客汇总连接

02.关于个人博客

相关文章
相关标签/搜索