说一下 runnable 和 callable 有什么区别?
相同点
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
主要区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛 型,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会 阻塞主进程的继续往下执行,如果不调用不会阻塞。
线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的, run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重 复调用,而 start() 只能调用一次。
start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待 run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪 状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直 接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必 须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根 本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
为什么我们调用 start() 方法时会执行 run() 方法,为什么我们 不能直接调用 run() 方法?
这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简 单,但是很多人都会答不上来!
new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并 使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线 程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执 行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
什么是 Callable 和 Future?
Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会 返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程 执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以 拿到异步执行任务的返回值。
Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果。
什么是 FutureTask
FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经 完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完 成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现类,所 以 FutureTask 也可以放入线程池中。
说说线程的生命周期及五种基本状态?
1. 新建(new):新创建了一个线程对象。
2. 可运行(runnable):线程对象创建后,当调用线程对象的 start()方法, 该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
3. 运行(running):可运行状态(runnable)的线程获得了cpu时间片 (timeslice),执行程序代码。
注:就绪状态是进入到运行状态的唯一入 口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4. 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有 机会再次被 CPU 调用以进入到运行状态。
阻塞的情况分三种:
(一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队 列(waitting queue)中,使本线程进入到等待阻塞状态;
(二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占 用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
(三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进 入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处 理完毕时,线程重新转入就绪状态。
5. 死亡(dead):线程run()、main()方法执行结束,或者因异常退出了 run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
Java 中用到的线程调度算法是什么?
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获 得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看, 各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多 个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调 度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。
有两种调度模型:分时调度模型和抢占式调度模型。
分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程 占用的 CPU 的时间片这个也比较好理解。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用 CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
线程的调度策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线 程的运行:
(1)线程体中调用了 yield 方法让出了对 cpu 的占用权利
(2)线程体中调用了 sleep 方法使线程进入睡眠状态
(3)线程由于 IO 操作受到阻塞
(4)另外一个更高优先级线程出现
(5)在支持时间片的系统中,该线程的时间片用完
什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时 间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。 时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择 (也就是说不要让你的程序依赖于线程的优先级)。
请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的 锁;
(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此 方法要处理 InterruptedException 异常;
(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不 能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与 优先级无关;
(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所 有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
sleep() 和 wait() 有什么区别?
两者都可以暂停线程的执行
类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
是否释放锁:sleep() 不释放锁;wait() 释放锁。
用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个 对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏 醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条 件,程序就会在没有满足结束条件的情况下退出。
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条 件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段 标准的使用 wait 和 notify 方法的代码:
synchronized(monitor){// 判断条件谓词是否得到满足while(!locked){// 等待唤醒 monitor.wait();}// 处理其他的业务逻辑}
为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
Java中,任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁 或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象 调用方法一定定义在Object类中。
wait(), notify()和 notifyAll()这些方法在同步代码块中调用 有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面 啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然 而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放 弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理 起来更加复杂。
综上所述,wait()、notify()和notifyAll()方法要定义在Object类中。
为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步 块中被调用?
当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁, 接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。
同样的,当一个线程需要调用对象的 notify()方法时,它会释放这 个对象的锁,以便其他在等待的线程就可以得到这个对象锁。
由于所有的这些方 法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步 方法或者同步块中被调用。
Thread 类中的 yield 方法有什么作用?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。 当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可 能是当前线程,也可能是其他线程,看系统的分配了。
为什么 Thread 类的 sleep()和 yield ()方法是静态的?
Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他 处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在 其他非运行线程调用这些方法。
线程的 sleep()方法和 yield()方法有什么区别?
(1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先 级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运 行的机会;
(2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后 转入就绪(ready)状态;
(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何 异常;
(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移 植性,通常不建议使用yield()方法来控制并发线程的执行。
如何停止一个正在运行的线程?
在java中有以下3种方法可以终止正在运行的线程:
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及 resume一样都是过期作废的方法。
- 使用interrupt方法中断线程。
Java 中 interrupted 和 isInterrupted 方法的区别?
interrupt:用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。 注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监 视线程的状态为并做处理。
支持线程中断的方法(也就是线程中断后会抛出 interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状 态被置为“中断状态”,就会抛出中断异常。
interrupted:是静态方法,查看当前中断信号是true还是false并且清除中断信 号。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后 面的就返回 false 了。
isInterrupted:查看当前中断信号是true还是false
什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前, 当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方 法在任务完成前就返回。
Java 中你怎样唤醒一个阻塞的线程?
首先 ,wait()、notify() 方法是针对对象的,调用任意对象的 wait()方法都将导 致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直 到获取成功才能往下执行;
其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证 同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调 用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就 将之前获取的对象锁释放。
notify() 和 notifyAll() 有什么区别?
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待 池中的线程不会去竞争该对象的锁。
notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争 成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
如何在两个线程间共享数据?
在两个线程间共享变量即可实现共享。 一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如 果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。
Java 如何实现多线程之间的通讯和协作?
可以通过中断 和 共享变量的方式实现线程间的通讯和协作
比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才 能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队 列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法 消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。
因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入 挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。 同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。 这种互相通信的过程就是线程间的协作。
Java中线程通信协作的最常见的两种方式:
一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll() 线程间直接的数据交换:
三.通过管道进行线程间通信:1)字节流;2)字符流
同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对 象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通 常会导致他们停止执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样 从侧面来说也可以避免死锁。
请知道一条原则:同步的范围越小越好。
什么是线程同步和线程互斥,有哪几种实现方式?
当一个线程对共享的数据进行操作时,应使之成为一个”原子操作“,即在没有 完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必 然会得到错误的处理结果,这就是线程的同步。
在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程 之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发 生,需要通过同步来实现线程安全。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若 干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它 要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成 是一种特殊的线程同步。
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式 就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户 态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模 式下的方法有:事件,信号量,互斥量。
实现线程同步的方法
同步代码方法:sychronized 关键字修饰的方法
同步代码块:sychronized 关键字修饰的代码块
使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问 提供了一种免锁机制
使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了 lock接口的锁他与sychronized方法具有相同的基本行为和语义
在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪 种级别的同步?
在 java 虚拟机中,每个对象( Object 和 class )通过某种逻辑关联监视器,每个监 视器和一个对象引用相关联,为了实现监视器的互斥功能,每个对象都关联着一 把锁。
一旦方法或者代码块被 synchronized 修饰,那么这个部分就放入了监视器的监 视区域,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许 执行该部分的代码
另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方 案
如果你提交任务时,线程池队列已满,这时会发生什么
这里区分一下:
(1)如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没 关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以 近乎认为是一个无穷大的队列,可以无限存放任务
(2)如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
什么叫线程安全?servlet 是线程安全吗?
线程安全是编程中的术语,指某个方法在多线程环境中被调用时,能够正确地处 理多个线程之间的共享变量,使程序功能正确完成。
Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一 个方法,是不能保证共享变量的线程安全性的。
Struts2 的 action 是多实例多线程的,是线程安全的,每个请求过来都会 new 一个新的 action 分配给这个请求,请求完成后销毁。
SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流 程。
Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安 全问题,但是性能可以提升不用处理太多的 gc,可以使用 ThreadLocal 来处理 多线程的问题。
在 Java 程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类 AtomicInteger
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock。
手动锁 Java 示例代码如下:
Lock lock = new ReentrantLock();lock. lock();try { System. out. println("获得锁");} catch (Exception e) { // TODO: handle exception} finally { System. out. println("释 放锁"); lock. unlock();}
你对线程优先级的理解是什么?
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先 权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程 会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1-10),1 代表最低 优先级,10 代表最高优先级。
Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级 有关,如非特别需要,一般无需设置线程优先级。
线程类的构造方法、静态块是被哪个线程调用的
这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new 这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用 的。
如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,
那么:
(1)Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方 法是Thread2 自己调用的
(2)Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法 是Thread1 自己调用的
Java 中怎么获取一份线程 dump 文件?你如何在 Java 中获取 线程堆栈?
Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump 文件中。
在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java应 用的 dump 文件。
在 Windows 下,你可以按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件 中,具体位置依赖应用的配置。
一个线程运行时发生异常会怎样?
如果异常没有被捕获该线程将会停止执行。
Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断 情况的一个内嵌接口。
当一个未捕获异常将造成线程中断时,JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的uncaughtException()方法进行处理。
Java 线程数过多会造成什么异常?
线程的生命周期开销非常高
消耗过多的 CPU
资源如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大 量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU资源时还将产生其他性能的开销。
降低稳定性JVM
在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并 且承受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的大 小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出 OutOfMemoryError 异常。
Java中垃圾回收有什么目的?什么时候进行垃圾回收?
垃圾回收是在内存中存在没有引用的对象或超过作用域的对象时进行的。 垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。
如果对象的引用被置为null,垃圾收集器是否会立即释放对象占 用的内存?
不会,在下一个垃圾回调周期中,这个对象将是被可回收的。 也就是说并不会立即被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放 其占用的内存。
finalize()方法什么时候被调用?析构函数(finalization)的目的 是什么?
1)垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的 finalize()方法;
finalize是Object类的一个方法,该方法在Object类中的声明protected void finalize() throws Throwable { }
在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现 对其资源的回收。
注意:一旦垃圾回收器准备释放对象占用的内存,将首先调用 该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用 的内存空间
2)GC本来就是内存回收了,应用还需要在finalization做什么呢? 答案是大部 分时候,什么都不用做(也就是不需要重载)。只有在某些很特殊的情况下,比如 你调用了一些native的方法(一般是C写的),可以要在finaliztion里去调用C的释 放函数。
为什么代码会重排序?
在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是 不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:
在单线程环境下不能改变程序运行的结果;
存在数据依赖关系的不允许重排序
需要注意的是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执 行语义。
as-if-serial规则和happens-before规则的区别
as-if-serial语义保证单线程内程序的执行结果不被改变,happensbefore关系保证正确同步的多线程程序的执行结果不被改变。
as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程 程序是按程序的顺序来执行的。
happens-before关系给编写正确同步的多 线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happensbefore指定的顺序来执行的。
as-if-serial语义和happens-before这么做的目的,都是为了在不改变 程序执行结果的前提下,尽可能地提高程序执行的并行度。
synchronized 的作用?
在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境 下,控制 synchronized 代码段不被多个线程同时执行。synchronized 可以修 饰类、方法、变量。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视 器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线 程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要 操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核 态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为 什么早期的 synchronized 效率低的原因。
庆幸的是在 Java 6 之后 Java 官方对 从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优 化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋 锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
synchronized关键字最主要的三种使用方式:
修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实 例的锁
修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态 成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不 管new了多少个对象,只有一份)。
所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方 法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的 锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例 对象锁。
修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对 象的锁。
总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码 块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实 例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有 缓存功能!
synchronized、volatile、CAS 比较
(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。
(3)CAS 是基于冲突检测的乐观锁(非阻塞)
synchronized 和 Lock 有什么区别?
首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁, 不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放 锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
synchronized 和 ReentrantLock 区别是什么?
synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是 类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样 的类变量
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都 相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。 相同点:两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比 如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获 取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。 同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时 才能释放锁。
主要区别如下:
ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开 启锁;
ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量 等。
二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
volatile 关键字的作用
对于可见性,Java 提供了 volatile 关键字来保证可见性和禁止指令重排。
volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可 见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主 存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详 细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。 volatile 常用于多线程环境下的单次操作(单次读或者单次写)。
Java 中能创建 volatile 数组吗?
能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是 整个数组。意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是 如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用 了。
volatile 变量和 atomic 变量有什么不同?
volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不 能保证原子性。
例如用 volatile 修饰 count 变量,那么 count++ 操作就不是原 子性的。 而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如 getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型 和引用变量也可以进行相似操作。
volatile 能使得一个非原子操作变成原子操作吗?
关键字volatile的主要作用是使变量在多个线程间可见,但无法保证原子性,对于 多个线程访问同一个实例变量需要加锁进行同步。
虽然volatile只能保证可见性不能保证原子性,但用volatile修饰long和double可 以保证其操作原子性。
所以从Oracle Java Spec里面可以看到:
对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原 子的。在操作的时候,可以分成两步,每次对32位操作。
如果使用volatile修饰long和double,那么其读写都是原子操作
对于64位的引用地址的读写,都是原子操作 在实现JVM时,可以自由选择是否把读写long和double作为原子操作 推荐JVM实现为原子操作
java基础第二章答案volatile 修饰符的有过什么实践?
单例模式
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:对于Double-Check这种可能出现的问题(当然这种概率已经非常小了, 但毕竟还是有的嘛~),解决方案是:只需要给instance的声明加上volatile关键 字即可volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之 后,对它的写操作就会有一个内存屏障(什么是内存屏障?),这样,在它的赋 值完成之前,就不用会调用读操作。注意:volatile阻止的不是singleton = newSingleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1- 2-3])完成之前,不会调用读操作(if (instance == null))。
synchronized 和 volatile 的区别是什么?
synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他 线程。
volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线 程环境下变量的可见性;禁止指令重排序。
区别
volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比 synchronized关键字要好。但是volatile关键字只能用于变量而 synchronized关键字可以修饰方法以及代码块。
synchronized关键字在 JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升, 实际开发中使用 synchronized 关键字的场景还是更多一些。
final
什么是不可变对象,它对写并发应用有什么帮助?
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也 即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不 可变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
只有满足如下状态,一个对象才是不可变的;
它的状态不能在创建后再被修改;
所有域都是 final 类型;并且,它被正确创建(创建期间没有发生 this 引用的逸出)。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/25976.html