JAVA面试汇总(二)多线程(四)

多线程内容比较多,今天写完了第四篇,后边还有五。

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

你可能感兴趣的:(JAVA面试汇总(二)多线程(四))