
1,谈论进程,线程和协程之间的区别
简而言之,进程是程序操作和资源分配的基本单元. 程序具有至少一个进程,并且一个进程具有至少一个线程. 该进程在执行过程中具有一个独立的内存单元,并且多个线程共享内存资源,从而减少了切换时间,从而提高了效率. 线程是进程的实体,是CPU调度和分配的基本单元,是可以独立运行的较小的基本单元. 同一进程中的多个线程可以并发执行.
2. 您知道守护程序线程吗?它和非守护进程线程有什么区别
程序运行后,jvm将等待非守护程序线程完成并关闭,但jvm将不等待守护程序线程. 守护程序线程的最典型示例是GC线程
3. 什么是多线程上下文切换
多线程上下文切换是指将CPU的控制从已经运行的线程切换到准备就绪并等待获得CPU执行权限的另一个线程的过程.
4. 如何创建两个线程?它们之间有什么区别?
通过实现java.lang.Runnable或通过扩展java.lang.Thread类. 与扩展Thread相比,实现Runnable接口可能更好. 有两个原因:
5. Thread类中的start()和run()方法有什么区别?
start()方法用于启动新创建的线程,并且start()在内部调用run()方法,这与直接调用run()方法不同. 当您调用run()方法时,它将仅在原始线程中被调用. 如果没有启动新线程,则start()方法将启动新线程.
6. 如何检测线程是否包含对象监视器
Thread类提供了一个holdLock(Object obj)方法,当且仅当对象obj的监视器由线程持有时,该方法才返回true. 请注意,这是一个静态方法,这意味着“某个“线程”是指当前线程.
7. Runnable和Callable之间的区别
Runnable接口中run()方法的返回值是空的,它所做的只是在run()方法中执行代码. Callable接口中的call()方法有一个返回值,是. 与Future和FutureTask一起使用的泛型类型可用于获取异步执行的结果.
这实际上是一个非常有用的功能,因为多线程比单线程更困难,更复杂. 原因之一是多线程充满了未知数. 是否执行了某个线程?某个线程执行了多长时间?当执行某个线程时,是否已经分配了我们期望的数据?不知道,我们所能做的就是等待此多线程任务完成. Callable + Future / FutureTask可以轻松获得多线程操作的结果多线程面试题,并且如果等待时间过长并且未获得所需的数据,则可以取消线程的任务
8. 是什么导致线程阻塞
阻塞是指挂起线程的执行以等待特定条件(例如资源就绪)的发生. 学习过操作系统的学生必须已经熟悉该操作系统. Java提供了大量支持阻塞的方法,让我们一一分析.
方法说明
睡眠()
sleep()允许以毫秒为单位指定一个时间段,这将导致线程在指定时间内进入阻塞状态,并且无法获取CPU时间. 经过指定的时间后,线程将重新进入可执行状态. 通常,sleep()用于等待资源准备就绪: 在测试发现不满足条件之后,让线程阻塞一段时间,然后重新测试直到条件满足
暂停()和恢复()
这两种方法一起使用. pause()使线程进入阻塞状态,并且不会自动恢复. 必须通过其相应的resume()进行调用,以使线程重新进入可执行状态. 通常,使用suspend()和resume()来等待另一个线程的结果: 在测试发现尚未产生结果之后,该线程被阻塞,并且在另一个线程产生结果之后,调用resume()恢复.
收益()

