Java并发编程相关面试问题-含程序答案

来源:互联网 发布:淘宝卖的黄金是真的吗 编辑:程序博客网 时间:2024/06/05 13:37

总结了并发编程面试中可能遇到的大部分编程题,写出答案供大家参考,如果问题请指出,谢过。

并发容器和框架

1.如何让一段程序并发的执行,并最终汇总结果?

使用CyclicBarrier 在多个关口处将多个线程执行结果汇总,
CountDownLatch 在各线程执行完毕后向总线程汇报结果。

CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
从api上理解就是CountdownLatch有主要配合使用两个方法countDown()和await(),countDown()是做事的线程用的方法,await()是等待事情完成的线程用个方法,这两种线程是可以分开的(下面例子:CountdownLatchTest2),当然也可以是同一组线程;CyclicBarrier只有一个方法await(),指的是做事线程必须大家同时等待,必须是同一组线程的工作。

CountdownLatch例子:

import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 各个线程执行完成后,主线程做总结性工作的例子 * @author xuexiaolei * @version 2017年11月02日 */public class CountdownLatchTest2 {    private final static int THREAD_NUM = 10;    public static void main(String[] args) {        CountDownLatch lock = new CountDownLatch(THREAD_NUM);        ExecutorService exec = Executors.newCachedThreadPool();        for (int i = 0; i < THREAD_NUM; i++) {            exec.submit(new CountdownLatchTask(lock, "Thread-"+i));        }        try {            lock.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("大家都执行完成了,做总结性工作");        exec.shutdown();    }    static class CountdownLatchTask implements Runnable{        private final CountDownLatch lock;        private final String threadName;        CountdownLatchTask(CountDownLatch lock, String threadName) {            this.lock = lock;            this.threadName = threadName;        }        @Override public void run() {            System.out.println(threadName + " 执行完成");            lock.countDown();        }    }}

CyclicBarrier例子:

import java.util.concurrent.*;/** * * @author xuexiaolei * @version 2017年11月02日 */public class CyclicBarrierTest {    private final static int THREAD_NUM = 10;    public static void main(String[] args) {        CyclicBarrier lock = new CyclicBarrier(THREAD_NUM, new Runnable() {            @Override public void run() {                System.out.println("这阶段大家都执行完成了,我总结一下,然后开始下一阶段");            }        });        ExecutorService exec = Executors.newCachedThreadPool();        for (int i = 0; i < THREAD_NUM; i++) {            exec.submit(new CountdownLatchTask(lock, "Task-"+i));        }        exec.shutdown();    }    static class CountdownLatchTask implements Runnable{        private final CyclicBarrier lock;        private final String threadName;        CountdownLatchTask(CyclicBarrier lock, String threadName) {            this.lock = lock;            this.threadName = threadName;        }        @Override public void run() {            for (int i = 0; i < 3; i++) {                System.out.println(threadName + " 执行完成");                try {                    lock.await();                } catch (BrokenBarrierException e) {                    e.printStackTrace();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

2.如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取最好的吞吐量?

虽然Exectors可以生成一些很常用的线程池,但毕竟在什么情况下使用还是开发者最清楚的。在某些自己很清楚的使用场景下,java线程池还是推荐自己配置的。下面是java线程池的配置类的参数,我们逐一分析一下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

  • corePoolSize - 池中所保存的线程数,包括空闲线程。
  • maximumPoolSize - 池中允许的最大线程数。
  • keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
  • unit - keepAliveTime 参数的时间单位。
  • workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。用BlocingQueue的实现类都可以。
  • threadFactory - 执行程序创建新线程时使用的工厂。自定义线程工厂可以做一些额外的操作,比如统计生产的线程数等。
  • handler - 饱和策略,即超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。策略有:Abort终止并抛出异常,Discard悄悄抛弃任务,Discard-Oldest抛弃最老的任务策略,Caller-Runs将任务退回给调用者策略。

至于线程池应当配置多大的问题,一般有如下的经验设置:
1. 如果是CPU密集型应用,则线程池大小设置为N+1。
2. 如果是IO密集型应用,则线程池大小设置为2N+1。

用有界队列好还是无界队列好?这种问题的答案肯定是视情况而定:
1. 有界队列有助于避免资源耗尽的情况发生。但他带来了新的问题:当队列填满后,新的任务怎么办?所以有界队列适用于执行比较耗资源的任务,同时要设计好相应的饱和策略。
2. 无界队列和有界队列刚好相反,在资源无限的情况下可以一直接收新任务。适用于小任务,请求和处理速度相对持平的状况。
3. 其实还有一种同步移交的队列 SynchronousQueue ,这种队列不存储任务信息,直接将任务提交给线程池。可以理解为容量只有1的有界队列,在特殊场景下有特殊作用,同样得设计好相应的饱和策略。

3.如何使用阻塞队列实现一个生产者和消费者模型?请写代码。

用有界的BlockingQueue来实现,其实BlockingQueue已经将很多调用细节隐去了,实现很简单了。

/** * 用阻塞队列快速实现生产者-消费者 * @author xuexiaolei * @version 2017年11月01日 */public class ProduceAndConsumer {    public static void main(String[] args) {        final BlockingQueue<Integer> list = new ArrayBlockingQueue<Integer>(10);        Procude procude = new Procude(list);        Consumer consumer = new Consumer(list);        procude.start();        consumer.start();    }    static class Procude extends Thread{        private final BlockingQueue<Integer> list;        Procude(BlockingQueue<Integer> list) {            this.list = list;        }        @Override public void run() {            while(true){                try {                    Integer take = list.take();                    System.out.println("消费数据:" + take);//                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    static class Consumer extends Thread{        private final BlockingQueue<Integer> list;        Consumer(BlockingQueue<Integer> list) {            this.list = list;        }        @Override public void run() {            while (true){                try {                    int i = new Random().nextInt(100);                    list.put(i);                    System.out.println("生产数据:" + i);                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

4.多读少写的场景应该使用哪个并发容器,为什么使用它?比如你做了一个搜索引擎,搜索引擎每次搜索前需要判断搜索关键词是否在黑名单里,黑名单每天更新一次。

用CopyOnWriteArrayList、CopyOnWriteArraySet。

CopyOnWriteArrayList特性:
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

搜索引擎黑名单缓存实现:(自己复查了很多遍,如有问题,请指出)

import java.util.Collections;import java.util.Set;import java.util.UUID;import java.util.concurrent.*;/** * 做了一个搜索引擎,搜索引擎每次搜索前需要判断搜索关键词是否在黑名单里,黑名单每天更新一次。 * @author xuexiaolei * @version 2017年11月03日 */public class SearchEngineBlackListCache {    private final Set<String> blackList = new CopyOnWriteArraySet<>();//黑名单集合    private final Set<String> todayBlackList = new ConcurrentSkipListSet<>();//当天添加的黑名单    /******内部类单例写法 start******/    private SearchEngineBlackListCache(){}    private static class Holder {        private static SearchEngineBlackListCache singleton = new SearchEngineBlackListCache();    }    public static SearchEngineBlackListCache getInstance(){        return Holder.singleton;    }    /******内部类单例写法 end******/    /**     * 获取黑名单列表     * @return     */    public Set<String> getBlackList() {        return Collections.unmodifiableSet(blackList);    }    /**     * 判断是否在黑名单内     * @param name     * @return     */    public boolean isBlack(String name){        return blackList.contains(name);    }    /**     * 将今天的黑名单加入到黑名单内,外部系统可以定时每天执行这个方法     */    public void mergeBlackList(){        synchronized (todayBlackList){            blackList.addAll(todayBlackList);            todayBlackList.clear();        }    }    /**     * 加入黑名单     * @param name     */    public void addBlackList(String name){        synchronized (todayBlackList) {            todayBlackList.add(name);        }    }    /**     * 随机生成50个线程来测试     * @param args     */    public static void main(String[] args) {        SearchEngineBlackListCache cache = SearchEngineBlackListCache.getInstance();        final int COUNT = 50;        CountDownLatch countDownLatch = new CountDownLatch(COUNT);        ExecutorService exec = Executors.newFixedThreadPool(COUNT);        for (int i = 0; i < COUNT; i++) {            exec.execute(new Runnable() {                @Override public void run() {                    try {                        countDownLatch.countDown();                        countDownLatch.await();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    cache.addBlackList(UUID.randomUUID().toString());//随机增加黑名单字符串                    cache.mergeBlackList();                    System.out.println(cache.getBlackList().size());                }            });        }        exec.shutdown();    }}

Java中的锁

1.如何实现乐观锁(CAS)?如何避免ABA问题?

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。

ABA问题:
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
解决方法:通过版本号(version)的方式来解决,每次比较要比较数据的值和版本号两项内容即可。

2.读写锁可以用于什么应用场景?

比如网上共享白板,共享文档等都适用。

读写锁逻辑:
1. 当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞。
2. 当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞。
3. 当读写锁在读模式锁状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞。

3.什么时候应该使用可重入锁?

举例来说明锁的可重入性

public class UnReentrant{    Lock lock = new Lock();    public void outer(){        lock.lock();        inner();        lock.unlock();    }    public void inner(){        lock.lock();        //do something        lock.unlock();    }}

outer中调用了inner,outer先锁住了lock,这样inner就不能再获取lock。其实调用outer的线程已经获取了lock锁,但是不能在inner中重复利用已经获取的锁资源,这种锁即称之为 不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。

synchronized、ReentrantLock都是可重入的锁,可重入锁相对来说简化了并发编程的开发。

4.什么场景下可以使用volatile替换synchronized?

状态标志:把简单地volatile变量作为状态标志,来达成线程之间通讯的目的,省去了用synchronized还要wait,notify或者interrupt的编码麻烦。
替换重量级锁:如果某个变量仅是单次读或者单次写操作,没有复合操作(i++,先检查后判断之类的)就可以用volatile替换synchronized。

并发工具

1.如何实现一个流控程序,用于控制请求的调用次数?

import java.util.Random;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;/** * 阻塞访问的线程,直到获取了访问令牌 * @author xuexiaolei * @version 2017年11月15日 */public class FlowControl2 {    private final static int MAX_COUNT = 10;    private final Semaphore semaphore = new Semaphore(MAX_COUNT);    private final ExecutorService exec = Executors.newCachedThreadPool();    public void access(int i){        exec.submit(new Runnable() {            @Override public void run() {                semaphore.acquireUninterruptibly();                doSomething(i);                semaphore.release();            }        });    }    public void doSomething(int i){        try {            Thread.sleep(new Random().nextInt(100));            System.out.println(String.format("%s 通过线程:%s 访问成功",i,Thread.currentThread().getName()));        } catch (InterruptedException e) {        }    }    public static void main(String[] args) {        FlowControl2 web = new FlowControl2();        for (int i = 0; i < 2000; i++) {            web.access(i);        }    }}

几道笔试题目

1.(百度笔试题)以下多线程对int型变量x的操作,哪几个不需要进行同步:

A. x=y;
B. x++;
C. ++x;
D. x=1;

答案:D
前三个都至少需要先读取,再操作,非原子操作。而D的话,直接赋值。

2.(阿里巴巴笔试题)多线程中栈与堆是公有的还是私有的

A:栈公有, 堆私有
B:栈公有,堆公有
C:栈私有, 堆公有
D:栈私有,堆私有

答案:C

3.一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。

int tally = 0;//glablevoid ThreadProc(){    for(int i = 1; i <= 50; i++)        tally += 1;}

答案:50到100
tolly+=1,要分为三个指令(读tolly,tolly+1,写回tolly)
50的一种情况是:线程一读x,线程二也读x,线程一寄存器加一,线程二寄存器加一,放回x,线程二放加x,这种情况虽然二个线程都对x加1,但显然只加了一次。所以到最后只加50次。

4.子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次,如此循环50次,试写出代码。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.atomic.AtomicBoolean;/** *  * @author xuexiaolei * @version 2017年11月06日 */public class Interview4_2 {    private static final int COUNT = 50;    private static final Object lock = new Object();    private static AtomicBoolean permit = new AtomicBoolean(true);//控制当前执行线程的权力    public static void main(String[] args) {        ExecutorService exec = Executors.newSingleThreadExecutor();        exec.execute(new Runnable() {            @Override public void run() {                for (int i = 0; i < COUNT; i++) {                    synchronized (lock) {                        while (!permit.get()) {                            try {                                lock.wait();                            } catch (InterruptedException e) {                                e.printStackTrace();                            }                        }                        System.out.println("子线程循环十次");                        permit.set(false);                        lock.notifyAll();                    }                }                System.out.println("子线程结束");            }        });        for (int i = 0; i < COUNT; i++) {            synchronized (lock) {                while (permit.get()) {                    try {                        System.out.println("主线程等待");                        lock.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                System.out.println("主线程循环一百次");                permit.set(true);                lock.notifyAll();            }        }        System.out.println("主线程结束");        exec.shutdown();    }}

5.(迅雷笔试题):编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** *5.(迅雷笔试题):编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。 * @author xuexiaolei * @version 2017年11月10日 */public class Interview5 {    private static final int COUNT = 10;    private static final Object lock = new Object();    private static volatile String permit = "A";//控制当前执行的线程标识    public static void main(String[] args) {        ExecutorService exec = Executors.newFixedThreadPool(3);        exec.execute(new Task("A"));        exec.execute(new Task("B"));        exec.execute(new Task("C"));        exec.shutdown();    }    static class Task implements Runnable {        private final String name;        Task(String name) {            this.name = name;        }        @Override public void run() {            for (int i = 0; i < COUNT; i++) {                synchronized (lock){                    while (!this.name.equals(permit)){                        try {                            lock.wait();                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                    System.out.print(this.name);                    permit = nextPermit(permit);                    lock.notifyAll();                }            }        }        private String nextPermit(String permit) {            if (permit.equals("A")) return "B";            if (permit.equals("B")) return "C";            if (permit.equals("C")) return "A";            throw new RuntimeException("hello");        }    }}

6.(Google面试题)有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推………现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:

A:1 2 3 4 1 2….
B:2 3 4 1 2 3….
C:3 4 1 2 3 4….
D:4 1 2 3 4 1….
请设计程序。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.ReentrantLock;/** * (Google面试题)有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式: A:1 2 3 4 1 2.... B:2 3 4 1 2 3.... C:3 4 1 2 3 4.... D:4 1 2 3 4 1.... 请设计程序。 版本一:设计的四个线程,线程间完全独立。和轮询的思想十分相似,线程各自尝试去获取文件锁,然后再看是否能写入当前文件。        当前十分低效,但如果 写操作 消耗的时间越多,效率就越高 一次输出例子如下:         1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1         2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1         3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3         4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 * @author xuexiaolei * @version 2017年11月14日 */public class Interview6 {    private static final AtomicInteger count = new AtomicInteger(0);    ///模拟四个文件    private static final StringBuffer fileA = new StringBuffer();    private static final StringBuffer fileB = new StringBuffer();    private static final StringBuffer fileC = new StringBuffer();    private static final StringBuffer fileD = new StringBuffer();    //四个文件的锁    private static final ReentrantLock lockA = new ReentrantLock();    private static final ReentrantLock lockB = new ReentrantLock();    private static final ReentrantLock lockC = new ReentrantLock();    private static final ReentrantLock lockD = new ReentrantLock();    //四个文件的书写位置    private static final AtomicInteger numberA = new AtomicInteger(1);    private static final AtomicInteger numberB = new AtomicInteger(2);    private static final AtomicInteger numberC = new AtomicInteger(3);    private static final AtomicInteger numberD = new AtomicInteger(4);    public static void main(String[] args) throws InterruptedException {        ExecutorService exec = Executors.newFixedThreadPool(4);        exec.execute(new writeTask(1));        exec.execute(new writeTask(2));        exec.execute(new writeTask(3));        exec.execute(new writeTask(4));        exec.shutdown();        Thread.sleep(5000);//等待线程池结束后输出文件内容        System.out.println(fileA);        System.out.println(fileB);        System.out.println(fileC);        System.out.println(fileD);    }    static class writeTask implements Runnable{        private final int wirteContent;//输出的内容,线程1的功能就是输出1,线程2的功能就是输出2        writeTask(int wirteContent) {            this.wirteContent = wirteContent;        }        @Override public void run() {            while (count.get() < 1000) {//1000为多个线程总共大概尝试的次数                //尝试写入A文件                try {                    boolean a = lockA.tryLock(1, TimeUnit.MILLISECONDS);                    if (a) {                        if (numberA.get() % 4 == wirteContent%4) {                            fileA.append(wirteContent + " ");                            numberA.incrementAndGet();                        }                        lockA.unlock();                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }                //尝试写入文件                try {                    boolean a = lockB.tryLock(1, TimeUnit.MILLISECONDS);                    if (a) {                        if (numberB.get() % 4 == wirteContent%4) {                            fileB.append(wirteContent + " ");                            numberB.incrementAndGet();                        }                        lockB.unlock();                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }                //尝试写入C文件                try {                    boolean a = lockC.tryLock(1, TimeUnit.MILLISECONDS);                    if (a) {                        if (numberC.get() % 4 == wirteContent%4) {                            fileC.append(wirteContent + " ");                            numberC.incrementAndGet();                        }                        lockC.unlock();                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }                //尝试写入D文件                try {                    boolean a = lockD.tryLock(1, TimeUnit.MILLISECONDS);                    if (a) {                        if (numberD.get() % 4 == wirteContent%4) {                            fileD.append(wirteContent + " ");                            numberD.incrementAndGet();                        }                        lockD.unlock();                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }                count.incrementAndGet();            }        }    }}

7.启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20….以此类推, 直到打印到75. 程序的输出结果应该为:

线程1: 1
线程1: 2
线程1: 3
线程1: 4
线程1: 5

线程2: 6
线程2: 7
线程2: 8
线程2: 9
线程2: 10

线程3: 71
线程3: 72
线程3: 73
线程3: 74
线程3: 75

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.atomic.AtomicInteger;/** * * 7.启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为: 线程1: 1 线程1: 2 线程1: 3 线程1: 4 线程1: 5 线程2: 6 线程2: 7 线程2: 8 线程2: 9 线程2: 10 ... 线程3: 71 线程3: 72 线程3: 73 线程3: 74 线程3: 75 处理边界条件有点烦,其他还是不错的 * @author xuexiaolei * @version 2017年11月14日 */public class Interview7 {    private static final Object lock = new Object();    private static final AtomicInteger counter = new AtomicInteger(0);    public static void main(String[] args) {        ExecutorService exec = Executors.newFixedThreadPool(3);        exec.execute(new Task("线程1", 0));        exec.execute(new Task("线程2", 1));        exec.execute(new Task("线程3", 2));        exec.shutdown();    }    static class Task implements Runnable {        private final String threadName;        private final int count;        Task(String threadName, int count) {            this.threadName = threadName;            this.count = count;        }        @Override public void run() {            do {                synchronized (lock) {                    while ((counter.get()/5)%3 != count) {//counter每次输出完成肯定是5的倍数,除以5然后对3取余判断是否是当前线程来写                        try {                            lock.wait();                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                    for (int i = 0; i < 5; i++) {                        System.out.println(threadName+":"+counter.incrementAndGet());                    }                    lock.notifyAll();                }            } while (counter.get() < 65);        }    }}

8.在Java中创建线程安全的Singleton。

一共五种写法:

  • 饿汉式写法
  • 懒汉式写法
  • 双重检查锁定
  • 内部类写法
  • 枚举写法
/** * 饿汉式写法 * * 就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。 * 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。 * * @author xuexiaolei * @version 2017年11月15日 */public class Singleton01 {    private static Singleton01 instance = new Singleton01();    private Singleton01(){}    public static Singleton01 getInstance() {        return instance;    }}/** * 懒汉式写法 * * 线程安全,但是getInstance方法效率十分低 * * @author xuexiaolei * @version 2017年11月15日 */public class Singleton02 {    private static Singleton02 instance = null;    private Singleton02(){}    public static synchronized Singleton02 getInstance(){        if (instance == null){            instance = new Singleton02();        }        return instance;    }}/** * 双重检查锁写法 * * getInstance()方法中,进行两次null检查。看似多此一举,但实际上却极大提升了并发度,进而提升了性能。为什么可以提高并发度呢?在单例中new的情况非常少,绝大多数都是可以并行的读操作。因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,执行效率提高的目的也就达到了。 * * 注意volatile的语义:可见性和禁止指令重排。可见性是jdk一直支持的,禁止指令重拍在jdk1.5之后才开始支持,所以此方法在jdk1.5以上才可运行。 * * @author xuexiaolei * @version 2017年11月15日 */public class Singleton03 {    private static volatile Singleton03 instance = null;    private Singleton03(){}    public static Singleton03 getInstance(){        if(instance == null){            synchronized (Singleton03.class){                if (instance == null){                    instance = new Singleton03();                }            }        }        return instance;    }}/** * 静态内部类法 * * Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。 * * @author xuexiaolei * @version 2017年11月15日 */public class Singleton04 {    private static class Inner{        private static Singleton04 instance = new Singleton04();    }    private Singleton04(){}    public static Singleton04 getInstance(){        return Inner.instance;    }}/** * 枚举写法 * * 优雅。使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。 * * @author xuexiaolei * @version 2017年11月15日 */public enum Singleton05 {    INSTANCE;    /****任意方法直接用****/    public void method(){    }}

代码在 https://gitee.com/xuea/alltest/tree/master/concurrency/src/main/java/com/leo/interview 均有体现。
欢迎各位大佬评论指出问题,谢过。

阅读全文
1 0
原创粉丝点击