最新2021-2022实习校招java面试题JUC大厂高频面试考点(下)(持续更新)

JUC面试题(难)

java基础面试题->传送门

一、volatile

1、了解volatile吗

volatile是Java虚拟机提供的轻量级的同步机制,有3个特性,分别是:保证可见性、不保证原子性、禁止指令重排

2、什么是指令重排

计算机在执行程序时,为了提高性能,编译器在编译java代码和处理器jvm字节码的时候常常会做指令重排多线程中使用线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测,所以需要使用轻量级的同步机制volatile。

3、在哪些地方用到过volatile?

多线程单例模式,通过引入DCL (Double Check Lock) 双端检锁机制

就是在进来和出去的时候,进行检测

public class SingletonDemo {
/**
instance = new SingletonDemo();可以分为以下三步进行完成:
memory = allocate(); // 1、分配对象内存空间
instance(memory); // 2、初始化对象
instance = memory; // 3、设置instance指向刚刚分配的内存地址
可能出现指令重排,故要加上volatile
*/
    private static volatile SingletonDemo instance = null;

    private SingletonDemo () {
        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo");
    }

    public static SingletonDemo getInstance() {
        if(instance == null) {
            // a 双重检查加锁多线程情况下会出现某个线程虽然这里已经为空,但是另外一个线程已经执行到d处
            synchronized (SingletonDemo.class) //b
            { 
           //c不加volitale关键字的话有可能会出现尚未完全初始化就获取到的情况。原因是内存模型允许无序写入
                if(instance == null) { 
                	// d 此时才开始初始化
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
//模拟多线程环境
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                SingletonDemo.getInstance();
            }, String.valueOf(i)).start();
        }
    }
}

二、CAS

1、CAS是什么?与synchronized有什么区别?cas有什么缺点?并如何解决

cas——Compare and Swap(比较并交换)是一种系统原语;它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁,所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

缺点:循环时间长,开销大,只能保证一个共享变量的原子操作,会产生ABA问题;

{ABA例子:假设有一个遵循CAS原理的提款机,小慧有100元存款,要使用这个提款机来提款50,由于提款机硬件出了点小问题,小灰的提款操作被同时提交两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。理想情况下,应该一个线程更新成功,另一个线程更新失败,小灰的存款只被扣一次。线程1首先执行成功,把余额从100改成50。线程2因为某种原因阻塞了。这时候,小灰的妈妈刚好给小灰汇款50元。线程2仍然是阻塞状态,线程3执行成功,把余额从50改成100。线程2恢复运行,由于阻塞之前已经获得了“当前值”100,并且经过compare检测,此时存款实际值也是100,所以成功把变量值100更新成了50}

解决办法:1、使用AtomicReference原子引用(保证修改对象引用时的线程安全性),2、使用AtomicStampedReference时间戳原子引用修改版本号(每次修改原子值,版本号加1)

2、int变量在多线程下如何保证其原子性

使用AtomicInteger,调用其api进行增删改查操作,例如:

atomicInteger.compareAndSet(1, 2)

二、集合

1、ArrayList是否线程安全?会报出现什么异常?导致原因?怎么解决?

不安全;java.util.ConcurrentModificationException

1、使用new Vectore<>(),

2、Collections.synchronizedList(new ArrayList<>())

3、使用CopyOnWriteArrayList()

2、hashSet底层是什么实现的?

hashmap

3、hashmap使用put方法添加key-value键值对,但是hashset的add方法就添加了一个数,这怎么解释

hashset的add方法实际调用了hashmap的put方法,只不过添加的值维key,而value是一个常量PRESENTpivate static final Object PRESENT = null;)

三、锁

1、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

乐观锁,每次操作时不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止
悲观锁是会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁可以使用volatile+CAS原语实现,带参数版本来避免ABA问题,在读取和替换的时候进行判定版本是否一致
悲观锁可以使用synchronize的以及Lock

2、死锁产生的四个条件

互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所申请的资源。

3、理解可重入锁

可重入锁就是递归锁

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

也就是说:线程可以进入任何一个它已经拥有的锁所同步的代码块

ReentrantLock / Synchronized 就是一个典型的可重入锁

4、当我们在getLock方法加两把锁会是什么情况呢?

最后得到的结果也是一样的,因为里面不管有几把锁,其它他们都是同一把锁,也就是说用同一个钥匙都能够打开

