Java并发及多线程常见面试题汇总

目录

1. 并行和并发有什么区别?

2.什么是线程死锁?

3.守护线程是什么?

4.创建线程有哪几种方式?

5.说一下Callable接口与Runnable接口的区别?

6.说说sleep()方法和wait()方法区别和共同点?

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

8.谈谈你对volatile的理解?

9.那你写一个Demo来证明一下什么叫保证了可见性?

10.原子性是什么意思?不保证原子性写个demo举例一下?volatile为什么不能保证原子性?而且为什么每次一运行的话,它的值都低于2w呢?如何解决原子性?

11.禁止指令重排是什么?了解吗?

12.你在哪些地方用到过volatile?

13.AtomicInteger这个玩意它的底层原理知道吗?那再跟我解释一下什么是CAS?谈谈你对UnSafe类的理解?CAS缺点?

14.原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?怎么解决ABA问题?

15.我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案?

16.JMM你谈谈?

17.谈谈synchronized和ReentrantLock的区别?

18.synchronized 和 volatile 的区别是什么?

19.线程池用过吗?线程池如何使用?线程池几个重要参数介绍?说说线程池的底层工作原理?

20.线程池的拒绝策略请你谈谈?你在工作中单一的/固定数的/可变你的三种创建线程池的方法,你用哪个多?

21.CountDownLatch/CyclicBarrier/Semaphore使用过吗?

22.什么是锁?说一下锁升级?

23.synchronized底层原理?

24.sleep(0)表示什么含义?

25.如何合理分配线程池的大小?

26.synchronized八锁理论?

27.LockSupport是什么?

28.ReentrantLock实现原理?简单说下aqs?

29.其他后续总结

1. 并行和并发有什么区别?

 答:并行是指两个或多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。

2.什么是线程死锁?

 答:两个或多个线程各自占有一些资源,都在等待对方释放资源,都停止执行的情况。

3.守护线程是什么?

 答:是个服务线程,例如GC就是守护线程。

4.创建线程有哪几种方式?

 答:传统的是继承Thread类和实现Runnable接口,java5以后又有实现Callable接口和通过java的线程池来获得。

5.说一下Callable接口与Runnable接口的区别?

 答:Runnable接口无返回值,不会抛出异常,方法名是run,Callable接口有返回值,是一个泛型,会抛出异常,方法名为call。

6.说说sleep()方法和wait()方法区别和共同点?

 答:两者的主要区别在于sleep()方法没有释放锁,而wait()方法释放了锁

        两者的共同点就是都可以使线程停止运行。sleep()通常用于暂停执行,而wait()通常用于线程通信。调用wait()方法后,线程不会自动苏醒,线程进入等待状态,需要等待其他线程唤醒,才能再次进入Runnable状态。而sleep()方法执行完成后,线程会自动苏醒。

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

 答:调用start()方法会启动线程并使线程进入就绪状态,直接执行run()方法的话不会以多线程的方式执行,会被当做main线程下的普通方法去执行。

8.谈谈你对volatile的理解?

 答:volatile是java虚拟机提供的轻量级的同步机制,保证可见性、不保证原子性、禁止指令重排。

9.那你写一个Demo来证明一下什么叫保证了可见性?

 答:假如int number = 0; number变量之前没有添加volatile关键字修饰,没有可见性

class Data{
	volatile int number = 0;
	public void addNumber() {
		this.number = 60;
	}
}
public class VolatileDemo1{
	public static void main(String[] args) {
		Data data = new Data();
		new Thread(() ->
		{
			System.out.println(data.number);
			try {
				Thread.sleep(3000);
			}catch(Exception e) {
				
			}
			data.addNumber();
			System.out.println(data.number);
		},"AAA"
		).start();
		while(data.number==0) {
			
		}
		System.out.println(data.number+"over");
	}
}

