javarunnable写法_最新Java⾯试题
⼀. Java程序如何停⽌⼀个线程?
建议使⽤”异常法”来终⽌线程的继续运⾏。在想要被中断执⾏的线程中, 调⽤ interrupted()⽅法,该⽅法⽤来检验当前线程是否已经被中断,即该线程 是否被打上了中断的标记,并不会使得线程⽴即停⽌运⾏,如果返回 true,则 抛出异常,停⽌线程的运⾏。在线程外,调⽤
interrupt()⽅法,使得该线程打 上中断的标记。
⼆. 说⼀下 java 中的多线程。
1. Java 中实现多线程的四种⽅式(创建多线程的四种⽅式)?
java线程池创建的四种①. 继承 Thread 类创建线程类
定义 Thread 类的⼦类,并重写该类的 run ⽅法,该 run ⽅法的⽅ 法体就代表了线程要完成的任务。因此把 run()⽅法称为执⾏体。
创建 Thread ⼦类的实例,即创建了线程对象。
调⽤线程对象的 start()⽅法来启动该线程。
②. 通过 Runnable 接⼝创建线程类
定义 Runnable 接⼝的实现类,并重写该接⼝的 run()⽅法,该 run() ⽅法的⽅法体同样是该线程的线程执⾏体。
创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
调⽤线程对象的 start()⽅法来启动该线程。
③. 通过 Callable 和 Future 创建线程
创建 Callable 接⼝的实现类,并实现 call()⽅法,该 call()⽅法将作 为线程执⾏体,并且有返回值。
创建 Callable 实现类的实例,使⽤ FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()⽅法的返回值。
使⽤ FutureTask 对象作为 Thread 对象的 target 创建并启动新线 程。
调⽤ FutureTask 对象的 get()⽅法来获得⼦线程执⾏结束后的返回值。
④. 通过线程池创建线程利⽤线程池不⽤ new 就可以创建线程,线程可复⽤,利⽤ Executors 创 建线程池。
扩展1:Java 中 Runnable 和 Callable 有什么不同?
Callable 定义的⽅法是 call(),⽽ Runnable 定义的⽅法是 run()。
Callable 的 call ⽅法可以有返回值,⽽ Runnable 的 run ⽅法不能有 返回值。
Callable 的 call ⽅法可抛出异常,⽽ Runnable 的 run ⽅法不能抛出 异常。
扩展2:⼀个类是否可以同时继承 Thread 和实现 Runnable接⼝?<
可以。⽐如下⾯的程序可以通过编译。因为 Test 类从 Thread 类中继承了 run()⽅法,这个 run()⽅法可以被当作对 Runnable 接⼝的实现。
public class Test extends Thread implements Runnable {    public static void main(String[] args) {        Thread t = new Thread(new Test());        t.start();    }
2. 实现多线程的同步。
在多线程的环境中,经常会遇到数据的共享问题,即当多个线程需要访问同 ⼀资源时,他们需要以某种顺序来确保该资源在某⼀时刻只能被⼀个线程使⽤, 否则,程序的运⾏结果将会是不可预料的,在这种情况下,就必须对数据进⾏ 同步。
在 Java 中,提供了四种⽅式来实现同步互斥访问: synchronized 和 Lock 和 wait()/notify()/notifyAll()⽅法和 CAS。
①. synchronized 的⽤法。
A . 同步代码块
synchronized 块写法: synchronized(object) {}
表⽰线程在执⾏的时候会将 object 对象上锁。(注意这个对象可以是任意 类的对象,也可以使⽤ this 关键字或者是 class 对象)。
可能⼀个⽅法中只有⼏⾏代码会涉及到线程同步问题,所以 synchronized 块 ⽐ synchronized ⽅法更加细粒度地控制了多个线程的访问,只有 synchronized 块中的内容不能同时被多个线程所访问,⽅法中的其他语句仍然 可以同时被多个线程所访问(包括 synchronized 块之前的和之后的)。
B . 修饰⾮静态的⽅法
当 synchronized 关键字修饰⼀个⽅法的时候,该⽅法叫做同步⽅法。
Java 中的每个对象都有⼀个锁(lock),或者叫做监视器(monitor), 当⼀个线程访问某个对象的 synchronized ⽅法时,将该对象上锁,其他任何 线程都⽆法再去访问该对象的 synchronized ⽅法了(这⾥是指所有的同步⽅ 法,⽽不仅仅是同⼀个⽅法),直到之前的那个线程执⾏⽅法完毕后(或者是 抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的 synchronized ⽅法。
注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制 关系。
注意,如果⼀个对象有多个 synchronized ⽅法,某⼀时刻某个线程已经进⼊ 到了某个 synchronized ⽅法,那么在该⽅法没有执⾏完毕前,其他线程是⽆法访 问该对象的任何 synchronized ⽅法的。
C . 修饰静态的⽅法
当⼀个 synchronized 关键字修饰的⽅法同时⼜被 static 修饰,之前说过, ⾮静态的同步⽅法会将对象上锁,但是静态⽅法不属于对象,⽽是属于类,它 会将这个⽅法所在的类的 Class 对象上锁。⼀个类不管⽣成多少个对象,它们 所对应的是同⼀个 Class 对象。
因此,当线程分别访问同⼀个类的两个对象的两个 static,synchronized ⽅法时,它们的执⾏顺序也是顺序的,也就是说⼀个线程先去执⾏⽅法,执⾏ 完毕后另⼀个线程才开始。
结论:
synchronized ⽅法是⼀种粗粒度的并发控制,某⼀时刻,只能有⼀个线 程执⾏该 synchronized ⽅法。
synchronized 块则是⼀种细粒度的并发控制,只会将块中的代码同步, 位于⽅法内,synchronized 块之外的其他代码是可以被多个线程同时访问到 的。
②.Lock 的⽤法。
使⽤ Lock 必须在 try-catch-finally 块中进⾏,并且将释放锁的操作放在 finally 块中进⾏,以保证锁⼀定被释放,防⽌死锁的发⽣。通常使⽤Lock 来 进⾏同步的话,是以下⾯这种形式去使⽤的:
Lock lock = ...;lock.lock();try{    //处理任务}catch(Exception ex){}finally{    lock.unlock();    //释放锁}
Lock 和 synchronized 的区别和 Lock 的优势。你需要实现 ⼀个⾼效的缓存,它允许多个⽤户读,但只允许⼀个⽤户写,以此 来保持它的完整性,你会怎样去实现它?
Lock 是⼀个接⼝,⽽ synchronized 是 Java 中的关键字, synchronized 是内置的语⾔实现;
synchronized 在发⽣异常时,会⾃动释放线程占有的锁,因此不会导 致死锁现象发⽣;⽽ Lock 在发⽣异常时,如果没有主动通过unLock()去释放 锁,则很可能造成死锁现象,因此使⽤ Lock 时需要在 finally 块中释放锁;
Lock 可以让等待锁的线程响应中断(可中断锁),⽽ synchronized 却不⾏,使⽤ synchronized 时,等待的线程会⼀直等待下去,不能够响应中 断(不可中断锁);
通过 Lock 可以知道有没有成功获取锁(tryLock()⽅法:如果获取 了锁,则返回 true;否则返回 false,也就说这个⽅法⽆论如何都会⽴即返回。在拿不到锁时不会⼀直在那等待。),⽽ synchronized 却⽆法办到。
Lock 可以提⾼多个线程进⾏读操作的效率(读写锁)。
Lock 可以实现公平锁,synchronized 不保证公平性。在性能上来说,如果线程竞争资源不激烈时,两者的性能是差不多的,⽽ 当竞争资源⾮常激烈时(即有⼤量线程同时竞争),此时 Lock 的性能要远远优 于 synchronized。所以说,在具体使⽤时要根据适当情况选择。
扩展1: volatile 和 synchronized 区别。
volatile 是变量修饰符,⽽ synchronized 则作⽤于代码块或⽅法。
volatile 不会对变量加锁,不会造成线程的阻塞;synchronized 会 对变量加锁,可能会造成线程的阻塞。
volatile 仅能实现变量的修改可见性,并不能保证原⼦性;⽽ synchronized 则 可 以 保 证 变 量 的 修 改 可 见 性 和 原 ⼦ 性 。
(synchronized 有两个重要含义:它确保了⼀次只有⼀个线程可以执 ⾏代码的受保护部分(互斥),⽽且它确保了⼀个线程更改的数据对于其它线程是可见的(更改的可见性),在释放锁之前会将对变量的修改 刷新到主存中)。
volatile 标记的变量不会被编译器优化,禁⽌指令重排序; synchronized 标记的变量可以被编译器优化。
扩展 2:什么场景下可以使⽤ volatile 替换 synchronized?只需要保证共享资源的可见性的时候可以使⽤ volatile 替代, synchronized 保证可操作的原⼦性,⼀致性和可见性。
③.wait()otify()otifyAll()的⽤法(Java 中怎样唤醒⼀个阻塞的线程?)。
在 Java 发展史上曾经使⽤ suspend()、resume()⽅法对于线程进⾏阻塞唤醒,但随之出 现很多问题,⽐较典型的还是死锁问题。
解决⽅案可以使⽤以对象为⽬标的阻塞,即利⽤ Object 类的 wait()和 notify()⽅法实现 线程阻塞。
⾸先,wait、notify ⽅法是针对对象的,调⽤任意对象的 wait()⽅法都将导致线程阻塞, 阻塞的同时也将释放该对象的锁,相应地,调⽤任意对象的 notify()⽅法则将随机解除该对 象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执⾏;其次,wait、 notify ⽅法必须在synchronized 块或⽅法中被调⽤,并且要保证同步块或⽅法的锁对象与调 ⽤ wait、notify ⽅法的对象是同⼀个,如此⼀来在调⽤ wait 之前当前线程就已经成功获取 某对象的锁,执⾏ wait 阻塞后当前线程就将之前获取的对象锁释放。
扩展 1: 为什么 wait(),notify(),notifyAll()等⽅法都定义在 Object 类中?
因为这三个⽅法都需要定义在同步代码块或同步⽅法中,这些⽅法的调⽤是依赖锁对 象的,⽽同步代码块或同步⽅法中的锁对象可以是任意对象,那么能被任意对象调⽤的⽅ 法⼀定定义在 Object 类中。
扩展 2: notify()和 notifyAll()有什么区别?
notify()和 notifyAll()都是 Object 对象⽤于通知处在等待该对象的线程的⽅法。void notify(): 唤醒⼀个正
在等待该对象的线程,进⼊就绪队列等待 CPU 的调度。void notifyAll(): 唤醒所有正在等待该对象的线程,进⼊就绪队列等待 CPU 的调度。两者的最⼤区别在于:notifyAll 使所有原来在该对象上等待被 notify 的线程统统退出 wait 的状态,变成等待该对 象上的锁,⼀旦该对象被解锁,他们就会去竞争。notify 他只是选择⼀个 wait 状态线程进⾏通知,并使它获得该对象上的锁,但不惊动其他 同样在等待被该对象 notify 的线程们,当第⼀个线程运⾏完毕以后释放对象上的锁,此时 如果该对象没有再次使⽤ notify 语句,即便该对象已经空闲,其他 wait 状态等待的线程由 于没有得到该对象的通知,继续处在 wait 状态,直到这个对象发出⼀个 notify 或 notifyAll, 它们等待的是被 notify 或 notifyAll,⽽不是锁。
④.CAS
它是⼀种⾮阻塞的同步⽅式。具体参见上⾯的部分。
扩展⼀:同步锁的分类?
Synchronized 和 Lock 都是悲观锁。
乐观锁,CAS 同步原语,如原⼦类,⾮阻塞同步⽅式。
扩展⼆:锁的分类?
⼀种是代码层次上的,如 java 中的同步锁,可重⼊锁,公平锁,读写锁。另外⼀种是数据库层次上的,⽐较典型的就是悲观锁和乐观锁,表锁,⾏锁,页锁。
扩展三:java 中的悲观锁和乐观锁?
悲观锁:悲观锁是认为肯定有其他线程来争夺资源,因此不管到底会不会发⽣争夺, 悲观锁总是会先去锁住资源,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放 锁。Synchronized 和 Lock 都是悲观锁。
乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直 到成功为⽌。就是当去做某个修改或其他操作的时候它认为不会有其他线程来做同样的操 作(竞争),这是⼀种乐观的态度,通常是基于 CAS 原⼦指令来实现的。CAS 通常不会将 线程挂起,因此有时性能会好⼀些。乐观锁的⼀种实现⽅式——CAS。
三. 实现线程之间的通信?
当线程间是可以共享资源时,线程间通信是协调它们的重要的⼿段。
1. Object 类中 wait()otify()otifyAll()⽅法。
2. ⽤ Condition 接⼝。
Condition 是被绑定到 Lock 上的,要创建⼀个 Lock 的 Condition 对 象必须⽤ newCondition()⽅法。在⼀个 Lock 对象⾥⾯可以创建多个 Condition 对象,线程可以注册在指定的 Condition 对象中,从⽽可以有 选择性地进⾏线程通知,在线程调度上更加灵活。
在 Condition 中,⽤ await()替换 wait(),⽤ signal()替换 notify(), ⽤ signalAll()替换 notifyAll(),传统线程的通信⽅式, Condition 都可以实现。调⽤ Condition 对象中的⽅法时,需要被包含在 lock()和 unlock()之间。
3. 管道实现线程间的通信。
实现⽅式:⼀个线程发送数据到输出管道流,另⼀个线程从输⼊管道流中 读取数据。
基本流程:
1)创建管道输出流 PipedOutputStream pos 和管道输⼊流 PipedInputStream pis。
2)将 pos 和 pis 匹配,t(pis)。
3)将 pos 赋给信息输⼊信息的线程,pis 赋给获取信息的线程,就可以实 现线程间的通讯了。
缺点:
1)管道流只能在两个线程之间传递数据。
线程 consumer1 和 consumer2 同时从 pis 中 read 数据,当线程 producer 往管道流中写⼊⼀段数据(1,2,3,4,5,6)后,每⼀个时刻只有⼀个 线程能获取到数据,并不是两个线程都能获取到 producer 发送来的数据,因 此⼀个管道流只能⽤于两个线程间的通讯。
2)管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流。
线程 producer 通过管道流向线程 consumer 发送数据,如果线程 consumer 想给线程 producer 发送数据,则需要新建另⼀个管道流pos1 和 pis1,将 pos1 赋给 consumer1,将 pis1 赋给 producer1。
4. 使⽤ volatile 关键字见上⾯部分。
四. 如何确保线程安全?
如果多个线程同时运⾏某段代码,如果每次运⾏结果和单线程运⾏的结果 是⼀样的,⽽且其他变量的值也和预期的是⼀样的,就是线程安全的。
Synchronized,Lock,原⼦类(如 atomicinteger 等),同步容器、并 发容器、 阻塞队列 、 同步辅助类(⽐ 如 CountDownLatch, Semaphore, CyclicBarrier)。
五. 多线程的优点和缺点?
1. 优点:
充分利⽤ cpu,避免 cpu 空转。
程序响应更快。
2. 缺点:
上下⽂切换的开销
当 CPU 从执⾏⼀个线程切换到执⾏另外⼀个线程的时候,它需要先存储当 前线程的本地的数据,程序指针等,然后载⼊另⼀个线程的本地数据,程序指 针等,最后才开始执⾏。这种切换称为“上下⽂切换”。CPU 会在⼀个上下⽂ 中执⾏⼀个线程,然后切换到另外⼀个上下⽂中执⾏另外⼀个线程。上下⽂切 换并不廉价。如果没有必要,应该减少上下⽂切换的发⽣。
增加资源消耗
线程在运⾏的时候需要从计算机⾥⾯得到⼀些资源。除了 CPU,线程还需 要⼀些内存来维持它本地的堆栈。它也需要占⽤操作系统中⼀些资源来管理线 程。
编程更复杂
在多线程访问共享数据的时候,要考虑线程安全问题。
六. 写出 3 条你遵循的多线程最佳实践。
1. 给线程起个有意义的名字。
2. 避免锁定和缩⼩同步的范围 。相对于同步⽅法我更喜欢同步块,它给我拥有对锁的绝对控制权。
3. 多⽤同步辅助类,少⽤ wait 和 notify 。⾸先,CountDownLatch, Semaphore, CyclicBarrier 这些同步辅助 类简化了编码操作,⽽⽤
wait 和 notify 很难实现对复杂控制流的控制。其次, 这些类是由最好的企业编写和维护在后续的 JDK 中它们还会不断优化和完善, 使⽤这些更⾼等级的同步⼯具你的程序可以不费吹灰之⼒获得优化。
4. 多⽤并发容器,少⽤同步容器。如果下⼀次你需要⽤到 map,你应该⾸先想到⽤ ConcurrentHashMap。
七. 多线程的性能⼀定就优于单线程吗?
不⼀定,要看具体的任务以及计算机的配置。⽐如说:
对于单核 CPU,如果是 CPU 密集型任务,如解压⽂件,多线程的性能反 ⽽不如单线程性能,因为解压⽂件需要⼀直占⽤ CPU 资源,如果采⽤多线程, 线程切换导致的开销反⽽会让性能下降。如果是交互类型的任务,肯定是需要 使⽤多线程的。
对于多核 CPU,对于解压⽂件来说,多线程肯定优于单线程,因为多个线 程能够更加充分利⽤每个核的资源。
⼋. 多线程中锁的种类。
1. 可重⼊锁
ReentrantLock 和 synchronized 都是可重⼊锁。如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该⽅法 中调⽤另外⼀个同步⽅法也同样持有该锁。⽐如:
public sychrnozied void test() {    xxxxxx;    test2();}public sychronized void test2() {    yyyyy;}
在上⾯代码段中,执⾏ test ⽅法需要获得当前对象作为监视器的对象锁, 但⽅法中⼜调⽤了 test2 的同步⽅法。
如果锁是具有可重⼊性的话,那么该线程在调⽤ test2 时并不需要再次获 得当前对象的锁,可以直接进⼊ test2 ⽅法进⾏操作。
可重⼊锁最⼤的作⽤是避免死锁。如果锁是不具有可重⼊性的话,那么该 线程在调⽤ test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程 所持有,不可能再次获得,那么线程在调⽤同步⽅法、含有锁的⽅法时就会产 ⽣死锁。
2. 可中断锁
顾名思义,就是可以响应中断的锁。
在 Java 中,synchronized 不是可中断锁,⽽ Lock 是可中断锁。lockInterruptibly()的⽤法已经体现了 Lock 的可中断性。如果某⼀线程 A 正 在执⾏锁中的代码,另⼀线程 B 正在等待获取该锁,可能由于等待时间过长, 线程 B 不想等待了,想先处理其他事情,我们可以让它中断⾃⼰或者在别的线 程中断它,这种就是可中断锁。
3. 公平锁
在 Java 中,synchronized 就是⾮公平锁,它⽆法保证等待的线程获取锁 的顺序。⽽对于 ReentrantLock 和 ReentrantReadWriteLock,它默认情况 下是⾮公平锁,但是可以设置为公平锁。
公平锁即尽量以请求锁的顺序来获取锁。⽐如同是有多个线程在等待⼀个 锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁, 这种就是公平锁。
4. 读写锁
正因为有了读写锁,才使得多个线程之间的读操作不会发⽣冲突。ReadWriteLock 就是读写锁,它是⼀个接⼝,ReentrantReadWriteLock 实现了这个接⼝。可以通过 readLock()获取读锁,通过 writeLock()获取写锁。
九. 锁优化

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。