当我们在getLock方法加两把锁,但是只解一把锁会出现程序直接卡死,线程不能出来,也就说明我们申请几把锁,最后需要解除几把锁

当我们只加一把锁,但是用两把锁来解锁的时候,运行程序会直接报错

5、什么是自旋锁、有什么优缺点?请手写一个自旋锁;

自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU,原来提到的cas,底层使用的就是自旋,自旋就是多次尝试,多次访问,不会阻塞的状态就是自旋。

优点:循环比较获取直到成功为止,没有类似于wait的阻塞

缺点:当不断自旋的线程越来越多的时候,会因为执行while循环不断的消耗CPU资源

/**
 * 手写一个自旋锁
 *
 * 循环比较获取直到成功为止,没有类似于wait的阻塞
 *
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到
 * @author: 陌溪
 * @create: 2020-03-15-15:46
 */
public class SpinLockDemo {

    // 现在的泛型装的是Thread,原子引用线程
    AtomicReference<Thread>  atomicReference = new AtomicReference<>();

    public void myLock() {
        // 获取当前进来的线程
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in ");

        // 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋
        while(!atomicReference.compareAndSet(null, thread)) {

        }
    }

    /**
     * 解锁
     */
    public void myUnLock() {

        // 获取当前进来的线程
        Thread thread = Thread.currentThread();

        // 自己用完了后,把atomicReference变成null
        atomicReference.compareAndSet(thread, null);

        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
    }

    public static void main(String[] args) {

        SpinLockDemo spinLockDemo = new SpinLockDemo();

        // 启动t1线程,开始操作
        new Thread(() -> {

            // 开始占有锁
            spinLockDemo.myLock();


            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 开始释放锁
            spinLockDemo.myUnLock();

        }, "t1").start();


        // 让main线程暂停1秒,使得t1线程,先执行
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 1秒后,启动t2线程,开始占用这个锁
        new Thread(() -> {

            // 开始占有锁
            spinLockDemo.myLock();
            // 开始释放锁
            spinLockDemo.myUnLock();

        }, "t2").start();

    }
}
6、为什么需要读写锁?如何实现?

使用ReentrantLock创建锁的时候,是独占锁,也就是说一次只能一个线程访问,但是有一个读写分离场景,读的时候想同时进行,因此原来独占锁的并发性就没这么好了,因为读锁并不会造成数据不一致的问题,因此可以多个人共享读

/**
 * 读写锁
 * 多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
 * 但是,如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
 *
 * @author: 陌溪
 * @create: 2020-03-15-16:59
 */

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 资源类
 */
class MyCache {

    /**
     * 缓存中的东西,必须保持可见性,因此使用volatile修饰
     */
    private volatile Map<String, Object> map = new HashMap<>();

    /**
     * 创建一个读写锁
     * 它是一个读写融为一体的锁,在使用的时候,需要转换
     */
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    /**
     * 定义写操作
     * 满足:原子 + 独占
     * @param key
     * @param value
     */
    public void put(String key, Object value) {

        // 创建一个写锁
        rwLock.writeLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);

            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            map.put(key, value);

            System.out.println(Thread.currentThread().getName() + "\t 写入完成");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 写锁 释放
            rwLock.writeLock().unlock();
        }
    }

    /**
     * 获取
     * @param key
     */
    public void get(String key) {

        // 读锁
        rwLock.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");

            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Object value = map.get(key);

            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 读锁释放
            rwLock.readLock().unlock();
        }
    }

    /**
     * 清空缓存
     */
    public void clean() {
        map.clear();
    }


}
public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();

        // 线程操作资源类,5个线程写
        for (int i = 1; i <= 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, String.valueOf(i)).start();
        }

        // 线程操作资源类, 5个线程读
        for (int i = 1; i <= 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

四、你认识哪些高并发下的计数器

1、CountDownLatch:减法,减到0

/**
现在有这样一个场景,假设一个自习室里有7个人,其中有一个是班长,班长的主要职责就是在其它6个同学走了后,关灯,锁教室门,然后走人,因此班长是需要最后一个走的,那么有什么方法能够控制班长这个线程是最后一个执行,而其它线程是随机执行的
*/
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        // 计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");
    }
}

2、CyclicBarrier:和CountDownLatch相反,需要集齐七颗龙珠,召唤神龙。也就是做加法,开始是0

public class CyclicBarrierDemo {