10.原子性是什么意思?不保证原子性写个demo举例一下?volatile为什么不能保证原子性?而且为什么每次一运行的话,它的值都低于2w呢?如何解决原子性?

 答:原子性其实就是看最终一致性能否保证,就是某个线程在做某个业务的时候,中间不可以被加塞或者分割,要么同时成功,要么同时失败。可以使用java.util.concurrent.atomic包下的AtomicInteger来解决。因为number++在多线程环境下是不安全的,通过查看它的字节码指令文件,number++底层被分成了三个指令,执行getfield拿到原始n,执行iadd进行加1操作,然后执行putfield把更新后的值写回,很多值在putfield这步写回去的时候,可能线程被挂起了,刚好也没收到最新值的通知,有一个纳秒级别的时间差,导致出现了写覆盖,导致最后的结果比预期的要小。

  不保证原子性demo举例:

class Data1{
  volatile int number = 0;
  public void addPlusPlus(){
    number++;
   } 
}

public class VolatileDemo2{
	public static void main(String[] args) {
	    Data1 data = new Data1();
	    for(int i=1;i<=20;i++){
	      new Thread(() -> {
	       for(int j=1;j<=1000;j++){
	       data.addPlusPlus(); 
	     }
	  },String.valueOf(i)).start(); 
	  }
	  while(Thread.activeCount()> 2){
	     Thread.yield();
	   }
	  System.out.println(Thread.currentThread().getName()+" "+data.number);
	}

}

   解决原子性demo如下:

import java.util.concurrent.atomic.AtomicInteger;

class Data1 {
	AtomicInteger atomicInteger = new AtomicInteger();

	public void addPlusPlus() {
		atomicInteger.getAndIncrement();
	}

}

public class VolatileDemo {
	public static void main(String[] args) {
		Data1 data = new Data1();
		for (int i = 1; i <= 20; i++) {
			new Thread(() -> {
				for (int j = 1; j <= 1000; j++) {
					data.addPlusPlus();
				}
			}, String.valueOf(i)).start();
		}
		while (Thread.activeCount() > 2) {
			Thread.yield();
		}
		System.out.println(Thread.currentThread().getName() + " " + data.atomicInteger);
	}

}

11.禁止指令重排是什么?了解吗?

 答:了解过,单线程环境下最终执行结果和代码顺序执行的结果一致。多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测。加volatile关键字可以避免多线程环境下乱序执行的现象。

 案例如下:

public class Test{
  int a = 0;
  boolean flag = false;
  public void method01() {
	  a=1;//语句一
	  flag = true;//语句二,多线程环境下可能语句二先执行
  }
  
  public void method02() {
	  if(flag) {
		  a = a+5;
	  }
	  System.out.println(a);//6或者5
  }

}

12.你在哪些地方用到过volatile?

 答:单例模式下用过volatile

  案例如下: 

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){

    }
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

13.AtomicInteger这个玩意它的底层原理知道吗?那再跟我解释一下什么是CAS?谈谈你对UnSafe类的理解?CAS缺点?

 答:知道,AtomicInteger底层是CAS。CAS全称为Compare And Swap,比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。UnSafe类是根据内存偏移地址来获取数据的。CAS缺点是多次循环比较时间长开销大,只能保证一个共享变量的原子性,要想保证多个就加锁。

14.原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?怎么解决ABA问题?

 答:比如线程one从工作内存中取出A,这时线程two也从内存中取出A,并且线程two将A改成了B,然后线程two又将数据变成A,这时候线程one进行CAS操作发现主内存中仍然是A,然后线程one操作成功。尽管线程one操作成功,但过程中还是出现了问题。知道,AtomicReference<V> 。就是增加时间戳,当时间戳跟要对比的时间戳不一致的话,就说明这个数据在中间被修改过。

15.我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案?

 答:会导致java.util.ConcurrentModificationException并发修改异常,解决方案:

                  1.new Vector<>()

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

                  3.new CopyOnWriteArrayList()  //CopyOnWriteArraySet<>()  ConcurrentHashMap<K,V>()

	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		for (int i = 1; i <= 30; i++) {
			new Thread(() -> {
				list.add(UUID.randomUUID().toString().substring(0, 8));
				System.out.println(list);
			}, String.valueOf(i)).start();

		}
	}

