66.同步块会导致线程饥饿吗?
没有定义线程可以进入同步块的顺序。因此,从理论上讲,如果许多线程正在等待同步块的入口,则某些线程必须比其他线程等待更长的时间。因此,他们没有足够的计算时间来及时完成工作。
5.守卫块
67.每个对象继承的哪两种方法java.lang.Object
可用于实现简单的生产者/消费者方案?
当工作线程完成其当前任务并且新任务的队列为空时,它可以通过获取队列对象的内在锁并调用方法来释放处理器wait()
。该生产者线程将唤醒该线程,该生产者线程将新任务放入队列中,并再次获取队列对象的相同内在锁并对其进行调用notify()
。
68.notify()
和之间有什么区别notifyAll()
?
两种方法都用于唤醒一个或多个通过调用进入睡眠状态的线程wait()
。虽然notify()
仅唤醒其中一个等待线程,但notifyAll()
唤醒所有等待线程。
69.如何确定通过调用唤醒哪个线程notify()
?
notify()
如果有多个线程正在等待,则不指定将通过调用唤醒哪些线程。因此,代码不应依赖任何具体的JVM实现。
70
下面的从某些队列实现中检索整数值的代码是否正确?
public Integer getNextInt() { Integer retVal = null; synchronized (queue) { try { while (queue.isEmpty()) { queue.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (queue) { retVal = queue.poll(); if (retVal == null) { System.err.println("retVal is null"); throw new IllegalStateException(); } } return retVal; }
尽管上面的代码将队列用作对象监视器,但是在多线程环境中它无法正确运行。原因是它有两个单独的同步块。当第6行中的另一个线程唤醒了两个线程时,两个线程在另一个notifyAll()
同步块中一个接一个地输入。如果队列的第二个块现在只有一个新值,那么第二个线程将轮询一个空队列并获得null作为返回值。
6.不可变的对象
71.什么是不可变对象
- 不变对象可以通过任何机制发布
- 不可变对象可以被任何线程安全地使用,而无需额外的同步,即使不使用同步来发布它们。
72.如何创建一个不可变的对象
要创建一个不变的对象,您需要:
- 不要添加任何设置方法
- 声明所有字段的最终和私有
- 如果字段是可变对象,则为获取方法创建其防御性副本
- 如果必须将传递给构造函数的可变对象分配给字段,请为其创建防御性副本
- 不允许子类覆盖方法。
73.为了实现不可变的类,您必须遵循哪些规则?
- 所有字段均应为最终字段和私有字段。
- 不应使用setter方法。
- 应该将类本身声明为final,以防止子类违反不变性原则。
- 如果字段不是原始类型,而是对另一个对象的引用:
- 不应有将引用直接暴露给调用者的getter方法。
- 不要更改引用的对象(或者至少更改这些引用对对象的客户端不可见)。
7,锁定对象
74.是否可以检查线程是否在某个给定对象上持有监视器锁定?
该类java.lang.Thread
提供静态方法Thread.holdsLock(Object)
,当且仅当当前线程持有作为方法调用的参数给定的对象上的锁时,该方法才返回true。
75.通过锁争用我们了解什么?
当两个或多个线程在获取锁中竞争时,发生锁争用。调度程序必须决定是否让必须等待睡眠的线程并执行上下文切换以让另一个线程占用CPU,或者让等待线程忙于等待是否更有效率。两种方式都会将空闲时间引入劣质线程。
76.哪些技术有助于减少锁争用?
在某些情况下,可以通过应用以下技术之一来减少锁争用:
- 锁的范围减小了。
- 减少获取某个锁的次数(锁拆分)。
- 使用硬件支持的乐观锁定操作而不是同步。
- 尽可能避免同步。
- 避免对象池。
77.可以将哪种减少锁争用的技术应用于以下代码?
synchronized (map) { UUID randomUUID = UUID.randomUUID(); Integer value = Integer.valueOf(42); String key = randomUUID.toString(); map.put(key, value); }
上面的代码执行随机UUID的计算,并将文字42转换为同步块内的Integer对象,尽管这两行代码对于当前线程而言是本地的,并不影响其他线程。因此,可以将它们移出同步块:
UUID randomUUID = UUID.randomUUID(); Integer value = Integer.valueOf(42); String key = randomUUID.toString(); synchronized (map) { map.put(key, value); }
78.举例说明技术锁拆分。
当一个锁用于同步对同一应用程序不同方面的访问时,锁拆分可能是减少锁争用的一种方法。假设我们有一个实现对应用程序的某些统计数据进行计算的类。此类的第一个版本在每个方法签名中使用关键字sync,以便在多个并发线程损坏之前保护内部状态。这也意味着每个方法调用都可能导致锁争用,因为其他线程可能会尝试同时获取同一锁。但是对于每种方法中每种统计数据类型,可以将对象实例上的锁拆分为几个较小的锁。因此,试图递增统计数据D1的线程T1不必等待锁定,而线程T2同时更新数据D2。
79.通过锁条我们了解什么?
与锁拆分相反,在锁拆分中,我们针对应用程序的不同方面引入了不同的锁,而锁条使用了多个锁来保护同一数据结构的不同部分。这种技术的一个示例是ConcurrentHashMap
JDKjava.util.concurrent
软件包中的类。该Map
实现使用内部不同的存储桶来存储其值。通过值的键选择存储桶。ConcurrentHashMap
现在使用不同的锁来保护不同的哈希桶。因此,一个尝试访问第一个哈希存储桶的线程可以获取该存储桶的锁,而另一个线程可以同时访问第二个存储桶。与同步版本HashMap
相比,当不同线程在不同存储桶上工作时,此技术可以提高性能。
80. Java Concurrency API中的Lock接口是什么?
java.util.concurrent.locks.Lock接口用作类似于同步块的线程同步机制。新的锁定机制比同步块更灵活,并提供更多选项。
锁和同步块之间的主要区别如下:
- 保证顺序?同步块不提供对等待线程进行访问的顺序的任何保证。锁接口处理它。
- 没有超时?如果未授予锁定,则同步块没有超时选项。锁定界面提供了这种选择。
- 单一方法?同步块必须完全包含在一个方法中,而锁接口的方法lock()和unlock()可以在不同的方法中调用。
8,执行人
8.1执行器接口
81. ExecutorService在计时器上的优点
- 与ExecutorService不同,Timer无法利用可用的CPU内核,特别是在使用ExecutorService之类的多个任务(例如ForkJoinPool)时
- 如果您需要在多个任务之间进行协调,则ExecutorService提供了协作式API。假设您必须提交N个工作者任务,然后等待所有任务完成。您可以使用invokeAll API轻松实现它。如果要通过多个Timer任务实现相同的目标,这将不那么简单。
- ThreadPoolExecutor提供了更好的API,用于管理线程生命周期。
82. Executor和ExecutorService这两个接口之间是什么关系?
该接口Executor
仅定义一种方法:execute(Runnable)
。该接口的实现将必须在将来的某个时间执行给定的Runnable实例。该ExecutorService
接口是该接口的扩展,Executor
并提供了其他方法来关闭基础实现,等待所有提交的任务终止,并且允许提交的实例Callable
。
83.当您submit()
对队列已满的ExecutorService实例执行新任务时会发生什么?
如的方法签名submit()
所示,该ExecutorService
实现应该抛出RejectedExecutionException
。
84.什么是ScheduledExecutorService?
接口ScheduledExecutorService
扩展了接口ExecutorService
并添加了方法,该方法允许将新任务提交给应该在给定时间点执行的基础实现。有两种方法可以安排一次性任务,而有两种方法可以创建和执行定期任务。
8.2线程池
85.您知道一种简单的方法来构造具有5个线程的线程池,该线程池执行一些返回值的任务吗?
SDK提供了一个factory和utility类,Executors
其静态方法newFixedThreadPool(int nThreads)
允许创建具有固定线程数的线程池(MyCallable
省略了的实现):
public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(5); Future<Integer>[] futures = new Future[5]; for (int i = 0; i < futures.length; i++) { futures[i] = executorService.submit(new MyCallable()); } for (int i = 0; i < futures.length; i++) { Integer retVal = futures[i].get(); System.out.println(retVal); } executorService.shutdown(); }
86.是否可以在Java 8中使用线程池执行流操作?
集合提供了一种parallelStream()
创建由线程池处理的流的方法。或者,您可以parallel()
在给定流上调用中间方法,以将顺序流转换为并行对应流。
87.对象池是否始终可以提高多线程应用程序的性能?
试图通过池化来避免构建新对象的对象池可以提高单线程应用程序的性能,因为通过从池中请求新对象来交换对象创建成本。在多线程应用程序中,此类对象池必须具有对该池的同步访问权限,并且锁争用的额外成本可能会超过新对象的额外构造和垃圾回收所节省的成本。因此,对象池可能无法始终提高多线程应用程序的整体性能。
8.3前叉/连接
88.我们如何访问并行流操作所使用的线程池?
并行流操作所使用的线程池可以通过访问ForkJoinPool.commonPool()
。这样,我们可以使用来查询其并行度commonPool.getParallelism()
。水平不能在运行时改变,但它可以通过提供以下JVM参数来配置:-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
。
89.使用Fork / Join框架可以解决哪些任务?
Fork / Join Framework的基类java.util.concurrent.ForkJoinPool
基本上是一个线程池,它执行的实例java.util.concurrent.ForkJoinTask
。该类ForkJoinTask
提供了fork()
和的两种方法join()
。当fork()
用于启动任务的异步执行时,该方法join()
用于等待计算结果。因此,Fork / Join框架可用于实现分而治之的算法,其中将更复杂的问题分为多个更小且更容易解决的问题。
90.是否可以使用Fork / Join-Framework在数字数组中找到最小的数字?
通过使用分而治之算法可以解决在数字数组中找到最小数字的问题。可以轻松解决的最小问题是两个数字组成的数组,因为我们可以通过一个比较直接确定两个数字中较小的一个。使用分治法将初始数组分为相等长度的两个部分,并将这两个部分提供给RecursiveTask
扩展该类的两个实例ForkJoinTask
。通过分叉这两个任务,它们将被执行,或者直接解决问题(如果数组的切片长度为2),或者再次递归将数组分为两个部分,并派生两个新的RecursiveTasks。最后,每个任务实例返回其结果(通过直接计算结果或通过等待两个子任务)。然后,根任务返回数组中的最小数字。
91.这两个类RecursiveTask
和和有RecursiveAction
什么区别?
与RecursiveTask
方法相反compute()
,RecursiveAction
不必返回值。因此,RecursiveAction
当操作直接在某些数据结构上执行而不必返回计算值时,可以使用该操作。
9,并发集合
92.什么是并发收集类?
Java Collection类是快速失败的,这意味着如果在使用迭代器遍历某个线程的同时更改了Collection,则iterator.next()将抛出ConcurrentModificationException。
93.什么是Java内存模型?
Java内存模型描述了Java编程语言中的线程如何通过内存进行交互。连同代码的单线程执行描述一起,内存模型提供了Java编程语言的语义。
94.线程安全之间有什么区别HashMap
,Hashtable
特别是在线程安全方面有什么区别?
的方法Hashtable
都是同步的。HashMap
实施情况并非如此。因此Hashtable
是线程安全的,而HashMap
不是线程安全的。因此,对于单线程应用程序,使用“较新的”HashMap
实现更为有效。
95.有一个简单的方法来创建一个任意执行的同步实例Collection
,List
还是Map
?
实用程序类Collections提供了方法synchronizedCollection(Collection)
,synchronizedList(List)
并且synchronizedMap(Map)
返回了由给定实例支持的线程安全的collection / list / map。
10,阻塞队列
96.什么是BlockingQueue?
BlockingQueue是一个Java队列,它支持以下操作:在检索和删除元素时等待队列变为非空,并在添加元素时等待队列中的空间变为可用。接口TransferQueue已添加。这是对BlockingQueue接口的改进,生产者可以在其中等待消费者接收元素。
11,原子变量
97.通过CAS操作我们了解什么?
CAS代表比较交换,并且意味着处理器提供单独的指令,仅在提供的值等于当前值时才更新寄存器的值。CAS操作可用于避免同步,因为线程可以通过向CAS操作提供其当前值和新值来尝试更新值。如果另一个线程同时更新了该值,则该线程的值不等于当前值,并且更新操作失败。然后,线程读取新值,然后重试。这样,必要的同步就可以通过乐观的旋转等待来交换。
98.哪些Java类使用CAS操作?
程序包中的SDK类java.util.concurrent.atomic
类似于AtomicInteger
或在AtomicBoolean
内部使用CAS操作来实现并发增量。
public class CounterAtomic { private AtomicLong counter = new AtomicLong(); public void increment() { counter.incrementAndGet(); } public long get() { return counter.get(); } }
12.并发随机数
99.上课的目的是java.lang.ThreadLocal
什么?
由于内存在不同线程之间共享,因此ThreadLocal
提供了一种分别存储和检索每个线程的值的方法。ThreadLocal
独立存储和检索每个线程的值的实现,这样,当线程A存储值A1且线程B将值B1存储在的同一实例中时ThreadLocal
,线程A稍后从该ThreadLocal
实例中检索值A1 ,线程B检索值B1。
100.有哪些可能的用例java.lang.ThreadLocal
?
的实例ThreadLocal
可用于在整个应用程序中传输信息,而无需在方法之间传递此信息。示例是在实例中传输安全性/登录信息ThreadLocal
,以便每种方法均可访问它。另一个用例是传输事务信息或通常在所有方法中都应可访问的对象,而无需在方法之间传递它们。