yield()使当前线程放弃当前分配的CPU时间,但不阻塞当前线程,也就是说,该线程仍处于可执行状态,并且可以随时重新分配CPU时间. 调用yield()的效果等同于调度程序认为该线程已执行了足够的时间转到另一个线程
等待()并通知()
这两种方法一起使用. wait()使线程进入阻塞状态. 它有两种形式. 一个允许以毫秒为单位指定时间段,而另一个则没有参数. 前者是相应的notify()线程在被调用或超过指定时间时重新进入可执行状态,而后者必须被调用以对应notify().
9. wait(),notify()和suspend(),resume()之间的区别
乍一看,它们似乎与suspend()和resume()方法没有什么不同,但是实际上它们是非常不同的. 区别的核心是,上述所有方法在阻塞时都不会释放占用的锁(如果被占用),而这对方法却相反. 上述核心差异导致一系列详细的差异.
首先,上述所有方法都属于Thread类,但该对直接属于Object类,即所有对象都具有这对方法. 乍一看,这似乎令人难以置信,但实际上这很自然,因为在释放占用的锁时该方法对被阻塞,并且该锁归任何对象所有,调用任何对象的wait()方法都会导致线程阻止,并且对象上的锁被释放. 调用任意对象的notify()方法会导致一个不受阻塞的线程,该线程是通过调用对象的wait()方法而被阻塞的线程随机选择的(但要等到实际执行锁定之后).
第二,可以在任何地方调用上述所有方法,但是必须在同步方法或块中调用这对方法. 原因也很简单. 只有同步方法或块中的同步线程或块才具有锁,然后才能释放该锁. 同样,调用此对方法的对象上的锁必须由当前线程拥有,以便可以释放该锁. 因此,必须将一对方法调用放在这样的同步方法或块中,并且该方法或块的锁定对象是调用该对方法的对象. 如果不满足此条件,则仍可以编译程序,但是在运行时将发生IllegalMonitorStateException.
wait()和notify()方法的上述特征确定它们通常与synced关键字一起使用. 将它们与操作系统进程间通信机进行比较会发现它们的相似之处: 同步方法或块提供了操作系统基本功能以外的功能,它们的执行不会受到多线程机制的干扰,这对方法是等效的到块和唤醒原语(这对方法都声明为已同步). 它们的结合使我们能够在操作系统上实现一系列精心设计的进程间通信算法(例如信号量算法),并用于解决各种复杂的线程间通信问题.
关于wait()和notify()方法的最后两点:
首先: 调用notify()方法将导致非阻塞线程从通过调用对象的wait()方法阻塞的线程中随机选择. 我们无法预测将选择哪个线程,因此在编程时,请特别注意避免由于这种不确定性而引起的问题.
第二个: 除了notify()之外,还有一个方法notifyAll(),它也可以起到类似的作用. 唯一的区别是,调用notifyAll()方法将阻止对象的wait()方法阻止的所有调用. 线程立即全部解除阻塞. 当然,只有获得锁的线程才能进入可执行状态.
谈到阻塞时,您会忍不住谈论僵局. 经过一点分析,您会发现在未指定超时期限的情况下调用suspend()方法和wait()方法都可能导致死锁. 不幸的是,Java不支持在语言级别避免死锁,我们必须小心避免在编程中死锁.
上面,我们对Java中实现线程阻塞的各种方法进行了一些分析. 我们专注于wait()和notify()方法,因为它们使用起来最强大,最灵活,但这也导致它们效率较低且更容易出错. 在实际使用中,应灵活运用各种方法,以更好地实现我们的目的.
11. 僵局的条件
1. 互斥条件: 一次只能由一个进程使用资源.
2. 请求和保留条件: 当进程由于请求资源而被阻塞时,它将保留获取的资源.
3. 无剥夺条件: 进程使用结束之前,不能强行剥夺该进程获取的资源.
4. 循环等待条件: 多个过程之间形成了一个循环等待资源的过程.
12. 为什么应该在同步块中调用wait()方法和notify()/ notifyAll()方法
这是JDK必需的. wait()方法和notify()/ notifyAll()方法必须先获取对象的锁,然后才能调用
放弃对象监视器时,wait()方法和notify()/ notifyAll()方法之间有什么区别
在放弃对象监视器时,wait()方法和notify()/ notifyAll()方法之间的区别在于,wait()方法立即释放了对象监视器,而notify()/ notifyAll()方法将等待剩余的线程在执行代码之前,不会放弃对象监视器.
13. wait()和sleep()之间的区别

