Java多线程Java面试题
Posted 北芳科技
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程Java面试题相关的知识,希望对你有一定的参考价值。
Java多线程【Java面试题】
一、创建线程的方式?
1、继承Thread类创建
通过继承Thread并且重写其run(),run方法中即线程执行任务。创建后的子类通过调用 start() 方法即可执行线程方法。
通过继承Thread实现的线程类,多个线程间无法共享线程类的实例变量。(需要创建不同Thread对象,自然不共享)
**
* 通过继承Thread实现线程
*/
public class ThreadTest extends Thread
private int i = 0 ;
@Override
public void run()
for(;i<50;i++)
System.out.println(Thread.currentThread().getName() + " is running " + i );
public static void main(String[] args)
for(int j=0;j<50;j++)
if(j=20)
new ThreadTest().start() ;
new ThreadTest().start() ;
2、通过Runnable接口创建线程类
该方法需要先 定义一个类实现Runnable接口,
并重写该接口的 run() 方法,此run方法是线程执行体。
接着创建 Runnable实现类的对象,作为创建Thread对象的参数target,此Thread对象才是真正的线程对象。
通过实现Runnable接口的线程类,是互相共享资源的。
/**
* 通过实现Runnable接口实现的线程类
*/
public class RunnableTest implements Runnable
private int i ;
@Override
public void run()
for(;i<50;i++)
System.out.println(Thread.currentThread().getName() + " -- " + i);
public static void main(String[] args)
for(int i=0;i<100;i++)
System.out.println(Thread.currentThread().getName() + " -- " + i);
if(i==20)
RunnableTest runnableTest = new RunnableTest() ;
new Thread(runnableTest,"线程1").start() ;
new Thread(runnableTest,"线程2").start() ;
3、使用Callable和Future创建线程
从继承Thread
类和实现Runnable
接口可以看出,上述两种方法都不能有返回值,且不能声明抛出异常。
而Callable
接口则实现了此两点,Callable
接口如同Runable
接口的升级版,其提供的call()方法将作为线程的执行体,同时允许有返回值。
但是Callable
对象不能直接作为Thread
对象的target
,因为Callable
接口是 Java 5
新增的接口,不是Runnable
接口的子接口。
对于这个问题的解决方案,就引入 Future接口,此接口可以接受call() 的返回值,RunnableFuture
接口是Future接口和Runnable接口的子接口,可以作为Thread对象的target 。
并且, Future 接口提供了一个实现类:FutureTask
。
FutureTask
实现了RunnableFuture
接口,可以作为 Thread
对象的target
。
public class CallableTest
public static void main(String[] args)
CallableTest callableTest = new CallableTest() ;
//因为Callable接口是函数式接口,可以使用Lambda表达式
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->
int i = 0 ;
for(;i<100;i++)
System.out.println(Thread.currentThread().getName() + "的循环变量i的值 :" + i);
return i;
);
for(int i=0;i<100;i++)
System.out.println(Thread.currentThread().getName()+" 的循环变量i : + i");
if(i==20)
new Thread(task,"有返回值的线程").start();
try
System.out.println("子线程返回值 : " + task.get());
catch (Exception e)
e.printStackTrace();
二、线程是状态
①初始(NEW
):新创建了一个线程对象,但还没有调用start()方法。
②运行(RUNNABLE
):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。
该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。
就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。
③阻塞(BLOCKED
):表线程阻塞于锁。
④等待(WAITING
):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
⑤超时等待(TIME_WAITING
):该状态不同于WAITING,它可以在指定的时间内自行返回。
⑥终止(TERMINATED
):表示该线程已经执行完毕。
三、Java 多线程加锁的方式
-
synchronized
关键字 -
Java.util.concurrent
包中的lock
接口和ReentrantLock
实现类
这两种方式实现加锁。
1)Lock
不是Java
语言内置的,synchronized
是Java语言的关键字,因此是内置特性。
Lock是一个类,通过这个类可以实现同步访问;
2)Lock
和synchronized
有一点非常大的不同,采用synchronized
不需要用户去手动释放锁,当synchronized
方法或者synchronized
代码块执行完之后,系统会自动让线程释放对锁的占用;
而Lock则
必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
总结来说,Lock
和synchronized
有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized
在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生,而Lock
在发生异常时,如果没有主动通过unLock()
去释放锁,则很可能造成死锁现象,因此使用Lock
时需要在finally
块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized
却不行,使用synchronized
时,等待的线程会一直等待下去,不能够响应中断; (I/O
和Synchronized
都能相应中断,即不需要处理interruptionException
异常)
4)通过Lock可以知道有没有成功获取锁,而synchronized
却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
所以说,在具体使用时要根据适当情况选择。
1.可重入锁
如果锁具备可重入性,则称作为可重入锁。
像synchronized
和ReentrantLock
都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:
基于线程的分配,而不是基于方法调用的分配。
举个简单的例子,当一个线程执行到某个synchronized
方法时,比如说method1
,而在method1
中会调用另外一个synchronized
方法method2
,此时线程不必重新去申请锁,而是可以直接执行方法method2
。
class MyClass
public synchronized void method1()
method2();
public synchronized void method2()
上述代码中的两个方法method1
和method2
都用synchronized
修饰了,假如某一时刻,
线程A执行到了method1
,此时线程A获取了这个对象的锁,而由于method2
也是synchronized
方法,假如synchronized
不具备可重入性,此时线程A需要重新申请锁。
但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
2.可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized
就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
在前面演示lockInterruptibly()
的用法时已经体现了Lock的可中断性。
3.公平锁
公平锁即尽量以请求锁的顺序来获取锁。
比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。
这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized
就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock
和ReentrantReadWriteLock
,它默认情况下是非公平锁,但是可以设置为公平锁。
另外在ReentrantLock类中定义了很多方法,比如:
isFair()
//判断锁是否是公平锁
isLocked()
//判断锁是否被任何线程获取了
isHeldByCurrentThread()
//判断锁是否被当前线程获取了
hasQueuedThreads()
//判断是否有线程在等待该锁
在ReentrantReadWriteLock
中也有类似的方法,同样也可以设置为公平锁和非公平锁。
不过要记住,ReentrantReadWriteLock
并未实现Lock接口,它实现的是ReadWriteLock接口。
4.读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock
就是读写锁,它是一个接口,ReentrantReadWriteLock
实现了这个接口。
可以通过readLock()
获取读锁,通过writeLock()获取写锁。
ReadWriteLock, ReadWriteLock
也是一个接口,在它里面只定义了两个方法:
四、DeplayQueue延时无界阻塞队列
在谈到DelayQueue
的使用和原理的时候,我们首先介绍一下DelayQueue
,
DelayQueue
是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。
该队列的头部是延迟期满后保存时间最长的Delayed
元素。
DelayQueue
阻塞队列在我们系统开发中也常常会用到,
例如:缓存系统的设计,缓存中的对象,超过了空闲时间,需要从缓存中移出;
任务调度系统,能够准确的把握任务的执行时间。
我们可能需要通过线程处理很多时间上要求很严格的数据,如果使用普通的线程,
我们就需要遍历所有的对象,一个一个的检查看数据是否过期等,
首先这样在执行上的效率不会太高,其次就是这种设计的风格也大大的影响了数据的精度。
一个需要12:00点执行的任务可能12:01才执行,
这样对数据要求很高的系统有更大的弊端。由此我们可以使用DelayQueue。
下面将会对DelayQueue做一个介绍,然后举个例子。
并且提供一个Delayed接口的实现和Sample
代码。
DelayQueue
是一个BlockingQueue
,其特化的参数是Delayed
。
(不了解BlockingQueue
的同学,先去了解BlockingQueue
再看本文)
Delayed
扩展了Comparable
接口,比较的基准为延时的时间值,Delayed
接口的实现类getDelay
的返回值应为固定值(final)
。
DelayQueue
内部是使用PriorityQueue
实现的。
DelayQueue=BlockingQueue+PriorityQueue+Delayed
DelayQueue
的关键元素BlockingQueue
、PriorityQueue
、Delayed
。
可以这么说,DelayQueue
是一个使用优先队列(PriorityQueue
)实现的BlockingQueue
,优先队列的比较基准值是时间。
他们的基本定义如下
public interface Comparable<T>
public int compareTo(T o);
public interface Delayed extends Comparable<Delayed>
long getDelay(TimeUnit unit);
public class DelayQueue<E extends Delayed> implements BlockingQueue<E>
private final PriorityQueue<E> q = new PriorityQueue<E>();
DelayQueue 内部的实现使用了一个优先队列。
当调用 DelayQueue 的 offer 方法时,把 Delayed 对象加入到优先队列 q 中。如下:
public boolean offer(E e)
final ReentrantLock lock = this.lock;
lock.lock();
try
E first = q.peek();
q.offer(e);
if (first == null || e.compareTo(first) < 0)
available.signalAll();
return true;
finally
lock.unlock();
DelayQueue 的 take 方法,把优先队列 q 的 first 拿出来(peek),如果没有达到延时阀值,则进行 await处理。如下:
public E take() throws InterruptedException
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try
for (; ; )
E first = q.peek();
if (first == null)
available.await();
else
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay > 0)
long tl = available.awaitNanos(delay);
else
E x = q.poll();
assert x != null;
if (q.size() != 0)
available.signalAll(); //wake up other takers return x;
finally
lock.unlock();
● DelayQueue 实例应用
Ps:为了具有调用行为,存放到 DelayDeque
的元素必须继承 Delayed
接口。Delayed
接口使对象成为延迟对象,它使存放在 DelayQueue
类中的对象具有了激活日期。
该接口强制执行下列两个方法。
一下将使用 Delay 做一个缓存的实现。
其中共包括三个类Pair、DelayItem、Cache
● Pair 类:
public class Pair<K, V>
public K first;
public V second;
public Pair()
public Pair(K first, V second)
this.first = first;
this.second = second;
以下是对 Delay 接口的实现:
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class DelayItem<T> implements Delayed
/**
* Base of nanosecond timings, to avoid wrapping
*/
private static final long NANO_ORIGIN = System.nanoTime();
/**
* Returns nanosecond time offset by origin
*/
final static long now()
return System.nanoTime() - NANO_ORIGIN;
/**
* Sequence number to break scheduling ties, and in turn to guarantee FIFO order among tied
* entries.
*/
private static final AtomicLong sequencer = new AtomicLong(0);
/**
* Sequence number to break ties FIFO
*/
private final long sequenceNumber;
/**
* The time the task is enabled to execute in nanoTime units
*/
private final long time;
private final T item;
public DelayItem(T submit, long timeout)
this.time = now() + timeout;
this.item = submit;
this.sequenceNumber = sequencer.getAndIncrement();
public T getItem()
return this.item;
public long getDelay(TimeUnit unit)
long d = unit.convert(time - now(), TimeUnit.NANOSECONDS); return d;
public int compareTo(Delayed other)
if (other == this) // compare zero ONLY if same object return 0;
if (other instanceof DelayItem)
DelayItem x = (DelayItem) other;
long diff = time - x.time;
if (diff < 0) return -1;
else if (diff > 0) return 1;
else if (sequenceNumber < x.sequenceNumber) return -1;
else
return 1;
long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS));
return (d == 0) ?0 :((d < 0) ?-1 :1);
以下是 Cache 的实现,包括了 put 和 get 方法
import javafx.util.Pair;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Cache<K, V>
private static final Logger LOG = Logger.getLogger(Cache.class.getName());
private ConcurrentMap<K, V> cacheObjMap = new ConcurrentHashMap<K, V>();
private DelayQueue<DelayItem<Pair<K, V>>> q = new DelayQueue<DelayItem<Pair<K, V>>>();
private Thread daemonThread;
public Cache()
Runnable daemonTask = new Runnable()
public void run()
daemonCheck();
;
daemonThread = new Thread(daemonTask);
daemonThread.setDaemon(true);
daemonThread.setName("Cache Daemon");
daemonThread.start();
private void daemonC以上是关于Java多线程Java面试题的主要内容,如果未能解决你的问题,请参考以下文章