    public static void main(String[] args) {
        /**
         * 定义一个循环屏障,参数1:需要累加的值,参数2 需要执行的方法
         */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙");
        });

        for (int i = 0; i < 7; i++) {
            final Integer tempInt = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");

                try {
                    // 先到的被阻塞,等全部线程完成后,才能执行方法
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

3、Semaphore:信号量

信号量主要用于两个目的

  • 一个是用于共享资源的互斥使用

  • 另一个用于并发线程数的控制

    /**
    模拟一个抢车位的场景,假设一共有6个车,3个停车位
    那么我们首先需要定义信号量为3,也就是3个停车位
     */
    public class SemaphoreDemo {
    
        public static void main(String[] args) {
    
            /**
             * 初始化一个信号量为3,默认是false 非公平锁, 模拟3个停车位
             */
            Semaphore semaphore = new Semaphore(3, false);
    
            // 模拟6部车
            for (int i = 0; i < 6; i++) {
                new Thread(() -> {
                    try {
                        // 代表一辆车,已经占用了该车位
                        semaphore.acquire(); // 抢占
    
                        System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
    
                        // 每个车停3秒
                        try {
                            TimeUnit.SECONDS.sleep(3);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + "\t 离开车位");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 释放停车位
                        semaphore.release();
                    }
                }, String.valueOf(i)).start();
            }
        }
    }
    

五、jvm

1、画出JVM基本机构图

96698196-0f6b-4ef6-9a20-85dd35d75f27

  • java栈:存放局部变量,栈由一系列帧组成
  • java堆:存放所有new出来的东西
  • 方法区:被虚拟机加载的类信息、常量、静态常量等。
  • 程序计数器(和系统相关):每个线程拥有一个PC寄存器,在线程创建时创建,指向下一条指令地址
  • 本地方法栈:
2、谈谈你对GC Root的理解

用于标记回收算法:从GC root进行遍历,把可达对象都标记,剩下那些不可达的进行回收,这种方式需要中断其他线程,并且可能产生内存碎片。

java中可以作为GC Roots的对象有:虚拟机栈中引用的对象、方法区中的类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(natice方法)引用的对象。

3、 如何排查死锁

当我们出现死锁的时候,首先需要使用jps命令查看运行的程序jps -l

再使用jstack查看堆栈信息

jstack  xxxx   # 后面参数是 jps输出的该类的pid

通过查看最后一行,我们看到 Found 1 deadlock,即存在一个死锁

4、常见的GC算法

1)引用计数法

每个对象有一个计数器,当对象被引用一次则计数器加1,但对象引用失效一次减1,对于计数器为0的对象意味着是垃圾对象,可以被GC回收。

缺点:每次对象复制时均要维护引用计数器,且计数器本身也有一定的消耗;较难处理循环引用。

在双端循环,互相引用的时候,容易报错,目前很少使用这种方式了

2)复制算法

复制算法在年轻代的时候,进行使用,复制时候有交换

image-20200318184759295

分对象会在From和To区域来回复制,如此交换15次(由jvm参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活就存入老年代中。

算法优点:没有产生内存碎片

3)标记清除

先标记后清除,缺点是会产生内存碎片,用于老年代多一些。

4)标记整理

标记清除整理

image-20200318185100936

5、你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值
使用jps和jinfo进行查看
1、jps:查看java的后台进程
2、jinfo:查看正在运行的java程序

生活常用调优参数

img

  • -Xms:初始化堆内存,默认为物理内存的1/64,等价于 -XX:initialHeapSize
  • -Xmx:最大堆内存,默认为物理内存的1/4,等价于-XX:MaxHeapSize
  • -Xss:设计单个线程栈的大小,一般默认为512K~1024K,等价于 -XX:ThreadStackSize
    • 使用 jinfo -flag ThreadStackSize 会发现 -XX:ThreadStackSize = 0
    • 这个值的大小是取决于平台的
    • Linux/x64:1024KB
    • OS X:1024KB
    • Oracle Solaris:1024KB
    • Windows:取决于虚拟内存的大小
  • -Xmn:设置年轻代大小
  • -XX:MetaspaceSize:设置元空间大小
    • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间的大小仅受本地内存限制。
    • -Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
    • 但是默认的元空间大小:只有20多M
    • 为了防止在频繁的实例化对象的时候,让元空间出现OOM,因此可以把元空间设置的大一些
  • -XX:PrintGCDetails:输出详细GC收集日志信息
    • GC
    • Full GC
