多线程面试

synchronized有什么作用,底层是如何实现的呢

【答案解析】
方法或代码块的互斥性来完成实际上的一个原子操作。(方法或代码块在被一个线程调用时,其他线程处于等待状态)
所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。【在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。】
a、静态方法:Java类对应的Class类的对象所关联的监视器对象。
b、实例方法:当前对象实例所关联的监视器对象。
c、代码块:代码块声明中的对象所关联的监视器对象。
【备注】更多可以参照体系里面的文章:
synchronized关键字

violatile关键字有什么作用呢?

【答案解析】
volatile关键词:用来对共享变量的访问进行同步,上一次写入操作的结果对下一次读取操作是肯定可见的。(在写入volatile变量值之后,CPU缓存中的内容会被写回内存;在读取volatile变量时,CPU缓存中的对应内容会被置为失效,重新从主存中进行读取),volatile不使用锁,性能优于synchronized关键词。
用来确保对一个变量的修改被正确地传播到其他线程中。
【注意】

  • 基本类型,引用类型的复制引用是原子操作;(即一条指令完成)
  • long与double的赋值,引用是可以分割的,非原子操作;
    【原因:但是java对long和double的赋值操作是非原子操作!!long和double占用的字节数都是8,也就是64bits。在32位操作系统上对64位的数据的读写要分两步完成,每一步取32位数据。这样对double和long的赋值操作就会有问题:如果有两个线程同时写一个变量内存,一个进程写低32位,而另一个写高32位,这样将导致获取的64位数据是失效的数据。因此需要使用volatile关键字来防止此类现象。volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。】
    【在64位JVM中double和long的赋值操作是原子操作。】
  • 要在线程间共享long或double的字段时,必须在synchronized中操作,或是声明成volatile

Object类的wait、notify和notifyAll方法有什么作用呢?

【答案解析】
线程之间锁的使用方式,是Java中多线程锁的基础,不过实际项目中一般会使用高级API,如果不行的话会最后使用这个
【备注】更多可以参照体系里面的文章:
Object类的wait、notify和notifyAll方法

java中如何实现一个线程呢?如果需要获取线程的返回结果该如何做呢

【答案解析】
3种实现方式:继承Thread,实习Runnable接口或者实现Callable接口
如果要获取线程的返回结果的话就实现CallAble接口
【备注】更多可以参照体系里面的文章:
基本线程类

线程状态有哪些,如何停止运行一个线程?线程状态转化方法有哪些呢

【答案解析】
线程可以处于下列状态之一::NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING(有超时的等待)、TERMINATED。
线程状态转换图:

thread1

【备注】
可以参照体系里面的文章:
基本线程类

如何实现一个线程池呢?

【答案解析】

	// 可变大小线程池,按照任务数来分配线程,
	ExecutorService e = Executors.newCachedThreadPool();
	// 单线程池,相当于FixedThreadPool(1)
    ExecutorService e = Executors.newSingleThreadExecutor();
	// 固定大小线程池。实际使用第3种会比较多
    ExecutorService e = Executors.newFixedThreadPool(3);
    // 然后运行
    e.execute(new MyRunnableImpl());

线程执行过程中,抛出一个RuntimeException,线程会是什么状态呢

【答案解析】
如果没有try {}catch包裹的话线程会中止

并发包下面必须掌握的相关知识:

【答案解析】
分为以下:
ThreadLocal类
原子类(AtomicInteger、AtomicBoolean……):乐观锁会使用这个
ReentrantLock和ReentrantReadWriteLock加锁和释放锁,实际项目一般使用这个类
容器类:BlockingQueue、ConcurrentHashMap、CopyOnWriteArrayList,很多场景使用容器类可以很轻松解决

可以参照体系里面的文章:
高级多线程控制类

讲讲对多线程的理解

【答案解析】
多线程开发中,最好每个线程是独立的,这样不用加锁,性能与代码安全性都好,比如起了5个线程处理List,可以实现用hash函数把list分成5份,每个线程启动的时候只处理自己对应的那一份就行。

使用容器类,比如BlockQueue阻塞队列或者ConCurrentHashMap,利用生产消费者等设计模型,能够解决绝大多数并发问题。

线程内部的独立变量如果不与外部交互的话,可以使用ThreadLocal类。

多线程开发中应该优先使用高层API(比如信号量Semaphore,回环栅栏CyclicBarrier等,也需要对应的使用场景),如果无法满足,使用java.util.concurrent.atomic(比如AtomicInteger原子操作类)和java.util.concurrent.locks包提供的中层API(一般都是用ReentrantLock的lock(),unlick()方法处理锁,用的主要也是中层API),而synchronized和volatile,以及wait,notify和notifyAll等低层API 应该最后考虑。

请实现一个生产消费者模型

【答案解析】
可以直接使用阻塞队列,也可以使用普通的list,但是这个时候要加上对象Object的wait和notify/notifyAll

