多线程内容比较多,今天写完了第四篇,后边还有五。
1. ReentrantLock 、synchronized和volatile比较
(1)ReentrantLock是一种锁,ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。new ReentrantLock(true)可以实现公平锁(按照等待时间越长越优先获得锁权限),如果传入false表示非公平锁(性能更好)。ReentrantLock可以通过lockInterruptibly()响应中断。tryLock()可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。
(2)synchronized用来修饰方法或者代码块的,是一种锁机制,增加了synchronized方法可以防止多线程同时执行该方法,如果是两个实例(同一个实例可以避免)的情况下的同一个synchronized方法被执行,实际上是还能够被同时执行的,如果需要控制就再增加上static就可以控制了
(3)volatile用来修饰变量的,在多线程中同步变量。表示如果要使用该内容,需要直接从内存中取值,而不能从缓存中取,避免了某些情况下的线程冲突
2. 在Java中CycliBarriar和CountdownLatch有什么区别?
(1)CountdownLatch: 一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。是并发包中提供的一个可用于控制多个线程同时开始某个动作的类,其采用的方法为减少计数的方式,当计数减至零时位于latch.Await()后的代码才会被执行,CountDownLatch是减计数方式,计数==0时释放所有等待的线程;CountDownLatch当计数到0时,计数无法被重置。
(2)CycliBarriar字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。 即:N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。CyclicBarrier是当await的数量到达了设置的数量的时候,才会继续往下面执行,CyclicBarrier计数达到指定值时,计数置为0重新开始。
(3)对于CountDownLatch来说,重点是那个“一个线程”,是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
(4)CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
CountDownLatch测试代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class TestCountDownLatch {
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(5);
Executor executor = Executors.newFixedThreadPool(5);
//测试阻塞其他线程
new Thread(new MyRunnable(countDownLatch)).start();
//为了测试效果进行线程休眠
Thread.sleep(1000);
for (int i = 1; i <= 5; i++) {
countDownLatch.countDown();
System.out.println("第" + i + "调用countDown方法结束");
//为了测试效果进行线程休眠
Thread.sleep(1000);
}
/*
*测试阻塞主线程
*/
for (int i = 1; i <= 5; i++) {
new Thread(new MyRunnable1(countDownLatch, i + "")).start();
Thread.sleep(1000);
}
try {
System.out.println("主线程阻塞");
countDownLatch.await();
System.out.println("主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyRunnable implements Runnable {
CountDownLatch countDownLatch;
public MyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println("进入线程,即将进入阻塞状态");
//调用await进行线程阻塞
countDownLatch.await();
System.out.println("线程进行执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class MyRunnable1 implements Runnable {
private CountDownLatch countDownLatch;
private String mark;
public MyRunnable1(CountDownLatch countDownLatch, String mark) {
super();
this.countDownLatch = countDownLatch;
this.mark = mark;
}
@Override
public void run() {
System.out.println(mark + "号线程开始");
try {
//使线程休眠,看到更好的测试效果
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(mark + "号线程结束");
//调用CountDownLatch的countDown方法进行次数减1
countDownLatch.countDown();
}
}
public CountDownLatch getCountDownLatch() {
return countDownLatch;
}
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public String getMark() {
return mark;
}
public void setMark(String mark) {
this.mark = mark;
}
}
}
//输出
进入线程,即将进入阻塞状态
第1调用countDown方法结束
第2调用countDown方法结束
第3调用countDown方法结束
第4调用countDown方法结束
第5调用countDown方法结束
线程进行执行...
1号线程开始
2号线程开始
3号线程开始
4号线程开始
5号线程开始
主线程阻塞
主线程继续执行
CyclicBarrier测试代码
import java.util.concurrent.*;
public class TestCyclicBarrier {
static final Integer NUM = 5;
public static void main(String[] args) throws InterruptedException {
//实例CyclicBarrier对象
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM);//实例化一个固定大小线程池
ExecutorService executor = Executors.newFixedThreadPool(NUM);
for (int i = 1; i <= NUM; i++) {
//执行线程
executor.execute(new MyRunnale2(cyclicBarrier, i + "号"));
//为了更好的效果,休眠一秒
Thread.sleep(1000);
}
System.out.println("指令通知完成");
//执行完毕需要关闭,否则主线程还在这卡着不关闭
executor.shutdown();
}
static class MyRunnale2 implements Runnable {
private CyclicBarrier cyclicBarrier;
private String mark;
public MyRunnale2(CyclicBarrier cyclicBarrier, String mark) {
super();
this.cyclicBarrier = cyclicBarrier;
this.mark = mark;
}
@Override
public void run() {
System.out.println(mark + "进入线程,线程阻塞中...");
try {
//barrier的await方法,在所有参与者都已经在此barrier上调用await方法之前,将一直等待。
cyclicBarrier.await();
System.out.println(mark + "进入开始执行...");
Thread.sleep(2000);//为了看到更好的效果,线程阻塞两秒
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(mark + "线程阻塞结束,继续执行...");
}
public CyclicBarrier getCyclicBarrier() {
return cyclicBarrier;
}
public void setCyclicBarrier(CyclicBarrier cyclicBarrier){
this.cyclicBarrier = cyclicBarrier;
}
}
}
//输出
1号进入线程,线程阻塞中...
2号进入线程,线程阻塞中...
3号进入线程,线程阻塞中...
4号进入线程,线程阻塞中...
5号进入线程,线程阻塞中...
5号进入开始执行...
1号进入开始执行...
2号进入开始执行...
3号进入开始执行...
4号进入开始执行...
指令通知完成
3号线程阻塞结束,继续执行...
2号线程阻塞结束,继续执行...
4号线程阻塞结束,继续执行...
5号线程阻塞结束,继续执行...
1号线程阻塞结束,继续执行...
3. CopyOnWriteArrayList可以用于什么应用场景?
(1)ArrayList在同时遍历和修改这个列表时,会抛出 ConcurrentModificationException。
(2)CopyOnWriteArrayList由于是copy出来的,因此不会出现这个错误。
(3)写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致gc。
(4)不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set操作后,读取到数据可能还是旧的,虽然 CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求。
(5)读写分离,读和写分开;最终一致性;使用另外开辟空间的思路,来解决并发冲突
4. Java中invokeAndWait 和 invokeLater有什么区别?
这个似乎没啥用,为啥问这个?
invokeAndWait()方法请求事件派发线程对组件进行相应更新,需要等待执行完成。
invokeLater()方法是异步调用更新组件的。
5. 多线程中的忙循环是什么?
忙循环就是用户循环让一个线程等待,不像传统方法使用wait()、sleep()或yield(),它们都放弃CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
按照自己的理解写了个忙循环
public class TestBusyWait {
public static int wait = 3;
public static void main(String[] args) throws Exception{
new Thread(new MyRunnable()).start();
for (int i = 0; i < 3; i++) {
new Thread(new MyRunnable1()).start();
}
Thread.sleep(2000);
}
static class MyRunnable implements Runnable {
public MyRunnable() {
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"空循环开始");
while (wait != 0) {
}
System.out.println(Thread.currentThread().getName()+"空循环完成");
}
}
static class MyRunnable1 implements Runnable {
public MyRunnable1() {
}
@Override
public void run() {
wait--;
try {
System.out.println(Thread.currentThread().getName()+"开始了");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"完成了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6. 怎么检测一个线程是否拥有锁?
在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。
public class TestHoldsLock {
static Object o = new Object();
public static synchronized void method1() {
System.out.println("before method1 Thread.holdsLock(o)=="+Thread.holdsLock(o));
synchronized (o) {
System.out.println("runing method1 Thread.holdsLock(o)=="+Thread.holdsLock(o));
}
System.out.println("after method1 Thread.holdsLock(o)=="+Thread.holdsLock(o));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("main Thread.holdsLock(o)=="+Thread.holdsLock(o));
TestHoldsLock test = new TestHoldsLock();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
}
}
//输出
main Thread.holdsLock(o)==false
before method1 Thread.holdsLock(o)==false
runing method1 Thread.holdsLock(o)==true
after method1 Thread.holdsLock(o)==false
7. 死锁的四个必要条件?
(1)互斥条件:一个资源每次只能被一个进程使用;
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
8. 什么是线程池,如何使用?
(1)什么是线程池:java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。实际上是对多个线程统一管理,避免了多次创建销毁操作,可以控制同时执行的最大线程数,防止执行过多式服务卡死。
(2)Executors工厂类可以创建多种不同的线程池
(3)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(4)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
(5)newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
(6)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorTest {
public static void main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool= Executors.newSingleThreadExecutor();
//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
Thread t1=new MyThread();
Thread t2=new MyThread();
Thread t3=new MyThread();
Thread t4=new MyThread();
Thread t5=new MyThread();
//将线程放到池中执行;
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" started");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end");
}
}
}
//输出
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
//如果修改为
ExecutorService pool= Executors.newFixedThreadPool(2);
//可以看到下面输出同时只有2个在执行
pool-1-thread-1 started
pool-1-thread-2 started
pool-1-thread-1 end
pool-1-thread-2 end
pool-1-thread-1 started
pool-1-thread-2 started
pool-1-thread-2 end
pool-1-thread-1 end
pool-1-thread-2 started
pool-1-thread-2 end
//ScheduledThreadPoolExecutor测试类,定时执行任务
public class ScheduledThreadExecutorTest{
public static void main(String[] args) {
//下面这样写也可以,也可以自己new
//ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
ScheduledThreadPoolExecutor exec =new ScheduledThreadPoolExecutor(2);
exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间执行
@Override
public void run() {
System.out.println("===================");
}}, 1000, 5000, TimeUnit.MILLISECONDS);
exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间打印系统时间,证明两者是互不影响的
@Override
public void run() {
System.out.println(System.nanoTime());
}}, 1000, 2000, TimeUnit.MILLISECONDS);
}
}
//输出,一直循环下去
===================
18116537502800
18118526972600
18120536395200
===================
18122534792700
18124535284500
===================
9. Java中interrupted 和 isInterrupted方法的区别?
(1)interrupt方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。线程中断仅仅是置线程的中断状态位,不会停止线程。视线程的状态为并做处理。一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
(2)interrupted查询当前线程(一定注意是当前线程)的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
(3)isInterrupted仅仅是查询当前线程的中断状态,并不清除原状态。
public class InterruptThreadTest extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("执行中");
if(this.isInterrupted()){
System.out.println("first call outside thread.interrupted(): " + this.interrupted());
System.out.println("first call outside thread.isInterrupted(): " + this.isInterrupted());
System.out.println("second call outside thread.interrupted(): " + this.interrupted());
System.out.println("second call outside thread.isInterrupted(): " + this.isInterrupted());
}
}
}
public static void main(String[] args) {
InterruptThreadTest thread = new InterruptThreadTest();
thread.start();
thread.interrupt();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("status: " + thread.getState());
System.out.println("thread is alive : " + thread.isAlive() );
}
}
//输出
执行中
first call outside thread.interrupted(): true
first call outside thread.isInterrupted(): false
second call outside thread.interrupted(): false
second call outside thread.isInterrupted(): false
执行中
执行中
执行中
执行中
执行中
。。。重复
status: TERMINATED
thread is alive : false
10. Java线程池中submit() 和 execute()方法有什么区别?
(1)两个方法都可以向线程池提交任务,execute()方法的返回类型是 void,它定义在Executor 接口中。
(2) submit()方法可以返回持有计算结果的 Future 对象,它定义在ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。
(3)excute方法会抛出异常。sumbit方法不会抛出异常,调用Future.get()如果有异常可以抛出。
(4)excute入参Runnable,submit入参可以为Callable(可以返回结果的),也可以为Runnable(不能返回结果)。
public class SingleThreadExecutorTest {
public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future> runnableFuture = executorService.submit(() -> System.out.println("run"));// submit一个Runnable任务
Future> callableFuture = executorService.submit(() -> "call");// submit一个Callable任务
executorService.execute(() -> System.out.println("test"));
System.out.println(runnableFuture.get());// Output null
System.out.println(callableFuture.get());// Output call
}
}
//输出
run
null
call
test