关于这两个的信息已在上面进行了详细说明,以下为摘要:
14. 为什么方法wait,nofity和nofityAll不在Thread类中
一个明显的原因是JAVA提供的锁是对象级的,而不是线程级的. 每个对象都有一个锁,该锁是通过线程获得的. 如果线程需要等待一些锁定,则可以在对象中调用wait()方法. 如果在Thread类中定义了wait()方法,则线程正在等待哪个锁并不明显. 简而言之,由于wait,notify和notifyAll是锁级操作,因此它们在Object类中定义,因为锁属于该对象.
15. 如何唤醒被阻塞的线程
如果线程由于调用wait(),sleep()或join()方法而被阻塞,则可以通过引发InterruptedException来中断线程并唤醒它;如果线程遇到IO阻塞,则无能为力,因为IO由操作系统实现,并且Java代码无法直接接触操作系统.
16. 什么是多线程上下文切换
多线程上下文切换是指将CPU的控制从已经运行的线程切换到准备就绪并等待获得CPU执行权限的另一个线程的过程.
17. 同步和ReentrantLock之间的区别
Synchronized是相同的关键字,就好像ReentrantLock是一个类一样,这是两者之间的本质区别. 由于ReentrantLock是一个类,因此它提供的功能比同步功能越来越多. 它可以被继承,可以具有方法,并可以具有各种类变量. ReentrantLock比同步的可伸缩性体现在以下几点: <
(1)ReentrantLock可以设置获取锁的等待时间,以避免死锁
(2)ReentrantLock可以获得各种锁信息
(3)ReentrantLock可以灵活地实现多通道通知
此外,两者的锁定机制实际上是不同的: ReentrantLock的底部调用Unsafe的park方法进行锁定,并且同步操作应该是对象标头中的标记字.
18. 什么是FutureTask
如前所述,FutureTask代表异步计算任务. 可以将具体的Callable实现类传递到FutureTask中,该类可以等待此异步操作任务的结果获得,判断它是否已完成,取消任务和其他操作. 当然,由于FutureTask还是Runnable接口的实现类,因此FutureTask也可以放入线程池中.
19. 如果线程有运行时异常怎么办?
如果未捕获到此异常,则线程停止执行. 另一个重要的一点是: 如果该线程拥有某个对象监视器,那么该对象监视器将立即释放
20. Java中有哪些类型的锁
21. 如何在两个线程之间共享数据
只需程之间共享对象,然后通过wait / notify / notifyAll,await / signal / signalAll唤醒并等待,例如,BlockingQueue设计为程之间共享数据
22. 如何正确使用wait()?使用是或否?
应该在循环中调用wait()方法,因为当线程让CPU开始执行时,可能还没有满足其他条件,因此在进行处理之前,最好先检查条件是否满足环. 以下是使用wait和notify方法的标准代码:
synchronized (obj) {
while (condition does not hold)
obj.wait(); // (Releases lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}

23. 什么是线程局部变量ThreadLocal
线程局部变量是限于内部线程的变量,它们属于线程本身,并且不在多个线程之间共享. Java提供ThreadLocal类来支持线程局部变量,这是一种实现线程安全的方法. 但是在管理环境(例如Web服务器)中使用线程局部变量时要小心. 在这种情况下,辅助线程的生命周期比任何应用程序变量的生命周期都要长. 工作完成后,如果不释放任何线程局部变量,则存在Java应用程序中内存泄漏的风险.
24. ThreadLoal的作用是什么?
简单地说,ThreadLocal是一种将空间交换时间的方法. 每个线程都维护一个ThreadLocal.
25. 生产者消费者模型的作用是什么?
(1)通过平衡生产者的生产能力和消费者的消费能力来提高整个系统的运行效率,这是生产者消费者模型的最重要作用
(2)去耦,这是生产者-消费者模型的副作用. 脱钩意味着生产者和消费者之间的联系减少了,联系越少,他们就越能独立发展而不会受到相互制约.
26. 编写生产者-消费者队列
可以通过阻塞队列或等待通知来实现.
使用阻塞队列来实现
//消费者
public class Producer implements Runnable{
private final BlockingQueue<Integer> queue;
public Producer(BlockingQueue q){
this.queue=q;
}
@Override
public void run() {
try {
while (true){
Thread.sleep(1000);//模拟耗时
queue.put(produce());
}
}catch (InterruptedException e){
}
}
private int produce() {
int n=new Random().nextInt(10000);
System.out.println("Thread:" + Thread.currentThread().getId() + " produce:" + n);
return n;
}
}
//消费者
public class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
public Consumer(BlockingQueue q){
this.queue=q;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(2000);//模拟耗时
consume(queue.take());
}catch (InterruptedException e){
}
}
}
private void consume(Integer n) {
System.out.println("Thread:" + Thread.currentThread().getId() + " consume:" + n);
}
}
//测试
public class Main {
public static void main(String[] args) {
BlockingQueue<Integer> queue=new ArrayBlockingQueue<Integer>(100);
Producer p=new Producer(queue);
Consumer c1=new Consumer(queue);
Consumer c2=new Consumer(queue);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
使用等待通知来实现
这种方法应该是最经典的,因此在这里我将不作解释
27. 如果在提交任务时线程池队列已满,那会发生什么?
如果使用LinkedBlockingQueue,即×××队列,没关系,继续将任务添加到阻塞队列中等待执行,因为LinkedBlockingQueue几乎可以看作是无限队列,因此可以无限期地存储任务;如果使用“有限队列”(例如ArrayBlockingQueue),则该任务将首先添加到ArrayBlockingQueue. 当ArrayBlockingQueue已满时,它将使用拒绝策略RejectedExecutionHandler来处理完整任务. 默认值为AbortPolicy.
28. 为什么要使用线程池
避免频繁创建和销毁线程以实现线程对象的重用. 此外,使用线程池还可以根据项目灵活地控制并发数.
29. Java中使用的线程调度算法是什么
抢先. 线程用完CPU后,操作系统会根据线程优先级和线程饥饿等数据计算总优先级,并将下一个时间片分配给线程以供执行.
30. Thread.sleep(0)的作用是什么
由于Java使用抢占式线程调度算法,因此可能经常发生线程经常获得CPU控制的情况. 为了允许某些优先级较低的线程也获得CPU控制,可以使用Thread .sleep(0)手动触发一个操作,该操作由操作系统分配时间片,这也是平衡CPU控制的操作.
31. 什么是CAS
CAS,全名是Compare and Swap,即比较替换. 假设有三个操作数: 内存值V,旧的期望值A和要修改的值B. 当且仅当期望值A和内存值V相同时,内存值将被修改为B并返回true,否则执行无操作并返回false. 当然,CAS必须与可变变量配合使用,以确保每次获取的变量都是主存储器中的最新值,否则旧的期望值A将始终是对于某个线程不会改变的值A,例如只要CAS操作失败,就永远不会成功

32. 什么是乐观锁定和悲观锁定
乐观锁: 乐观锁认为竞争并不总是发生的,因此它不需要持有该锁,并且会将这两个动作作为原子操作进行比较-替换来修改内存中的变量. 如果失败,则表明存在冲突. ,那么应该有相应的重试逻辑.
悲观锁: 悲观锁认为竞争总是会发生,因此,每次对资源进行操作时,它都会持有排他锁,就像同步锁一样. 资源.
33. 什么是ConcurrentHashMap的并发性?
ConcurrentHashMap并发性是段的大小,默认为16,这意味着最多16个线程可以同时操作ConcurrentHashMap,这也是ConcurrentHashMap到Hashtable的最大优势,无论如何,Hashtable可以通过两个线程获得数据是否在哈希表中?
34. ConcurrentHashMap的工作原理
ConcurrentHashMap的实现原理在jdk 1.6和jdk 1.8中有所不同.
jdk 1.6:
ConcurrentHashMap是线程安全的,但是与Hashtablea相比,实现线程安全的方法有所不同. Hashtable锁定哈希表结构并且正在阻塞. 当一个线程拥有此锁时,其他线程必须阻塞并等待它释放该锁. ConcurrentHashMap是一个单独的锁定方法. 它不会锁定整个哈希表,而是一个本地锁定. 也就是说,当一个线程持有此本地锁时,它不会影响其他线程对哈希表其他部分的访问.
具体实现: ConcurrentHashMap内部有一个段
jdk 1.8
在jdk 8中,ConcurrentHashMap不再使用段分隔锁,而是使用乐观锁CAS算法来实现同步问题,但最底层仍是“数组+链表->红黑树”的实现.
37,CyclicBarrier和CountDownLatch之间的区别
这两个类非常相似. 两者都在java.util.concurrent下,可用于指示代码运行到特定点. 两者之间的区别是:
39. Java线程中的++运算符安全吗?
不是线程安全的操作. 它涉及多个指令,例如读取变量的值,将其增加然后将其存储回内存中. 在此过程中可能会有多个线程交叉
40. 多线程开发有哪些好的做法?
1. 我可以创建一个数组吗?
可以用Java创建volatile类型的数组,但这只是对数组的引用多线程面试题,而不是整个数组. 如果更改引用所指向的数组,则该数组将受到volatile的保护,但是如果多个线程同时更改该数组的元素,则volatile标识符将不会发挥以前的保护作用.
2. 挥发物可以将非原子操作转变为原子操作吗?
一个典型的例子是类中long类型的成员变量. 如果您知道成员变量将被多个线程访问,例如计数器,价格等,则最好将其设置为volatile. 为什么?因为在Java中读取长类型变量不是原子的,所以需要将其分为两个步骤. 如果一个线程正在修改long变量的值,则另一个线程可能只会看到该值的一半(前32位). 但是读写可变的long或double变量是原子的.
一种实践是使用volatile修改long和double变量,以便可以按原子类型对其进行读写. double和long均为64位宽,因此将两种类型的读取分为两部分,第一次读取前32位,然后读取其余的32位. 这个过程不是原子的. 但是在Java中,易读的long或double变量的读写是原子的. 易失性修复特征的另一个作用是提供内存屏障(内存屏障),例如,分布式框架中的应用程序. 简而言之,当您编写一个volatile变量时,Java内存模型将插入一个写屏障(写屏障),而在读取一个volatile变量之前,将插入一个读屏障(读屏障). 这意味着当您编写一个volatile字段时,可以确保任何线程都可以看到您编写的值. 同时,在写入之前,您还可以确保所有线程都可以看到任何值更新,因为内存屏障会将所有其他写入的值更新到缓存中.
3. 可变变量提供什么保证?
volatile具有两个主要功能: 1.避免指令重新排列. 2.可见性保证. 例如,JVM或JIT将对语句重新排序以获得更好的性能,但是即使没有同步块也将使用易失性类型变量. 在这种情况下,该赋值将不会与其他语句重新排序. 易失性可确保事前发生,从而确保一个线程的修改对其他线程可见. 在某些情况下,volatile也可以提供原子性,例如读取64位数据类型,例如long和double都不是原子的(低32位和高32位),而volatile类型的double和long则是原子的.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-188383-1.html
国家连年扩招