美文网首页小卜java
JAVA面试汇总(二)多线程(三)

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

作者: 汤太咸啊 | 来源:发表于2021-12-03 17:52 被阅读0次

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

    1. 谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解

    (1)当synchronized作用于普通方法是,锁对象是this
    (2)当synchronized作用于静态方法是,锁对象是当前类的Class对象
    (3)当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj
    (4)类锁:synchronized修饰静态的方法或代码块
    (5)方法锁:用synchronized修饰非静态方法
    (6)重入锁:以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。synchronized和ReentrantLock 都是可重入锁。

    //不可冲入锁,明显lock方法,锁了之后,别的都要等待,直到unlock才可以
    public class Lock {
        private boolean isLocked = false;
        public synchronized void lock() throws InterruptedException {
            while (isLocked) {
                wait();
            }
            isLocked = true;
        }
        public synchronized void unlock() {
            isLocked = false;
            notify();
        }
    }
    //可重入锁,判断非该线程等待,如果仍然是该线程执行,则计数加一
    public class Lock {
        boolean isLocked = false;
        Thread lockedBy = null;
        int lockedCount = 0;
        public synchronized void lock() throws InterruptedException {
            Thread thread = Thread.currentThread();
            while (isLocked && lockedBy != thread) {
                wait();
            }
            isLocked = true;
            lockedCount++;
            lockedBy = thread;
        }
        public synchronized void unlock() {
            if (Thread.currentThread() == this.lockedBy) {
                lockedCount--;
                if (lockedCount == 0){
                    isLocked = false;
                    notify();
                }
            }
        }
    }
    

    2. static synchronized 方法的多线程访问和作用

    (1)重点在static上面,也就是这个方法是静态的,所有的线程过来都是执行这个静态方法,因此控制所有的并发都要走这一个方法。
    (2)网上有很多解释,基本上都是在,不带static的是只控制到这个类的同一个实例的访问,带static的情况下就包括了所有的实例(即使不是同一个实例的情况)都被控制了访问。

    3. 同一个类里面两个synchronized方法,两个线程同时访问的问题

    (1)两个方法都没有synchronized修饰,调用时都可进入,方法A和方法B都没有加synchronized关键字时,调用方法A的时候可进入方法B,方法A和方法B都可以并行执行
    (2)一个方法有synchronized修饰,另一个方法没有,调用时都可进入,方法A加synchronized关键字而方法B没有加时,调用方法A的时候可以进入方法B,但是方法A只能一个一个执行,方法B可以并行执行
    (3)两个方法都加了synchronized修饰,一个方法执行完才能执行另一个,方法A和方法B都加了synchronized关键字时,调用方法A之后,必须等A执行完成才能进入方法B,方法B执行完了才能执行方法A,因此实际上是顺序执行的
    (4)两个方法都是静态方法且还加了synchronized修饰,一个方法执行完才能执行另一个,方法A和方法B都是static静态方法,且都加了synchronized关键字,则调用方法A之后,需要等A执行完成才能进入方法B,方法B执行完了才能执行方法A,因此实际上是顺序执行的,在如果是同一个实例执行的情况下和(3)是一致的,但是如果不是同一个实例下,(3)也会并行执行,但是(4)还是调用方法A之后,需要等A执行完成才能进入方法B,方法B执行完了才能执行方法A,实际上是顺序执行的。

    //可以自己运行下,去掉两个synchronized,保留一个,都带着,再带着static,各种情况运行下
    public class TwoSynchronized {
        public synchronized void method1() {
            try {
                System.out.println(Thread.currentThread().getName() + " method1 started");
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName() + " method1 end");
            }catch (Exception e){
    
            }
        }
        public synchronized void method2() {
            try {
                System.out.println(Thread.currentThread().getName()+" method2 started");
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName()+" method2 end");
            }catch (Exception e){
    
            }
        }
        public static void main(String[] args) throws InterruptedException {
            TwoSynchronized two = new TwoSynchronized();
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        two.method1();
                    }
                }).start();
            }
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        two.method2();
                    }
                }).start();
            }
            Thread.sleep(100000);
        }
    }
    

    4. 你如何确保main()方法所在的线程是Java程序最后结束的线程?

    可以使用 Thread 类的 join()方法来确保所有程序创建的线程在 main()方法退出前结束。之前讲过了。

    5. 谈谈volatile关键字的作用

    (1)保证可见性,当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。
    (2)保证有序性,Java内存模型允许编译器和处理器对指令重排序以提高运行性能,只会对不存在数据依赖性的指令重排序。因此有在多线程情况下,会导致某些由于指令重排的问题,被声明为volatile可以禁止指令重排序。
    (3)内存屏障,在每个volatile写操作的前面插入一个StoreStore屏障。在每个volatile写操作的后面插入一个StoreLoad屏障。在每个volatile读操作的前面插入一个LoadLoad屏障。在每个volatile读操作的后面插入一个LoadStore屏障。

    LoadLoadL,Load1;LoadLoad;Load2:保证load1的读取操作在在load2及后续读取操作之前执行
    StoreStore,Store1;StoreStore;Store2:在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
    LoadStore,Load1;LoadStore;Store2:在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
    StoreLoad,Store1;StoreLoad;Load2:保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行
    

    6. 谈谈ThreadLocal关键字的作用

    (1)每个线程都会存在自己独立的ThreadLocal
    (2)实际上是一个map,可以把一些在线程开始时候放入一些内容到ThreadLocal中,这样在线程的生命周期内,这些内容都可以直接从ThreadLocal中拿到(如果启动了多线程除外)。
    (3)很多时候AOP的过程中需要从ThreadLocal中取出来一些东西

    7. Java中Semaphore是什么?

    Semaphore信号量,可以控制同时执行的线程数,创建时设定最多同时访问个数,acquire()申请新请求计数+1,达到设定数值后,内部会执行Thread的挂起操作,并把超过指定个数的线程放入到队列中等待唤醒。当release()后会唤醒队列里的前几个线程执行。

    import java.util.concurrent.Semaphore;
    public class SemaphoreTest {
        static Semaphore semaphore = new Semaphore(2, true);
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test();
                    }
                }).start();
            }
        }
        public static void test() {
            try {
                //这块可以运行时可以看到实际时先打印出来,说明已经进入了方法了
                System.out.println("waiting"+Thread.currentThread().getName());
                //申请一个新的请求
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"进来了");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"走了");
            //释放一个请求
            semaphore.release();
        }
    }
    

    8. 什么是Callable和Future?

    (1)Callable和Runnable是一类,但是Callable在线程执行完成,可以返回一些内容。
    (2)Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable用于产生结果,Future用于获取结果。
    (3)把Callable和Future结合起来有个FutureTask,可以执行Callable并且获取结果。下面来个样例:

    public class TestCallableThread implements Callable<String> {
        public static void main(String[] args) throws Exception{
            TestCallableThread tct = new TestCallableThread();
            FutureTask<String> ft = new FutureTask<>(tct);
            new Thread(ft).start();
            while(true){
                if(ft.isDone()){
                    System.out.println(ft.get());
                    break;
                }else{
                    System.out.println("waiting");
                    Thread.sleep(1000);
                }
            }
        }
        @Override
        public String call() throws Exception {
            Thread.sleep(10000);
            return "Very Good";
        }
    }
    

    9. ThreadLocal、synchronized 和volatile 关键字的区别

    (1)ThreadLocal是每个线程独立的一个Map,可以存储一些内容,和另外两个没啥关系
    (2)synchronized用来修饰方法或者代码块的,是一种锁机制,增加了synchronized方法可以防止多线程同时执行该方法,如果是两个实例(同一个实例可以避免)的情况下的同一个synchronized方法被执行,实际上是还能够被同时执行的,如果需要控制就再增加上static就可以控制了
    (3)volatile用来修饰变量的,在多线程中同步变量。表示如果要使用该内容,需要直接从内存中取值,而不能从缓存中取,避免了某些情况下的线程冲突

    0. synchronized与Lock的区别

    (1)synchronized是一个关键字,执行完毕释放,无法获得锁状态
    (2)Lock是一个类,需要手动锁、解锁,否则就会死锁,可以通过trylock来知道有没有获取锁

    相关文章

      网友评论

        本文标题:JAVA面试汇总(二)多线程(三)

        本文链接:https://www.haomeiwen.com/subject/emkmxrtx.html