java多线程面试

1、多线程如何同步?

  1. 用synchronized方法锁,还有就是用synchronized同步语句块上锁
  2. 使用ReentrantLock可重入锁进行上锁同步,要记得用finally解锁
  3. 使用join也是一种同步功能,使得线程按照一定顺序执行
  4. 使用ThreadLocal分别给每一个线程分配一个本地内存变量。(例如在用户权限模块),ThreadLocal和前面的上锁机制不同的是:上锁同步用的时间换空间,是访问串行化,对象共享化。而ThreadLocal使用的是空间换时间,访问并行化,对象独享化。
  5. 使用CountDownLatch

2、java中锁的分类:

  1. 内置锁synchronized和显示锁Lock(ReentrantLock):内置锁的上锁和解锁都是隐式自动的,而显示锁需要手动上锁和解锁
  2. 公平锁和非公平锁:公平锁表示线程获取锁的顺序是按照加锁的顺序来分配的,即FIFO。而非公平锁是随机获取锁的。ReentrantLock默认是非公平锁。可以通过new ReentrantLock(true)创建公平锁
  3. 可重入锁和不可重入锁:  "可重入锁"也叫“递归锁”,指的是同一线程外层方法获得锁之后 ,内层递归方法仍然有获取该锁的代码,可重入锁的最大作用是可以避免死锁,因此可以在递归中使用。synchronized锁是重入锁,因此在一个synchronized内部调用本类的其他synchronized内容时,可以得到锁,例如子类synchronized代码中能够执行父类中的synchronized代码。ReentrantLock也是可重入锁。 不可重入锁又叫自旋锁,与可重入锁不同,不可递归调用,不然会发生死锁。java没有自旋锁API,是一种技术。一般是因为线程间的切换时间大于线程等待锁的时间,此时使用自旋锁比较合算,在短暂时间内可以有效提高效率,但是时间长的话会浪费cpu。
  4. 互斥锁和共享锁:互斥锁(也叫独占锁),即两个线程获得锁是互斥的,一个线程获得了锁,其他线程在此线程释放锁之前是不能获得锁的,如常用的ReentrantLock。共享锁,则允许多个线程同时获取锁,并发访问共享资源,如:ReadWriteLock读写锁,读读共享,写写互斥,读写互斥,写读互斥。
  5. 悲观锁和乐观锁:主要是在数据库层面的概念。悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。数据库的for update是悲观锁操作,以及前面列举的锁,有点是安全性高,缺点是会阻塞。乐观锁一般是在业务层面,在读取数据的时候读取整行数据,或者读多一个版本号(时间戳也可以),然后在写数据时与原来的数据进行比较,有点是效率高,如果连两个事务同时提交更新,就会破坏数据安全。
  6. 读写锁:读写锁分为读锁和写锁,多个读锁之间是不需要互斥的(读操作不会改变数据,如果上了锁,反而会影响效率),写锁和写锁之间需要互斥,也就是说,如果只是读数据,就可以多个线程同时读,但是如果你要写数据,就必须互斥,使得同一时刻只有一个线程在操作。
  7. 信号量。

3、suspend和resume:暂停和唤醒线程。这两个方法配套使用,和wait和notify的区别是:suspend暂停后会阻塞(sleep也一样),阻塞时不会释放占用的锁,而wait会自动释放锁。suspend容易造成死锁问题,不推荐使用。

4、

5、什么是java内存模型JMM?

答:JMM决定了一个线程对共享变量进行写入时,对另一个线程是否可见。线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

6、常用并发包:

  • CountDownLatch计数器。可以让指定线程等待其他线程执行完毕再继续执行。主要使用countDown()来减一。
  • CyclicBarrier循环屏障。通过它可以实现让一组线程等待至某个状态之后再全部同时执行。使用await()方法来减一。
  • Semaphore信号量。可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。availablePermits()可以用来获取当前许可的个数。具体使用场景类似比如停车场,availablePermits()获得停车位,acquire() 占用停车位, release() 开车离开停车场,释放资源(停车位)。

7、原子类。java.util.concurrent.atomi包。基于CAS算法实现了区别于synchronouse同步锁的一种乐观锁。例如AtomicInteger,AtomicLong,AtomicIntegerArray,AtomicBoolean。

8、CAS和AQS的理解。

  • CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制(主要是因为减少了线程的状态切换),CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。优点:无锁,高并发下有更好的性能,同时也不会产生死锁。2、缺点:1、存在ABA问题。ABA的解决方法和乐观锁很像,使用版本号就可以解决。2、循环时间长开销大。3、只能保证一个共享变量的原子操作。可以用循环进行cas的自旋,但是会导致缺点2的出现
  • AQS(AbstractQueuedSynchronizer),是一个用于构建锁和同步器的框架。

9、多线程要保证的三大特性

  • 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  • 有序性:程序执行的顺序按照代码的先后顺序执行。-->引出重排序的概念:编译器和处理器可能会为了优化对代码进行重排序,as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是多线程情况下,两个线程之间的数据并没有直接依赖关系,所以在各自的线程里可能会进行各自的重排序,导致了多线程程序的语义被破坏,改变了结果。

10、线程池相关

11、Callable

12、Future

猜你喜欢

转载自blog.csdn.net/qq_16979575/article/details/80364774