public class BlockingQueueTest {
    //生产者
    public static class Producer implements Runnable{
        private final BlockingQueue<Integer> blockingQueue;
        private volatile boolean flag;
        private Random random;
        public Producer(BlockingQueue<Integer> blockingQueue) {
            this.blockingQueue = blockingQueue;
            flag=false;
            random=new Random();
        }
        public void run() {
            while(!flag){
                int info=random.nextInt(100);
                try {
                    blockingQueue.put(info);
                    System.out.println(Thread.currentThread().getName()+" produce "+info);
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }               
            }
        }
        public void shutDown(){
            flag=true;
        }
    }
    //消费者
    public static class Consumer implements Runnable{
        private final BlockingQueue<Integer> blockingQueue;
        private volatile boolean flag;
        public Consumer(BlockingQueue<Integer> blockingQueue) {
            this.blockingQueue = blockingQueue;
        }
        public void run() {
            while(!flag){
                int info;
                try {
                    info = blockingQueue.take();
                    System.out.println(Thread.currentThread().getName()+" consumer "+info);
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }               
            }
        }
        public void shutDown(){
            flag=true;
        }
    }
    public static void main(String[] args){
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>(10);
        Producer producer=new Producer(blockingQueue);
        Consumer consumer=new Consumer(blockingQueue);
        //创建5个生产者,5个消费者
        for(int i=0;i<10;i++){
            if(i<5){
                new Thread(producer,"producer"+i).start();
            }else{
                new Thread(consumer,"consumer"+(i-5)).start();
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        producer.shutDown();
        consumer.shutDown();
    }
}

请实现一个公平锁

公平锁:
公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。

非公平锁:
在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。

基于ReentrantLock可以实现公平锁与非公平锁,构造函数根据数据参数内容调用FairSync或者NonFairSync,就可以实现公平锁或者非公平锁。
private static final ReentrantLock lock = new ReentrantLock(); //非公平锁
private static final ReentrantLock fairlock = new ReentrantLock(true); //公平锁
公平锁FairSync的核心是锁内部有一个双向链表,每次获取锁都是依次从链表队首取值。

可以参照完整范例加深理解:

public class ReentrantLockTest {
    private static final ReentrantLock lock = new ReentrantLock();  
    private static final ReentrantLock fairlock = new ReentrantLock(true);  
    private int n;  
    public static void main(String[] args) {  
        ReentrantLockTest rlt = new ReentrantLockTest();  
        for (int i = 0; i < 100; i++) {  
            Thread nonT = new Thread(new NonFairTestThread(rlt));  
            nonT.setName("nonFair[" + (i + 1) + "]");  
            nonT.start();  
            Thread fairT = new Thread(new FairTestThread(rlt));  
            fairT.setName("fair[" + (i + 1) + "]");  
            fairT.start();  
        }  
    }  
    // 非公平锁  
    static class NonFairTestThread implements Runnable {  
        private ReentrantLockTest rlt;  
        public NonFairTestThread(ReentrantLockTest rlt) {  
            this.rlt = rlt;  
        }  
        public void run() {  
            lock.lock();  
            try {  
                rlt.setNum(rlt.getNum() + 1);  
                System.out.println(Thread.currentThread().getName()  
                        + " nonfairlock***************" + rlt.getNum());  
            } finally {  
                lock.unlock();  
            }  
        }  
    }  
    // 公平锁  
    static class FairTestThread implements Runnable {  
        private ReentrantLockTest rlt;  
        public FairTestThread(ReentrantLockTest rlt) {  
            this.rlt = rlt;  
        }  
        public void run() {  
            fairlock.lock();  
            try {  
                rlt.setNum(rlt.getNum() + 1);  
                System.out.println(Thread.currentThread().getName()  
                        + "   fairlock=======" + rlt.getNum() + "   "  
                        + fairlock.getHoldCount() + " queuelength="  
                        + fairlock.getQueueLength());  
            } finally {  
                fairlock.unlock();  
            }  
        }  
    }  
    public void setNum(int n) {  
        this.n = n;  
    }  
    public int getNum() {  
        return n;  
    }  
}

【备注】ReentrantLock的公平锁与非公平锁的源码解析:
上面的lock()方法最后都会调用acquire(int arg) 这个方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && //这个方法获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //这个方法是阻塞
            selfInterrupt(); //这个就是interrupt()阻断方法
    }

然后:公平锁FairSync:
公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                  ///如果当前线程是等待队列的第一个或者等待队列为空,则通过cas指令设置state为1,当前线程获得锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { //如果当前线程本身就持有锁,那么叠加状态值,持续获得锁
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

其中hasQueuedPredecessors是用于检查是否有等待队列的。

    public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

非公平锁NonfairSync
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式:
非公平锁的TryAcquire方法源码如下

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

然后就是:

		final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                    //如果当前没有线程占有锁,当前线程直接通过cas指令占有锁,管他等待队列,就算自己排在队尾也是这样
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
posted @ 2022-07-12 17:28  Faetbwac  阅读(21)  评论(0编辑  收藏  举报