并发编程网Java并发面试题

部分答案参考了网络上的回答,具体可以参考书籍:
《Java并发编程实战》
《深入理解Java虚拟机》
及JDK源码

多线程

1. java中有几种方法可以实现一个线程?

Java中线程的运行最终都是通过Thread类,具体实现上可以有三种方式:
1.直接继承Thread类,并重写run方法
2.实现Runable接口,并通过Thread来调用
3.使用线程池ThreadPoolExecutor,直接向线程池提交任务
实际仍然构建了Runable或Callabe接口并提交到线程池的Thread来运行,因为Java只能单继承,在继承Thread后就不能继承其他类了,而实现Runnable接口后还可以实现其它接口,继承类,比较来说更灵活

2. 如何停止一个正在运行的线程?

Java线程虽然提供了stop和suspend方法,但是不推荐使用来停止线程。
stop方法天生不安全,会立刻停止线程的运行,因此会导致数据出现不一致的状态,资源得不到释放等;
suspend方法会阻塞线程,但是不会释放锁持有的锁,从而可能会导致出现死锁。
Thread.interrupt方法可以中断线程执行,并抛出中断异常供上层处理。
比较好的方法是使用标志变量来控制线程的运行,通过标志位来控制线程是继续运行还是挂起或者清理退出。挂起可以使用wait方法,并通过notify或notifyAll来重启

3. notify()和notifyAll()有什么区别?

当一个线程进入wait状态后,必需等待其它线程通过notify或notifyAll唤醒。
notify方法仅会唤醒在等待对象上的一个线程并获得锁,notifyAll则会唤醒所有等待对象上的线程重新竞争锁。
使用notify需要注意唤醒的为随机一个线程,执行后如果没有再唤醒则可能会导致死锁;
而notifyAll等待线程都已经被唤醒,重新等待获取锁,不会出现死锁情况。

4. sleep()和 wait()有什么区别?

两个方法来自不同的类,sleep方法属于Thread类,wait方法属于Object类。
sleep为静态方法,会让当前线程进入休眠,而wait为成员方法,会让调用线程进入等待。
sleep必需传入一个时间,让当前线程进入休眠状态,而wait可以指定也可以不指定等待的时间,线程进入WAIT状态。
虽然两个方法都可以暂停线程的运行,但sleep不会释放所持有的锁,线程恢复后可直接运行;wait则会释放持有的锁,在恢复后需要重新竞争获取锁。

5. 什么是Daemon线程?它有什么意义?

Daemon线程即后台线程或守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这个线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。
反过来说,只要有任何非后台线程还在运行,程序就不会终止。必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行finally子句的情况下就会终止其run()方法。

6. java如何实现多线程之间的通讯和协作?

利用同步和互斥来解决多线程之间的通讯和协作;可以说资源互斥、协调竞争是要解决的因,而同步是竞争协调的果。
通过synchronized/wait/notify/notifyAll/Lock/Condition来控制对共享资源的互斥访问,线程等待和唤醒。
利用提供的同步工具如:CyclicBarrier/Semaphore/Countdownbatch。

1. 什么是可重入锁(ReentrantLock)?

当一个线程请求其它线程持有的锁时会被阻塞,可重入是指一个线程在请求获取自身占有的锁时可以成功,即对同一个锁保护的区域可以重复进入。
JVM内部提供一个针对该锁的线程重入计数,重入时加一,释放时减一。
JDK提供的synchronized和ReentrantLock都是可重入的。

2. 当一个线程进入某个对象的一个synchronized的实例方法后,其它线程是否可进入此对象的其它方法?

其它线程是否可进入对象的其它方法取决与其它方法是否需要请求与改方法相同的锁,一般来说可以进入非synchronized方法。

3. synchronized和java.util.concurrent.locks.Lock的异同?

Lock几乎可以实现synchronized所有功能并额外提供其它高级功能,如:定时锁,可中断锁等。
synchronized为Java语言关键字,由JVM内部提供对同步的支持,性能可以随JVM优化而自动提高。Lock则为Java并发包中提供的一个锁实现接口。
synchronized获取的锁随着同步块自动释放,而Lock获取的锁需要在finnaly块中保证释放,否则可能造成死锁。

