Java常见问题-多线程

  • 现在有 T1、T2、T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行?

  这个多线程问题比较简单,可以用 join 方法实现。

  • 在 Java 中 Lock 接口比 synchronized 块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?

  lock 接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像 ConcurrentHashMap 这样的高性能数据结构和有条件的阻塞。

  • 在 java 中 wait 和 sleep 方法的不同?

最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。Wait 通常被用于线程间交互,sleep 通常被用于暂停执行。

  • 用 Java 实现阻塞队列。

阻塞队列的实现原理
1、生产者向队尾添加元素
2、消费者向队头消费元素
3、添加和消费过程是线程安全的

方法一:使用非阻塞队列PriorityQueue以及wait()和 notify()方法来实现阻塞队列。(调用wait/notify的线程必须持有对象的锁。)

方法二:使用阻塞队列实现方式 实现生产者消费者问题

几种常用的阻塞队列
  1. ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
  3. PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  5. SynchronousQueue:一个不存储元素的阻塞队列。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
  • 用 Java 写代码来解决生产者——消费者问题。

与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在 Java 中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。

在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型。

  • 用 Java 编程一个会导致死锁的程序,你将怎么解决?

这是我最喜欢的 Java 线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写 deadlock free code(无死锁代码?),他们很挣扎。

只要告诉他们,你有 N 个资源和 N 个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n 可以替换为 2,越大的数据会使问题看起来更复杂。通过避免 Java 中的死锁来得到关于死锁的更多信息。

  • 什么是原子操作,Java 中的原子操作是什么?

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间切换到另一个线程。

  • Java 中的 volatile 关键是什么作用?怎样使用它?在 Java 中它跟 synchronized 方法有什么不同?

在java中可以实现可见性的两个关键字 ,是jvm提供的轻量级的同步机制;

 1)使用关键字synchronized

 2)使用关键字volatile

volatile可以保证变量的可见性,但是不能保证复合操作的原子性

volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

Synchronized能够实现多线程的原子性(同步)和可见性。

  • 什么是竞争条件?你怎样发现和解决竞争?

竞争条件:在java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象。

发现问题:当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞争条件。

解决方案:使用线程锁(如果在一个线程对数据进行操作的时候,禁止另外一个线程操作此数据,那么,就能很好的解决以上的问题了。这种操作叫做给线程加锁。)

  • 死锁与活锁的区别,如何避免死锁现象?锁与饥饿的区别?

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

产生死锁的必要条件:

互斥条件:所谓互斥就是进程在某一时间内独占资源。

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。

循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。

饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

Java中导致饥饿的原因:

  1. 高优先级线程吞噬所有的低优先级线程的CPU时间。
  2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
  3. 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法),因为其他线程总是被持续地获得唤醒。
  • 使用Runnable还是Thread?

Java不支持类的多重继承,但允许你实现多个接口。所以如果你要继承其他类,当然是调用Runnable接口就好了。

  • 为什么我们调用 start()方法时会执行 run()方法,为什么我们不能直接调用 run()方法?

start()方法被用来启动新创建的线程,当你调用 start()方法时你将创建新的线程,并且执行在 run()方法里的代码。但是如果你直接调用 run()方法,它不会创建新的线程也不会执行调用线程的代码。

  • Java 中你怎样唤醒一个阻塞的线程?

这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了 IO 阻塞,我并且不认为有一种方法可以中止线程。

如果线程因为调用wait()、sleep()、或者 join()方法而导致的阻塞,你可以中断线程,并且通过抛出 InterruptedException 来唤醒它。

  • 在 Java 中 CycliBarriar 和 CountdownLatch 有什么区别?

CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

  • Java中synchronized 和 ReentrantLock 有什么不同?

相似点:

它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。

不同点:

种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。

而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。

2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象

  • 什么是不可变对象,它对写并发应用有什么帮助?

这个 java 面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么 String 是不可变的。

(不可变对象String)一旦被创建,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁,每次对他们的改变都是产生了新的对象,而(可变对象)就是那些创建后,状态可以被改变的对象.

String类中所有字符串都是常量,数据是无法更改,由于string对象的不可变,所以可以共享。(线程安全,可以不用被synchronize就在并发环境中共享)

StringBuffer代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,可以通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。(线程安全,减少了synchroinzed的使用)

StringBuilder实际上,StringBuilder和StringBuffer基本相似。StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。(线程不安全)

  • 你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?

多线程和并发程序中常遇到的有 Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。

这是大多数基于面试的,而不是基于实际应用的 Java 线程问题。

  • Java程序中如何检测死锁(可以使用jconsole工具)

(1)输入 jps 命令,可以看到 java 的进程 id 列表。
(2)再输入 jstack + 进程ID,可以看到所有的线程栈。
(3)栈列表最下方,可以看到Found one Java-level deadlock既是发生死锁的线程。

  • 序列化和反序列化的原理

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。

  • 为什么需要序列化与反序列化?

(1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中;(持久性)
(2)通过序列化以字节流的形式使对象在网络中进行传递和接收;(可传输,网络)
(3)通过序列化在进程间传递对象;(可传输,进程)

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。
(1)java.io.ObjectOutputStream:表示对象输出流;
它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
(2)java.io.ObjectInputStream:表示对象输入流;
它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;

本文作者:盗梦笔记

本文链接:https://www.cnblogs.com/zhaojinhui/p/14606091.html

版权声明:本作品采用zhaojh许可协议进行许可。

posted @   盗梦笔记  阅读(48)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示