16.JMM你谈谈?

 答:JMM是java内存模型,有三大特性,分别是可见性、原子性、有序性。可见性就是多个线程从主内存中拷贝值到自己的工作内存中去,若有一个线程修改了拷贝过来的这个值,并将修改好的值返回给了主内存,那么此时其他线程不知道主内存的值已经修改了,此时就必须有一种机制来及时通知其他线程,这样及时通知的情况,就是JMM的可见性。

17.谈谈synchronized和ReentrantLock的区别?

 答:1、synchronized是JVM层面,它是java的关键字,ReentrantLock是API层面的具体类,它是java5以后新出的一个类;

        2、synchronized不需要手动释放锁而ReentrantLock需要手动释放锁;

        3、synchronized是非公平锁,ReentrantLock默认是非公平锁,但也支持公平锁,通过构造方法去设置;

        4、synchronized不支持精确唤醒,ReentrantLock可支持精确唤醒。

        5、synchronized不能中断,lock是可以中断的,主要是设置超时trylock()的方法。

18.synchronized 和 volatile 的区别是什么?

答:volatile是java虚拟机提供的轻量级的同步机制,所以volatile性能比synchronized要好,但是volatile只能用于变量,而synchronized关键字可以修饰方法以及代码块。

19.线程池用过吗?线程池如何使用?线程池几个重要参数介绍?说说线程池的底层工作原理?

答:用过,线程池有三种常见的创建方式,第一种是Executors.newFixedThreadPool(创建一个定长的线程池),第二种是Executors.newCachedThreadPool(一池多线程,可扩容的线程池),第三种是Executors.newSingleThreadExecutor。线程池还有七大参数分别是corePoolSize常驻核心线程数,maximumPoolSize线程池同时容纳执行的最大线程数,keepAliveTime空闲线程的存活时间,当存活时间达到keepAliveTime时,多余的线程会被销毁直到剩下corePoolSize个线程为止。unit:keepAliveTime的单位,handler:拒绝策略,当阻塞队列满了且当前工作线程数大于等于线程池最大线程数,就会启用拒绝策略。workQueue阻塞队列,被提交但未被执行的任务,threadFactory表示生产线程的线程工厂,一般用默认即可。线程池底层工作原理,在创建了线程池以后,等待提交过来的任务请求,当添加任务请求时,线程池会判断,如果当前工作线程数小于corePoolSize,那么就创建线程来执行这个任务,如果当前工作线程数大于等于corePoolSize那就将这个任务放入阻塞队列中去,如果当前阻塞队列已满但还小于线程的最大线程数,就创建非核心线程去执行这个任务,如果阻塞队列已满且工作线程数大于等于maximumPoolSize,那么就启用拒绝策略。当一个线程完成任务时,它会从队列中取下一个任务来执行。当一个线程无事可做超过一定的时间(keepAliveTime),会判断,如果当前工作线程数大于corePoolSize,那么就销毁掉当前线程。

20.线程池的拒绝策略请你谈谈?你在工作中单一的/固定数的/可变你的三种创建线程池的方法,你用哪个多?

答:有四大拒绝策略分别是AbortPolicy直接抛出异常阻止系统正常运行,CallerRunsPolicy将任务回退到调用者,DiscardOldestPolicy抛弃队列中等待最久的任务,再把当前任务加入队列中尝试再次提交,DiscardPolicy直接丢弃任务。自定义线程池,不用Executors去创建,用ThreadPoolExecutor去创建。

21.CountDownLatch/CyclicBarrier/Semaphore使用过吗?

答:CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞.其他线程调用countDown方法计数器减1,当计数器的值变为0,因调用await方法被阻塞的线程会被唤醒,继续执行,CyclicBarrier类似于召唤神龙,等到最后一个线程到达屏障时,屏障才会开门,线程进入屏障通过await()方法。Semaphore是信号量,类似于抢车位,acquire()方法相当于抢到车位、release()方法相当于离开车位。

22.什么是锁?说一下锁升级?

