java面试题_并发与多线程

01. java用()机制实现了进程之间的同步执行
A. 监视器
B. 虚拟机
C. 多个CPU
D. 异步调用

正解: A

解析: 监视器机制即锁机制
02. 线程安全的map在JDK 1.5及其更高版本环境 有哪几种方法可以实现?
A. Map map = new HashMap()
B. Map map = new TreeMap()
C. Map map = new ConcurrentHashMap();
D. Map map = Collections.synchronizedMap(new HashMap());

正解:CD

解析: 1. HashMap,TreeMap 未进行同步考虑,是线程不安全的。
      2. HashTable 和 ConcurrentHashMap 都是线程安全的。区别在于他们对加锁的范围不同,HashTable 对整张Hash表进行加锁,而ConcurrentHashMap将Hash表分为16桶(segment),每次只对需要的桶进行加锁。
      3. Collections 类提供了synchronizedXxx()方法,可以将指定的集合包装成线程同步的集合。比如,
      List  list = Collections.synchronizedList(new ArrayList());
      Set  set = Collections.synchronizedSet(new HashSet())
03. 下列关于java并发的说法中正确的是:
A. copyonwritearraylist适用于写多读少的并发场景
B. readwritelock适用于读多写少的并发场景
C. concurrenthashmap的写操作不需要加锁,读操作需要加锁
D. 只要在定义int类型的成员变量i的时候加上volatile关键字,那么多线程并发执行i++这样的操作的时候就是线程安全的了

正解:B
解析:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。
     ReadWriteLock 当写操作时,其他线程无法读取或写入数据,而当读操作时,其它线程无法写入数据,但却可以读取数据 。适用于 读取远远大于写入的操作。
     ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和HashTable功能相同但是线程安全的方法。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。
04. 下列关于JAVA多线程的叙述正确的是()
A. 调用start()方法和run()都可以启动一个线程
B. CyclicBarrier和CountDownLatch都可以让一组线程等待其他线程
C. Callable类的call()方法可以返回值和抛出异常
D. 新建的线程调用start()方法就能立即进行运行状态

正解:C
解析: CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。
      它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点时被阻塞),
      直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。

      CountDownLatch允许一个或多个线程等待其他线程操作完成。
05. 以下是java concurrent包下的4个类,选出差别最大的一个
A. Semaphore
B. ReentrantLock
C. Future
D. CountDownLatch

正解:C

解析:
   1、答案选C。
       a、它是个接口。
       b、别的类都处理线程间的关系,处理并发机制,但该类只用于获取线程结果。
   2、Future表示获取一个正在指定的线程的结果。对该线程有取消和判断是否执行完毕等操作。
   3、CountDownLatch 是个锁存器,他表示我要占用给定的多少个线程且我优先执行,我执行完之前其他要使用该资源的都要等待。
   4、 Semaphore,就像是一个许可证发放者,也想一个数据库连接池。证就这么多,如果池中的证没换回来,其他人就不能用。
   5、 ReentrantLock 和 synchronized一样,用于锁定线程
06. 假设如下代码中,若t1线程在t2线程启动之前已经完成启动。代码的输出是()
public static void main(String[]args)throws Exception {
    final Object obj = new Object();
    Thread t1 = new Thread() {
        public void run() {
            synchronized (obj) {
                try {
                    obj.wait();
                    System.out.println("Thread 1 wake up.");
                } catch (InterruptedException e) {
                }
            }
        }
    };
    t1.start();
    Thread.sleep(1000);//We assume thread 1 must start up within 1 sec.
    Thread t2 = new Thread() {
        public void run() {
            synchronized (obj) {
                obj.notifyAll();
                System.out.println("Thread 2 sent notify.");
            }
        }
    };
    t2.start();
}

A. Thread 1 wake up
   Thread 2 sent notify.
B. Thread 2 sent notify.
   Thread 1 wake up
C. A、B皆有可能
D. 程序无输出卡死