4. 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

乐观和悲观是指对多并发时是否会出现锁竞争的态度。
乐观锁总是假设并发不会出现竞争来运行程序,不需要先获取锁,当出现竞争且失败时可进行重试。悲观锁则假设一定会出现竞争,需要先获取锁程序才能继续运行。
乐观锁在中低并发和多读少写的情况下可获得更高性能,因为不需要获取锁并且出现冲突的可能性低;
而在高并发情况下出现竞争冲突的概率变高,使用悲观锁可获得更好的性能。
乐观锁的实现:通常使用volatile+CAS机制实现,设计上使用版本号或时间戳进行冲突检测。
悲观锁实现主要有:数据库行锁、表锁,读写锁等

并发框架

1. SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap是针对HashMap的同步包装器,通过对读写方法加synchronized内部锁来实现同步。会对整个对象同步,读写操作都需要加锁,相当于只能顺序执行;迭代时修改会抛出异常
ConcurrentHashMap为JDK并发包提供的高并发HashMap实现,可支持多并发读和一定量并发写。内部读操作不加锁,写操作通过CAS机制和对细粒度的桶加锁代替对整个对象加锁,因此可以获得更好的性能。支持迭代时另一线程尝试修改而不抛出异常。

2. CopyOnWriteArrayList可以用于什么应用场景?

适用于多读少写的场景。CopyOnWriteArrayList内部实现对读操作不加锁,而对写操作会首先通过数组复制获得当前对象的一份拷贝供修改,修改结束后通过CAS操作更新老的引用指向修改后的拷贝。但需要注意数组太大造成的数组复制性能降低。

线程安全

1. 什么叫线程安全?servlet是线程安全吗?

简单来说,在多线程并发调用情况下,行为仍然表现正常。
引用《Java并发编程实战》:

当多个线程访问一个类时,如果不考虑这些线程在运行环境下的调度和交替执行,并且不需要额外的同步及在
调用方代码不需要做其它协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。

servlet不是线程安全的。servlet在运行时是单例模式因此对同一个servlet的多个并发请求会由同一个servlet实例处理。
如果servlet中定义了共享的变量而不采用同步保证则会出现并发问题。

2. 同步有几种实现方法?

1.使用synchronized同步方法;2.使用synchronized同步代码块;3.使用volatile关键字修饰特殊变量;
4.使用ReentrantLock可重入锁 5.使用ThreadLocal线程绑定变量

3. volatile有什么用?能否用一句话说明下volatile的应用场景?

volatile可以保证变量的可见性,即任何线程对变量的修改其它线程可立刻看到,JVM内强制刷新工作内存值;
另外volatile在JVM形成内存屏障,禁止指令重排序。
代码编译后形成一个寄存器加0空操作,使得CPU缓存刷新回内存同时使其它CPU缓存无效化需要重新加载,实现修改立即可见。而修改写回内存代表前面的操作已完成,使得指令重排不能越过。
volatile适用于作为共享标志位变量,对变量值修改不依赖于原值的场景。

4. 请说明下java的内存模型及其工作流程。

Java内存模型即JMM是JVM规范定义用来屏蔽操作系统底层硬件差异的内存设计模型。
JMM主要目标是定义程序中共享变量的访问规则,规定所有共享变量存储在主内存,每个线程又有自己的工作内存,工作内存保存了主内存中本线程用到变量的副本。线程对变量的操作只能在工作内存进行,不能直接操作主内存,线程间工作内存也不能相互访问。
JMM中定义了几种对主内存和工作内存的操作及执行规范来进行主内存和工作内存数据的交互。线程在对一个变量使用use前必需要先经过对主内存的读取read,并加载load到工作内存,而在修改后写回主内存也要先进过对工作内存变量进行存储store然后写入write主内存。

5. 为什么代码会重排序?

代码重排序是为了提供程序在运行时的性能,在Java程序中会出现编译期代码重排序、JVM执行时重排序和CPU指令执行重排序。使用synchronized关键字、volatile关键字和Lock可以在内存形成屏障来禁止指令重排序。

题目地址

你可能感兴趣的:(并发编程网Java并发面试题)