答:并发环境下,多线程会对同一资源进行争抢,可能会导致数据不一致的问题。在java中每个对象都有一把锁,这把锁就存放在对象头中。java对象包含了三个部分对象头、实例数据以及对齐填充字节。对象头中又包含了Mark Word以及Class Pointer,MarkWord里存放锁信息,锁标志位是01,偏向锁位为0时,表示无锁。锁标志位是01,偏向锁位为1时,表示有一个线程正在持有这把锁,偏向锁,它会保存线程ID,等到下次线程来就可以直接获取锁。当有其他线程也想获取锁,锁升级为轻量级锁,锁标志位为00。当多个线程开始竞争,没抢到的自旋,当等待时间过长,或者自旋次数超过10次,升级为重量级锁,锁标志位为10。

为什么要升级重量级锁?

因为自旋消耗cpu资源,cpu空转,重量级锁会进入阻塞队列等待不耗cpu资源。

无锁、偏向锁、轻量级锁、重量级锁。

23.synchronized底层原理?

答:synchronized被编译后会生成monitorenter和monitorexit两个字节码指令。monitorenter指令指向同步代码块的开始位置,monitorexit指令则指向同步代码块的结束位置。在执行monitorenter指令时会尝试获取对象的锁,如果锁的计数器为0,则表示可以被获取,获取后将锁计数器加1。在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那么当前线程就要阻塞等待,直到锁被另一个线程释放为止。

24.sleep(0)表示什么含义?

答:表示重新让各个线程竞争cpu资源,包括这个线程自己。

25.如何合理分配线程池的大小?

答:N为CPU的核数(java中可以根据Runtime.getRuntime().availableProcessors()查看CPU核数)

如果是CPU密集型应用,则线程池大小设置为N+1

如果是IO密集型应用,则线程池大小设置为2N+1

26.synchronized八锁理论?

答:一个对象如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程只能等待,锁的是当前对象this。加个普通方法后发现和同步锁无关(三锁)。换成两个对象后,不是同一把锁了,情况立刻变化(四锁)。对于静态同步方法,锁的是当前类的Class对象。(五六七八锁  【全局锁】)

27.LockSupport是什么?

答:LockSupport是juc并发包下的一个类,它是用于等待和唤醒机制的。解决了传统的等待唤醒机制的两个问题,传统的等待唤醒机制有Object类下的wait()和notify(),ReentrantLock类下的await()和singnal()。它们的共同的两个问题是都必须在锁块中,并且等待必须在唤醒之前执行。而LockSupport中的park()和unpark()就解决了这一问题,它没有锁块的限制,并且唤醒可以在等待之前执行。

延伸问题1:为什么唤醒可以在阻塞之前执行?

答:因为底层有凭证permit这样一个概念,默认是0。调用unpark()时加一,相当于获取了一个凭证,所以再调用park()方法消费掉一个凭证就不会阻塞了。

延伸问题2:调用两次unpark(),两次park()程序处于什么状态?为什么?

答:程序还处于阻塞状态,因为多次调用unpark()方法只能获取到一个凭证,所以两次park()方法后会处于阻塞状态。

28.ReentrantLock实现原理?简单说下aqs?

答:它的底层是aqs,aqs是抽象的队列同步器。aqs使用一个volatile的int类型成员变量state来表示同步状态,通过一个双向队列将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,用CAS去完成对state值的修改。Node节点有个int属性为waitStatus,它表示当前节点在队列中的状态。以非公平锁举例子,先进来一个线程A调用lock()后调用的是底层的NonfairSync类的lock(),刚进来state为0,走if代码块CAS先设置state为1,并且当前持有线程为A。当下一个线程B进来后就会走else代码块下的acquire(1)方法,进去后有三个重要的方法,分别是tryAcquire()、addWaiter()以及acquireQueued()。

延伸问题1:AQS里面有个变量叫State,它的值有几种?

答:三种,没占用是0,占用了是1,大于1是可重入锁。

延伸问题2:如果AB两个线程进来了以后,请问这个总共有多少个Node节点?        

答:三个

29.其他后续总结

1、synchronized加到静态方法上是给类上锁,加到实例方法上是给对象上锁。

2、new 对象的过程可以分为3步,首先为对象分配内存空间,然后初始化这个对象,最后将变量指向分配的内存地址。多线程环境下可能引起指令重排,所以volatile关键字加上就可以禁止指令重排。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值