正解: B
解析: t1 启动后执行 obj.wait() 时,进入阻塞状态,让出时间片并释放锁,
      等待其他线程的唤醒。然后 t2 获取到 obj,并唤醒 t1,待 t2 执行完毕,
      释放锁后,t1 再继续执行。
07.截止JDK1.8版本,java并发框架支持锁包括?
A. 读写锁
B. 自旋锁
C. X锁
D. 乐观锁
F. 排他锁

正解:ABD
解析:截止JDK1.8版本,java并发框架支持锁包括: 读写锁, 自旋锁, 乐观锁。
   锁的分类:
   1、自旋锁 ,自旋,jvm默认是10次吧,有jvm自己控制。for去争取锁
   2、阻塞锁 被阻塞的线程,不会争夺锁。
   3、可重入锁 多次进入改锁的域
   4、读写锁
   5、互斥锁 锁本身就是互斥的
   6、悲观锁 不相信,这里会是安全的,必须全部上锁
   7、乐观锁 相信,这里是安全的。
   8、公平锁 有优先级的锁
   9、非公平锁 无优先级的锁
   10、偏向锁 无竞争不锁,有竞争挂起,转为轻量锁
   11、对象锁 锁住对象
   12、线程锁
   13、锁粗化 多锁变成一个,自己处理
   14、轻量级锁 CAS 实现
   15、锁消除 偏向锁就是锁消除的一种
   16、锁膨胀 jvm实现,锁粗化
   17、信号量 使用阻塞锁 实现的一种策略
   18、排它锁:X锁,若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
08. 关于sleep()和wait(),以下描述错误的一项是( )
A. sleep是线程类(Thread)的方法,wait是Object类的方法;
B. sleep不释放对象锁,wait放弃对象锁
C. sleep暂停线程、但监控状态仍然保持,结束后会自动恢复
D. wait后进入等待锁定池,只有针对此对象发出notify方法后获得对象锁进入运行状态

正解:D
解析:notity()和notifyAll()两个方法均可,应该进入就绪状态而不是运行状态。
09. 下列关于java 中的 wait()方法和 sleep()方法的区别描述错误的是?
A. wait()方法属于Object类,二sleep()属于Thread类
B. 调用wait()方法的时候,线程会放弃对象锁
C. 调用sleep()方法的过程中,线程不会释放对象锁
D. sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程

正解: D
解析:1. 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
     sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
     2、 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得敏感词线程可以使用同步控制块或者方法。
     sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源敏感词线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待敏感词线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
     Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
     3、使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
      synchronized(x){ 
         x.notify() 
        //或者wait() 
      }
     4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
10.有关线程的叙述正确的是()
A. 可以获得对任何对象的互斥锁定
B. 通过继承Thread类或实现Runnable接口,可以获得对类中方法的互斥锁定
C. 线程通过调用对象的synchronized方法可获得对象的互斥锁定
D. 线程调度算法是平台独立的

正解:CD
解析:A,“任何对象”锁定,太绝对了,你能锁住你没有权限访问的对象吗?
     B,前半句话讲的是创建线程的方式,后半句讲的是锁定,驴头不对马嘴。
     C,正确。
     D,线程调度分为协同式调度和抢占式调度,Java使用的是抢占式调度,也就是每个线程将由操作系统来分配执行时间,线程的切换不由线程本身来决定(协同式调度)。这就是平台独立的原因。

11. 下列代码执行结果为()
public static void main(String argv[])throws InterruptedException{
            Thread t=new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.print("2");
                }
            });
            t.start();
             
            t.join();
            System.out.print("1");
        }

A. 21
B. 12
C. 可能为12,也可能为21
D. 以上答案都不对

正解:A
解析:比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
12. 下列哪个说法是正确的()
A. ConcurrentHashMap使用synchronized关键字保证线程安全
B. HashMap实现了Collction接口
C. Array.asList方法返回java.util.ArrayList对象
D. SimpleDateFormat是线程不安全的

正解:D
解析:ConcurrentHashMap 使用segment来分段和管理锁,segment继承自ReentrantLock,因此ConcurrentHashMap使用ReentrantLock来保证线程安全。

13. 并发集合和普通集合如何区别?
并发集合常见的有ConCurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。并发集合位于java.util.concurrent包下,是jdk1.5之后才有的,主要作者是Doug Lea(http://baike.baidu.com/view/3141057.http)完成的。
在java中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了synchronized同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。
ConcurrentHashMap是线程安全的HashMap的实现,默认构造同样有initialCapacity和loadFactor属性,不过还多了一个concurrencyLevel属性,三属性默认值分别为16、0.75及16。其内部使用锁分段技术,维持着锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。
Put操作:并没有在此方法上加上synchronized,首先对key.hashcode进行hash操作,得到key的hash值。Hash操作的算法和map不同,根据此hash值计算并获取其对应的数组中的Segment对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操作。
ConcurrentHashMap基于concurrencyLevel划分出了多个Segment来对key-value进行存储,从而避免每次put操作都得锁住整个数组。在默认的情况下,最佳情况下可允许16个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。
Get(key)
首先对key.hashCode进行hash操作,基于其值找到对应的Segment对象,调用其get方法完成当前操作。而Segment的get操作首先通过hash值和对象数组大小减1的值进行按位与操作来获取数组上对应位置的HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的HashEntry产生不一致性,那么ConcurrentHashMap是如何保证的?
对象数组大小的改变只有在put操作时有可能发生,由于HashEntry对象数组对应的变量是volatile类型的,因此可以保证如HashEntry对象数组大小发生改变,读操作可看到最新的对象数组大小。
在获取到了HashEntry对象后,怎么能保证它及其next属性构成的链表上的对象不会改变呢?这点ConcurrentHashMap采用了一个简单的方式,即HashEntry对象中的hash、key、next属性都是final的,这也就意味着没办法插入一个HashEntry对象到基于next属性构成的链表中间或末尾。这样就可以保证当获取到HashEntry对象后,其基于next属性构建的链表是不会发生变化的。
ConcurrentHashMap默认情况下采用将数据分为16个段进行存储,并且16个段分别持有各自不同的锁Segment,锁仅用于put和remove等改变集合对象的操作,基于volatile及HashEntry链表的不变性实现了读取的不加锁。这些方式使得ConcurrentHashMap能够保持极好的并发支持,尤其是对于读取比插入和删除频繁的Map而言,而它采用的这些方法也可谓是对于Java内存模型、并发机制深刻掌握的体现。
14. 在java中wait和sleep方法的不同?
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
15. synchronized和volatile关键字的作用
       一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
    (1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
    (2)禁止进行指令重排序。
    Volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
    Synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
    (1)volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别的。
    (2)volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。
    (3)volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
    (4)volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
16. 什么是线程池,如何使
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高了代码的执行效率。
在JDK的java.util.concurrent.Executors中提供了生成多种线程池的静态方法:
ExecutorService newCanchedThreadPool = Excutors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Excutors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然后调用他们的execute方法即可。
17. 线程池的好处
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
18. 线程池的启动策略
(1)线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
(2)当调用execute()方法添加一个任务时,线程池会做如下判断:
a.如果正在执行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b.如果正在执行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
c.如果这时候队列满了,而且正在执行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务;
d.如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告诉调用者“不能再接受任务了”。
(3)当一个线程完成任务时,它会从队列中取下一个任务来执行。
(4)当一个线程无事可做,超过一定的时间(keyAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到coorPoolSize的大小。
19. 如何控制某个方法允许并发访问线程的大小?
可以使用Semaphore控制,就是信号,初始化n个信号,在线程中,运行semaphore.acquire()申请请求,这样最多只能有n个线程并发访问,多余n个线程
时就排队等待。线程完成后释放信号,这样新的线程就可以使用了。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值