Java多线程常用面试题(含答案,精心总结整理)

本文详细总结了Java多线程面试中的常见问题,包括如何保证线程执行顺序、Lock接口与synchronized的区别、wait和sleep的差异、阻塞队列的实现、生产者消费者问题、死锁的产生及解决、原子操作与volatile关键字的作用等。通过对这些问题的探讨,揭示了Java并发编程中的关键概念和技术,是面试准备的重要参考资料。
摘要由CSDN通过智能技术生成

Java并发编程问题是面试过程中很容易遇到的问题,提前准备是解决问题的最好办法,将试题总结起来,时常查看会有奇效。

现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。

核心:

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
想要更深入了解,建议看一下join的源码,也很简单的,使用wait方法实现的。

t.join(); //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。

代码实现:

public static void main(String[] args) {
        method01();
        method02();
    }

    /**
     * 第一种实现方式,顺序写死在线程代码的内部了,有时候不方便
     */
    private static void method01() {
        Thread t1 = new Thread(new Runnable() {
            @Override public void run() {
                System.out.println("t1 is finished");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 is finished");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 is finished");
            }
        });

        t3.start();
        t2.start();
        t1.start();
    }


    /**
     * 第二种实现方式,线程执行顺序可以在方法中调换
     */
    private static void method02(){
        Runnable runnable = new Runnable() {
            @Override public void run() {
                System.out.println(Thread.currentThread().getName() + "执行完成");
            }
        };
        Thread t1 = new Thread(runnable, "t1");
        Thread t2 = new Thread(runnable, "t2");
        Thread t3 = new Thread(runnable, "t3");
        try {
            t1.start();
            t1.join();
            t2.start();
            t2.join();
            t3.start();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?

这个题的原答案我认为不是很全面。
Lock接口 和 ReadWriteLock接口 如下:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

ReadWriteLock是对Lock的运用,具体的实现类是 ReentrantReadWriteLock ,下面用这个类来实现读写类型的高效缓存:

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 用ReadWriteLock读写锁来实现一个高效的Map缓存
 * Created by LEO on 2017/10/30.
 */
public class ReaderAndWriter<K, V> {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();
    private final Map<K, V> map;

    public ReaderAndWriter(Map<K, V> map) {
        this.map = map;
    }

    /*************   这是用lock()方法写的   ********************/
//    public V put(K key, V value){
//        writeLock.lock();
//        try {
//            return map.put(key, value);
//        }finally {
//            writeLock.unlock();
//        }
//    }
//    public V get(K key){
//        readLock.lock();
//        try {
//            return map.get(key);
//        }finally {
//            readLock.unlock();
//        }
//    }

    /*************   这是用tryLock()方法写的   ********************/
    public V put(K key, V value){
        while (true){
            if(writeLock.tryLock()){
                try {
                    System.out.println("put "+ key +" = " + value);
                    return map.put(key, value);
                }finally {
                    writeLock.unlock();
                }
            }
        }
    }
    public V get(K key){
        while (true){
            if (readLock.tryLock()) {
                try {
                    V v = map.get(key);
                    System.out.println("get "+ key +" = " + v);
                    return v;
                } finally {
                    readLock.unlock();
                }
            }
        }
    }


    /********************    下面是测试区       *********************************/
    public static void main(String[] args) {
        final ReaderAndWriter<String, Integer> rw = new ReaderAndWriter<>(new HashMap<>());
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            exec.execute(new TestRunnable(rw));
        }
        exec.shutdown();
    }

    static class TestRunnable implements Runnable{
        private final ReaderAndWriter<String, Integer> rw;
        private final String KEY = "x";

        TestRunnable(ReaderAndWriter<String, Integer> rw) {
            this.rw = rw;
        }

        @Override
        public void run() {
            Random random = new Random();
            int r = random.nextInt(100);
            //生成随机数,小于30的写入缓存,大于等于30则读取数字
            if (r < 30){
                rw.put(KEY, r);
            } else {
                rw.get(KEY);
            }
        }
    }
}

在java中wait和sleep方法的不同?

通常会在电话面试中经常被问到的Java线程面试问题。
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。

此处我想理一下Java多线程的基础知识:
- Java的多线程锁是挂在对象上的,并不是在方法上的。即每个对象都有一个锁,当遇到类似synchronized的同步需要时,就会监视(monitor)每个想使用本对象的线程按照一定的规则来访问,规则也就是在同一时间内只能有一个线程能访问此对象。
- Java中获取锁的单位是线程。当线程A获取了对象B的锁,也就是对象B的持有标记上写的是线程A的唯一标识,在需要同步的情况下的话,只有线程A能访问对象B。
- Thread常用方法有:start/stop/yield/sleep/interrupt/join等,他们是线程级别的方法,所以并不会太关心锁的具体逻辑。
- Object的线程有关方法是:wait/wait(事件参数)/notify/notifyAll,他们是对象的方法,所以使用的时候就有点憋屈了,必须当前线程获取了本对象的锁才能使用,否则会报异常。但他们能更细粒度的控制锁,可以释放锁。

用Java实现阻塞队列。

这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。

下面是实现了阻塞的take和put方法的阻塞队列(分别用synchronized 和 wait/notify 实现):

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 实现了阻塞的take和put方法的阻塞队列
 *      分别用synchronized 和 wait/notify 实现
 * @author xuexiao
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值