本文共 14743 字,大约阅读时间需要 49 分钟。
目录介绍
- 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万字],将会陆续发表到网上,转载请注明出处,谢谢!
- 链接地址:
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有博客将陆续开源到GitHub!
5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程,会降低程序的性能,那么该如何做才能降低性能?
-
线程池好处:
- 1)降低资源消耗;
- 2)提高相应速度;
- 3)提高线程的可管理性。
-
线程池的实现原理:
- 当提交一个新任务到线程池时,判断核心线程池里的线程是否都在执行。如果不是,则创建一个新的线程执行任务。如果核心线程池的线程都在执行任务,则进入下个流程。
- 判断工作队列是否已满。如果未满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
- 判断线程池是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了,则交给饱和策略来处理这个任务。
5.0.0.3 线程中start和run方法有什么区别?wait和sleep方法的不同?sleep() 、join()、yield()有什么区别?
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的方式在多线程中正常工作了。
- 更多详细参考博客:
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 同步和非同步、阻塞和非阻塞的概念?分别有哪些使用场景?
-
同步和非同步
- 同步和异步体现的是消息的通知机制:所谓同步,方法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方法里面的代码才是被线程自身所调用的。
-
举个例子
-
同步方法和同步块,哪个是更好的选择?
- 同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
-
同步的范围越少越好吗?
- 是的。虽说同步的范围越少越好,但是在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 overtag b set number overtag = b;num =200tag = a;num =100//调用method02方法打印日志【static静态方法】tag a set number overtag = a;num =100tag b set number overtag = b;num =200
- 在static方法前加synchronized:静态方法属于类方法,它属于这个类,获取到的锁,是属于类的锁。
- 在普通方法前加synchronized:非static方法获取到的锁,是属于当前对象的锁。
- 结论:类锁和对象锁不同,synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。
其他介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:
- 知乎:
- 简书:
- csdn:
- 喜马拉雅听书:
- 开源中国:
- 泡在网上的日子:
- 邮箱:yangchong211@163.com
- 阿里云博客: 239.headeruserinfo.3.dT4bcV
- segmentfault头条:
- 掘金: