28、java 中有几种方法可以实现一个线程?
继 承 Thread 类
实现 Runnable 接口
实现 Callable 接口, 需要实现的是 call() 方法
29、如何停止一个正在运行的线程?
使用共享变量的方式 在这种方式中, 之所以引入共享变量, 是因为该变量可以被多个执行相同任务的线程用 来作为是否中断的信号, 通知中断线程的执行。
使用 interrupt 方法终止线程
如果一个线程由于等待某些事件的发生而被阻塞, 又该怎样停止该线程呢? 这种情况经 常会发生, 比如当一个线程由于需要等候键盘输入而被阻塞, 或者调用Thread.join()方 法, 或者 Thread.sleep()方法, 在网络中调用 ServerSocket.accept()方法,或者调用了 DatagramSocket.receive()方法时,都有可能 导致线程阻塞, 使线程处于处于不可运行状态时, 即使主程序中将该线程的共享变量设 置为 true, 但该线程此时根本无法检查循环标志, 当然也就无法立即中断。这里我们给 出的建议是,不要使用 stop()方法,而是使用 Thread 提供的interrupt()方法,因为该方法 虽然不会中断一个正在运行的线程,但是它可以使一 个被阻塞的线程抛出一个中断异常, 从而使线程提前结束阻塞状态, 退出堵塞代码。
30、notify()和 notifyAll()有什么区别?
当一个线程进入 wait 之后,就必须等其他线程 notify/notifyall,使用 notifyall,可以唤醒 所有处于 wait 状态的线程,使其重新进入锁的争夺队列中,而 notify 只能唤醒一个。 如果没把握, 建议 notifyAll, 防止 notigy 因为信号丢失而造成程序异常。
31、什么是 Daemon 线程?
它有什么意义? 所谓后台(daemon)线程, 是指在程序运行的时候在后台提供一种通用服务的线程, 并 且这个线程并不属于程序中不可或缺的部分。因此, 当所有的非后台线程结束时, 程序 也就终止了, 同时会杀死进程中的所有后台线程。反过来说, 只要有任何非后台线程还在运行, 程序就不会终止。必须在线程启动之前调用 setDaemon()方法, 才能把它设置为后台线程。注意: 后台进程在不执行 finally 子句的情况下就会终止其 run()方法。 比如: JVM 的垃圾回收线程就是 Daemon 线程, Finalizer 也是守护线程。
32、java 如何实现多线程之间的通讯和协作?
中断 和 共享变量
33、什么是可重入锁(ReentrantLock)? 举例来说明锁的可重入性
public class UnReentrant{
Lock lock = new Lock();
publiclock.lock();
inner();
lock.unlock();
}
public void inner(){
lock.lock();
//do something
lock.unlock();
}
outer 中调用了 inner,outer 先锁住了 lock,这样 inner 就不能再获取 lock。其实调用 outer 的线程已经获取了 lock 锁, 但是不能在 inner 中重复利用已经获取的锁资源,这 种锁即称之为 不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着 的代码块。 synchronized、ReentrantLock 都是可重入的锁, 可重入锁相对来说简化了并发编程的 开发。
34、当一个线程进入某个对象的一个 synchronized 的实例方 法后,其它线程是否可进入此对象的其它方法?
如果其他方法没有 synchronized 的话, 其他线程是可以进入的。 所以要开放一个线程安全的对象时, 得保证每个方法都是线程安全的。
35、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
悲观锁: 总是假设最坏的情况, 每次去拿数据的时候都认为别人会修改, 所以每次在拿 数据的时候都会上锁, 这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数 据库里边就用到了很多这种锁机制, 比如行锁, 表锁等, 读锁, 写锁等, 都是在做操 作之前先上锁。再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。
乐观锁: 顾名思义, 就是很乐观, 每次去拿数据的时候都认为别人不会修改, 所以不会 上锁, 但是在更新的时候会判断一下在此期间别人有没有去更新这个数据, 可以使用版本号等 机制。乐观锁适用于多读的应用类型, 这样可以提高吞吐量, 像数据库提供的类似于 write_condition 机制, 其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
乐观锁的实现方式:
1、使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标 识, 不一 致时可以采取丢弃和再次尝试的策略。
2、java 中的 Compare and Swap 即 CAS ,当多个线程尝试使用 CAS 同时更新 同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的 线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操 作 中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A) 和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自 动将该位置值更新为新值 B。否则处理器不做任何操作。
CAS 缺点:
1、ABA 问题: 比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中 取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A, 这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线 程 one 的 CAS 操作成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
2、循环时间长开销大: 对于资源竞争严重( 线程冲突严重) 的情况, CAS 自旋的概率会比较大, 从而浪费更 多的 CPU 资源, 效率低于 synchronized。
3、只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时, 我们可以使用循环 CAS 的方式来保证原子操作, 但是对 多个共享变量操作时, 循环 CAS 就无法保证操作的原子性, 这个时候就可以用锁。
36、SynchronizedMap 和 ConcurrentHashMap 有什么区 别?
SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访为 map。
ConcurrentHashMap 使用分段锁来保证在多线程下的性能。
ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将 hash 表分为 16 个桶, 诸如 get,put,remove 等常用操作只锁当前需要用到的桶。这样, 原来只能一个线程进入,现在却能同时有 16 个写线程执行,并发性能的提升是显而易见 的。
另外 ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中, 当 iterator 被创建后集合再发生改变就不再是抛出 ConcurrentModificationException, 取而代之的是在改变时 new 新的数据从而 不影响原有的数据 ,iterator 完成后再将头指针替换为新的数据 ,这样 iterator 线程可以使用原来老的数据, 而写线程也可以并发的完成改变。