6、oom和StackoverflowError

JVM中常见的两个错误,均属于Error,不是Exception异常

1)StackoverFlowError :栈溢出

2)OutofMemoryError: java heap space:堆溢出

java heap space

创建了很多对象,导致堆空间不够存储

GC overhead limit exceeded

GC回收时间过长时会抛出OutOfMemoryError,过长的定义是,超过了98%的时间用来做GC,并且回收了不到2%的堆内存

连续多次GC都只回收了不到2%的极端情况下,才会抛出。假设不抛出GC overhead limit 错误会造成GC清理的这点内存很快会再次被填满,迫使GC再次执行,这样就形成了恶性循环,CPU的使用率一直都是100%,而GC却没有任何成果。

unable to create new native thread

不能够创建更多的新的线程了,也就是说创建线程的上限达到了

在高并发场景的时候,会应用到高并发请求服务器时,经常会出现如下异常java.lang.OutOfMemoryError:unable to create new native thread,准确说该native thread异常与对应的平台有关

导致原因:

  • 应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限
  • 服务器并不允许你的应用程序创建这么多线程,linux系统默认运行单个进程可以创建的线程为1024个,如果应用创建超过这个数量,就会报 java.lang.OutOfMemoryError:unable to create new native thread

解决方法:

  1. 想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
  2. 对于有的应用,确实需要创建很多线程,远超过linux系统默认1024个线程限制,可以通过修改linux服务器配置,扩大linux默认限制

Metaspace

java.lang.OutOfMemoryError:Metaspace

元空间内存不足,Matespace元空间应用的是本地内存

-XX:MetaspaceSize 的处理化大小为20M

六、线程基础面试题

1、你知道有哪些线程池?

Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。

1、newCachedThreadPool创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

2、newFixedThreadPool创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

3、newScheduledThreadPool创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

4、newSingleThreadExecutor,Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程) ,这个线程
池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

2、如何停止一个正在运行的线程

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的
方法。
3、使用interrupt方法中断线程。

class MyThread extends Thread {
    volatile boolean stop = false;
    public void run() {
        while (!stop) {
            System.out.println(getName() + " is running");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("week up from blcok...");
                stop = true; // 在异常处理代码中修改共享变量的状态
            }
        }
        System.out.println(getName() + " is exiting...");
    }
}
class InterruptThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread m1 = new MyThread();
        System.out.println("Starting thread...");
        m1.start();
        Thread.sleep(3000);
        System.out.println("Interrupt thread...: " + m1.getName());
        m1.stop = true; // 设置共享变量为true
        m1.interrupt(); // 阻塞时退出阻塞状态
        Thread.sleep(3000); // 主线程休眠3秒以便观察线程m1的中断情况
        System.out.println("Stopping application...");
    }
}
3、sleep()和wait() 有什么区别?
  1. 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。
  2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态
  3. 在调用 sleep()方法的过程中, 线程不会释放对象锁。
  4. 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
4、什么是线程安全,上下文

线程安全就是说多线程访问同一代码,不会产生不确定的结果;

5、什么是上下文,什么是多线程中的上下文切换,上下文切换的活动,还有引起线程上下文切换的原因
  • 上下文是指某一时间点 CPU 寄存器和程序计数器的内容;
  • 多线程会共同使用一组计算机上的 CPU,而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用 CPU。不同的线程切换使用 CPU发生的切换数据等就是上下文切换。
  • 1.挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处。
    2.在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复。
    3.跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序
    中。
  • 原因:
  1. 当前执行任务的时间片用完之后,系统 CPU 正常调度下一个任务;

  2. 当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一任务;

  3. 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;

  4. 用户代码挂起当前任务,让出 CPU 时间;

  5. 硬件中断;

6、在 java 中守护线程和本地线程区别

java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。
任何线程都可以设置为守护线程和用户线程,通过方法 Thread.setDaemon(bool
on);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()
必须在 Thread.start()之前调用,否则运行时会抛出异常。
两者的区别
唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果
全部的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可
以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的
线程;比如 JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产
生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线
程时,Java 虚拟机会自动离开

7、什么是 Callable 和 Future?

Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能
更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。可以认为
是带有回调的 Runnable。
Future 接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable
用于产生结果,Future 用于获取结果。

  • 9
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百里东君~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值