java面经4-线程

线程

1、多线程中的i++线程安全吗?为什么?
若i是局部变量则是安全的
若是全局变量则不安全,i++,不是原子操作。
2、如何线程安全的实现一个计数器?
使用AtomicInteger
使用Synchronized关键字;
3、多线程同步的方法
Synchroinzed关键字、Lock、volatile、ThreadLocal
4、介绍一下生产者消费者模式?
组成:缓冲区、消费者、生产者
操作:消费者必须在缓冲区>0的时候才可以消费、生产者必须在缓冲区小于缓冲区最大存储数的时候才可以生产
优点:1、解耦;2、异步;3、削峰。当消费者的消费速度更不是生产者的时候
5、线程,进程,然后线程创建有很大开销,怎么优化?
使用线程池,避免线程频繁的被创建和销毁,重用线程。
6、线程池运行流程,参数,策略
参数:核心线程数、最大线程数、同步队列、拒绝策略
流程:当来到一个任务,若存在空闲的线程,则使用该线程执行任务。若不存在空闲线程,当前线程数小于核心线程数时,会新建一个线程来执行任务;若当前线程数大于等于核心线程数,会将任务加入同步队列。若同步队列未满,则添加成功;否则若当前线程数小于最大线程数时,会创建线程来执行任务;若当前线程数大于等于最大线程数时,则会抛出异常,交由异常处理方法来进行操作。
7、讲一下AQS吧。
AbstractQueueSynchronized 是一个抽象类
抽象队列式同步器
state状态:volatile int类型的变量,用户表示当前同步状态

根据自己同步器需要满足的性质实现线程获取和释放资源的方式(修改同步状态变量的方式)即可,至于具体线程等待队列的维护(如获取资源失败入队、唤醒出队、以及线程在队列中行为的管理等),AQS在其顶层已经帮我们实现好了,AQS的这种设计使用的正是模板方法模式

8、创建线程的方法,哪个更好,为什么?
实现Runnable接口。
采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

10、Java中有几种线程池?
(1) newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      				60L, TimeUnit.SECONDS,
                                      				new SynchronousQueue<Runnable>());

(2) newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

(3) newScheduledThreadPool创建一个定长线程池,支持定时和周期性任务执行(DelayedWorkQueue)

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                E first = q.peek();
                if (first == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(TimeUnit.NANOSECONDS);
                    if (delay > 0) {
                        if (nanos <= 0)
                            return null;
                        if (delay > nanos)
                            delay = nanos;
                        long timeLeft = available.awaitNanos(delay);
                        nanos -= delay - timeLeft;
                    } else {
                        E x = q.poll();
                        assert x != null;
                        if (q.size() != 0)
                            available.signalAll();
                        return x;
                    }
                }
            }
        } finally {
            lock.unlock();
        }
    }

(4) newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

11、线程池有什么好处?
1)线程的重用
2)控制线程池的并发数
3)管理线程(定时、定期、单线程、并发数控制)
12、cyclicbarrier和countdownlatch的区别
cyclicbarrier: N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待,可重用
countdownlatch:一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行,不可重用
13、如何理解Java多线程回调方法?
Future?
15、概括的解释下线程的几种可用状态。
New、Blocked、Runnable、Running、Dead
16、同步方法和同步代码块的区别是什么?
同步方法:(粗粒度锁):
1.修饰一般方法: public synchronized void method (){…},获取的是当前调用对象this上的锁
2.修饰静态方法: public static synchronized void method (){…},获取当前类的对象上的锁
同步代码块(细粒度锁):
synchronized ( obj ) {…},同步代码块可以指定获取哪个对象上的锁

17、启动线程有哪几种方式,线程池有哪几种?
要启动的可以分为两类:返回结果和不返回结果。对于这两种,也分别有两种启动线程的方式:
1)继承Thread类,implements Runnable接口
2)实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的线程

18、在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
在 java 虚拟机中, 每个对象( Object 和 class )通过某种逻辑关联监视器,每个监视器和一个对象引用相关联, 为了实现监视器的互斥功能, 每个对象都关联着一把锁.
一旦方法或者代码块被 synchronized 修饰, 那么这个部分就放入了监视器的监视区域, 确保一次只能有一个线程执行该部分的代码, 线程在获取锁之前不允许执行该部分的代码

另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案

19、sleep() 和 wait() 有什么区别?

sleep()wait()
不释放锁、让出CPUwait():释放锁、让出CPU
任何地方使用只能在同步方法或同步块中使用
是线程类的方法wait()是Object的方法

20、同步和异步有何异同,在什么情况下分别使用他们?举例说明。

同步:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
异步:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
区别:一个需要等待,一个不需要等待,
在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
哪些情况建议使用同步交互呢?比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作,其余情况都优先使用异步交互。

21、设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。使用内部类实现线程,对j增减的时候没有考虑顺序问题。

22、启动一个线程是用run()还是start()?
start()
23、请说出你所知道的线程同步的方法
synchronized、Lock、volatile、ThreadLocal、使用原子类
24、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

25、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?
反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记线程的stop方法,以后我们再也不要说“停止线程”了。而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。
suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
26、线程的sleep()方法和yield()方法有什么区别?
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行
27、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
不可以。方法A为成员方法。
28、请说出与线程同步以及线程调度相关的方法。
线程同步就是当一个线程对内存进行操作时,其他线程不可以对这个内存地址进行操作,其他线程只能是等待状态。
线程调度是按照特定的机制为多个线程分配cpu使用权;
wait()使一个线程处于等待状态,并且释放所持对象的锁;
sleep()使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法会抛出InterruptException异常
notify()唤醒一个处于等待状态的线程,调用此方法不能确定唤醒哪一个线程 又jvm确定并且与优先级无关
notifyall()唤醒所有进行等待状态的线程,该方法不是将对象的锁给所有线程 而是让他们竞争,获得锁的线程进入就绪状态。
29、举例说明同步和异步

30、什么是线程池(thread pool)?

31、说说线程的基本状态以及状态之间的关系?

32、如何保证线程安全?
原子性——锁
这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。
可见性——volatile+锁
可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略或者理解错误的一点。
CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。
这一点是操作系统或者说是硬件层面的机制,所以很多应用开发人员经常会忽略。
顺序性——volatile+锁
顺序性指的是,程序执行的顺序按照代码的先后顺序执行

33、为什么采用双亲委派模型
避免重复加载 + 避免核心类篡改。
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值