并行和并发有什么区别?
- 并行:是指多个线程任务在不同CPU上同时进行,是真正意义上的同时执行。
- 并发:是指多个线程任务在同一个CPU上快速地轮换执行,由于切换的速度非常快,给人的感觉就是这些线程任务时在同时进行的,但其实并发只是一种逻辑上的同时进行;
线程和进程的区别?
- 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
- 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
- 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源
- 系统开销:在创建或撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤销线程时的开销。
守护线程是什么?
守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点
JVM中垃圾回收线程就是典型的守护线程。
创建线程的几种方式?
- 通过实现Runnable接口来创建Thread线程
- 通过实现Callable接口来创建Thread线程
- 通过继承Thread类来创建一个线程
- 使用Executor框架来创建线程池
Runnable和Callable有什么区别?
- 实现了Runnable接口后无法返回结果信息,实现了callable接口后有返回值
- 实现了Runnable接口异常无法通过throws抛出异常,实现了callable接口后可以直接抛出Exception异常。
线程状态及转换?
sleep()和wati()的区别?
sleep()和wait()的区别就是调用sleep方法的线程不会释放对象锁,而调用wait()方法会释放对象锁。
线程的run()和start()有什么区别?
- 调用start()方法是用来启动线程的,轮到该线程执行时,会自动调用run();直接调用run()方法,无法达到启动多线程的目的,相当于主线程线性执行Thread对象的run()方法。
- 一个线程对线的start()方法只能调用一次,多次调用会抛出线程错误的异常;run()没有限制。
在Java程序中怎么保证多线程的运行安全?
- 使用安全的类
- 使用自动锁
- 使用手动锁
Java线程同步的几种方法?
- 使用同步代码块
- 使用同步方法
- 使用互斥锁ReetrantLock
Thread.interrupt()方法的工作原理是什么?
- interrupt的作用不是中断线程,而是【通知线程应该中断了】
- 如果线程处于被阻塞状态,那么线程将立刻退出被阻塞状态,抛出一个interruptedException异常
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就可以这么做。
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
- 在调用阻塞方法时正确处理interruptedException异常。
谈谈ThreadLocal的理解?
首先ThreadLocal是线程变量,属于当前线程,对其他线程是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
比如:数据库的连接问题,如果一个客户端频繁连接使用数据库,那么就需要多次建立连接和关闭,我们的服务器可能吃不消,而且如果有一万个客户端,那么服务器压力更大。此时就是使用ThreadLocal的时候了,因为ThreadLocal在每个线程中中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
在哪些场景下会使用到ThreadLocal?
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息
- 数据库连接,Session会话管理
说一说自己对于synchronized关键字的了解?
synchronized的作用:Synchronized就是解决多个线程之间的同步问题,能够确保被它修饰的方法或者代码块在同一时间,只能被一个线程执行。
在早期,Synchronized关键字也被称为重量级锁。当时如果对线程进行挂起、唤醒操作,会导致操作系统从用户态转换到内核态,时钟周期长,导致性能消耗巨大
但是自jdk1.6之后,Synchronized进行了大量的优化,比如轻量级锁等技术来减少锁操作的开销。
Synchronized的使用:1. 放在普通同步方法前,锁的是当前实例对象。 2. 放在静态同步方法前,锁是当前Class对象。 3. 放在同步代码块前,锁的是Synchronized括号里的实例对象。
Synchronized的实现原理:Synchronized是通过进入和退出monitor对象来实现方法和代码块的同步的。
代码块:当我们使用Synchronized修饰代码块时,编译开始和结束的时候会分别插入monitorenter和monitorexit指令。每个对象有自己额monitor对象,当代码执行到monitorenter执行时,就会尝试去获取对象的锁,如果锁的计数器为0,获取成功后就会加一。当执行到monitorexit指令的时候就会把锁的计数器设置为0,表示锁被释放成功。
如何在项目中使用synchronized的?
- 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法:给当前类加锁,占用的是类的锁,与非静态Synchronized方法占用的锁不一样。
- 修饰代码块:给指定对象加锁。
说说JDK1.6之后synchronized关键字底层做了哪些优化,可以详细介绍一下这些优化吗?
JDK1.6之后对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四种状态:1.偏向锁:
谈谈sychronized和ReenTrantLock的区别
底层实现上来说,synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),对象只有在同步块或同步方法中才能调用wait/notify方法,ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。
synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。
- 是否可手动释放:synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活。
- 是否可中断:synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成; ReentrantLock则可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断。
- 是否公平锁:synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。
- 锁是否可绑定条件Condition:synchronized不能绑定; ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机唤醒一个线程要么唤醒全部线程。
锁的对象:synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。
synchronized和volatile的区别是什么?
- synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
- synchronized 可以保证线程间的有序性(个人猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
- synchronized 线程阻塞,volatile 线程不阻塞。
- volatile 本质是告诉 jvm 当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到该变量其他线程被阻塞。
- volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
谈一下你对volatile关键字的理解?
在讲volatile关键字之前,我需要先讲讲java的内存模型,我们的java的变量都存储在主内存当中,每当有一个线程需要读取内存中的变量的时候,java虚拟机会将主内存中的变量拷贝一份放入线程的工作内存中,多个线程之间并不可见,如果我们要保证可见性,就得使用volatile关键字,volatile可以保证变量的可见性,通过通知的方式让其他线程可见,但volatile并不保证变量的原子性,如果要保证变量的原子性,我们可以使用sync关键字或者atomic类,同时volatile可以禁止指令重排,因为编译器和cpu会对我们的代码进行优化,在一些数据没有依赖性的时候,我们的代码执行顺序可能不是我们想象的那样,在多线程情况下,可能会造成一些奇怪的错误,volatile实现指令重排的底层是使用了一个cpu指令叫内存屏障,通过内存屏障的前后指令会不进行指令重排,同时会强制刷新cpu缓存。
总结:volatile是一个java虚拟机提供的轻量级的同步机制,保证可见性和禁止指令重排,但不保证原子性。
说一下对ReentranReadWriteLock的理解?
说下对悲观锁和乐观锁的理解?
- 乐观锁总是假设事情向着好的方向发展,就比如有些人天生乐观,向阳而生!乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于多读的应用类型,因为乐观锁在读取数据的时候不会去加锁,这样可以省去了锁的开销,加大了系统的整个吞吐量。即时偶尔有冲突,这也无伤大雅,要么重新尝试提交要么返回给用户说跟新失败,当然,前提是偶尔发生冲突,但如果经常产生冲突,上层应用会不断的进行自旋重试,这样反倒是降低了性能,得不偿失。
- 悲观锁总是假设事情向着坏的方向发展,就比如有些人经历了某些事情,可能不太相信别人,只信任自己,身在黑暗,脚踩光明!悲观锁每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞住,直到我释放了锁,别人才能拿到锁,这样的话,数据只有本身一个线程在修改,就确保了数据的准确性。因此,悲观锁适用于多写的应用类型。
乐观锁常见的两种实现方式是什么?
乐观锁常见的两种实现方式 - 蝶花残梦 - 博客园
乐观锁的缺点有哪些?
乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但它也有缺点:
乐观锁只能保证一个共享变量的原子操作。如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。
长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。
ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。
CAS和synchronized的使用场景?
CAS 适用于写比较少的情况下(多读场景,冲突一般较少),synchronized 适用于写比较多的情况下(多写场景,冲突一般较多)。对于资源竞争较少(线程冲突较轻)的情况,使用 synchronized 同步锁进行线程阻塞,唤醒切换,以及用户态内核态间的切换操作,都会额外消耗 cpu 资源;而 CAS 基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能;对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized
Java 并发编程这个领域中,synchronized 关键字一直都被认为是元老级的角色,很多人都会称它为 “重量级锁”。但是,在 JavaSE1.6 之后,进行了主要包括为了减少获得锁和释放锁所带来的性能消耗,而引入了 偏向锁 和 轻量级锁 以及其它各种优化,在某些情况下,synchronized 并不是那么重了。synchronized 的底层实现,主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和 CAS 类似的性能;而线程冲突严重的情况下,性能是远高于 CAS。
简单说下对Java中的原子类的理解?
简单说下对 Java 中的原子类的理解? - 六星社区
说下对线程池的理解?为什么要使用线程池?
面试官:谈谈你对线程池的理解
创建线程池的参数有哪些?
JAVA线程池有哪些配置参数,各自的作用是什么? - 启明星2006 - 博客园