美文网首页小卜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