线程基础题总结

基础线程面试题总结

只是思路的整理,具体回答请百度


有几种实现线程的方式?

  1. 从不同的角度看,会有不同的答案。
  2. 典型答案是两种
  3. 我们看原理,两种本质都是一样的
  4. 具体展开说其他方式
  5. 结论

实现Runnable接口和继承Thread类哪种更好?

  1. 从代码架构角度
  2. 新建线程的损耗
  3. Java不支持双继承

一个线程两次调用start()方法会出现什么情况?为什么?

  1. 会报错,非法的线程状态,线程六个状态的转换问题
  2. start()源码解析:
    启动新线程检查线程的状态:threadStatus(线程状态是否为0)
    加入线程group组、调用start0()本地方法

既然start()方法会调用run()方法,为什么我们选择调用start()方法,而不是直接调用run()方法呢?

  1. start()方法的作用
  2. 直接调用run()方法相当于是一个接口中的普通方法

如何停止线程

  1. 原理:用interrupt来请求、好处
  2. 想停止线程,要请求方、被停止方、子方法被调用方相互配合
  3. 最后再说错误的方法: stop/suspend已废弃, volatile的boolean无法处理长时间阻塞的情况

如何处理不可中断的阻塞

  1. 针对特定的情况使用特定的方法区处理

线程有哪几种状态?生命周期是什么?

  1. 线程间的状态转化
    在这里插入图片描述

生产者与消费者设计模式(wait、notify)

package Thread;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 *  用 wait/notify来实现
 * @author 陈觉如
 *
 */
public class ProducerConsumerModel {
	public static void main(String[] args) {
		EventStorage eventStorage = new EventStorage();
		Producer producer = new Producer(eventStorage);
		Consumer consumer =new Consumer(eventStorage);
		
		new Thread(producer).start();
		new Thread(consumer).start();
	}
}

class Producer implements Runnable{
	private EventStorage storage;
	public Producer(EventStorage storage) {
		this.storage = storage;
	}
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			storage.put();
		}
	}
}

class Consumer implements Runnable{

	private EventStorage storage;
	public Consumer(EventStorage storage) {
		this.storage = storage;
	}
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			storage.take();
		}
	}
}
class EventStorage{
	 private int maxSize;
	 private List<Date> storage;
	 
	 public EventStorage(){
		 maxSize = 10;
		 storage = new ArrayList<Date>();
	 }
	 
	 public synchronized void put() {
		if (storage.size() == maxSize) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		storage.add(new Date());
		System.out.println("仓库里有了"+storage.size()+"个产品。");
		notify();
	 }
	 
	 public synchronized void take() {
		if (storage.size() == 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("拿到了"+storage.get(0)+",现在仓库还剩下"+storage.size());
		storage.remove(0);
		notify();
	}
}

两个线程交替打印0~100的奇偶数(wait、notify)

package Thread;

/**
 * 两个线程交替打印0~100的奇偶数
 * 用synchronized关键字实现
 * @author 陈觉如
 */
public class WaitNotifyPrintOddEvenSyn {

	private static int count;
	private static final Object lock = new Object();
	
	public static void main(String[] args) {
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while( count < 100) {
					synchronized (lock) {
						if ( (count & 1) == 0 ) {
							System.out.println(Thread.currentThread().getName()+":"+count++);
						}
					}
				}
			}
		},"偶数").start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while( count < 100) {
					synchronized (lock) {
						if ( (count & 1) == 1 ) {
							System.out.println(Thread.currentThread().getName()+":"+count++);
						}
					}
				}
			}
		},"奇数").start();
		
	}
	
}

package Thread;
/**
 * 两个线程交替打印0~100的奇偶数
 * 用 wait 和 notify 关键字实现
 * @author 陈觉如
 *
 */
public class WaitNotifyPrintOddEvenWait {
	static class TurningRunner implements Runnable{
		private static int count;
		private static final Object lock = new Object();
		@Override
		public void run() {
			
			while (count <= 100) {
				synchronized (lock) {
					System.out.println(Thread.currentThread().getName()+":"+count++);
					lock.notify();
					if (count <= 100) {
						try {
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
			
		}
		
	}
	public static void main(String[] args) {
		new Thread(new TurningRunner(),"偶数").start();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new TurningRunner(),"奇数").start();
	}
}

为什么wait()需要在同步代码块内使用,而sleep()方法不需要

  1. 为了让通信的可靠,防止死锁永久等待的发生,wait() 与 notify()方法
  2. sleep()只是指定自己线程的,与其他线程关系不大

为什么线程通信的方法wait()、notify()和notifyAll()被定义在Object类中?而sleep定义在Thread类里?

  1. wait()、notify()和notifyAll()是锁级别的操作,是属于每一个对象的,对象的对象头中都保存了锁的状态,锁是绑定了对象,而不是线程

wait方法是属于Object对象的,那调用Thread.wait会怎么样?

  1. Thread.wait()可以调用,但是Thread类比较特殊,在线程退出的时候,会自动调用notify方法,所以不适合

如何选择用notify还是nofityAll ?

  1. 唤醒一个或者多个线程

notifyAll之 后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

  1. 回到最初始的状态Runnable状态

用suspend(和resume()来阻塞线程可以吗?为什么?

  1. 不可以,不安全弃用了
  2. 推荐wait\notify去阻塞线程

wait/notify、sleep异同 (方法属于哪个对象?线程状态怎么切换? )

  1. 相同:阻塞、响应中断
  2. 不同:同步方法中、释放锁、指定时间、所属类

在join期间,线程处于哪种线程状态?

  1. waiting 主线程等待子线程

守护线程和普通线程的区别

  1. 守护线程作用:给用户线程提供服务
  2. 守护线程的特性:线程类型默认继承父线程、一般被jvm自动启动、不影响jvm的退出
  3. 整体无区别、唯一区别在于jvm的退出、作用

我们是否需要给线程设置为守护线程?

  1. 不应该这么做,会变的非常危险,因为守护线程和普通线程的区别

java异常体系

如何全局处理异常?为什么要全局处理?不处理行不行?

  1. 用实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e)进行处理操作
  2. 在线程中设置默认的获取器,Thread.setDefaultUncaughtExceptionHandler(获取器对象);

run方法是否可以抛出异常?如果抛出异常,线程的状态会怎么样?

  1. 不可以,run方法底层没有实现向外抛的操作,只能try-catch

一共有哪几类线程安全问题?

  1. 运行结果错误
  2. 死锁等活跃性问题
  3. 对象发布和初始化的时候的安全问题

哪些场景需要额外注意的线程安全问题?

  1. 访问共享的变量或资源,会有并发风险
  2. 所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题
  3. 不同的数据之间存在捆绑关系的时候
  4. 我们使用其他类的时候,如果对方没有声明自己的线程是安全的,大概率也会存在并发问题

什么是多线程的上下文切换?

上下文切换可以认为是内核(操作系统的核心)在CPU上对于进程(包括线程)进行以下的活动:
(1)挂起一个进程,将这个进程在CPU中的状态(上下文)存储于内存中的某处
(2)在内存中检索下一个进程的上下文并将其在CPU的寄存器中恢复
(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程

JMM应用的实例:单例模式8种写法、单例和并发的关系

  1. 饿汉式(静态常量)[可用] JVM加载的时候保证线程的安全性
/**
 * 饿汉式(静态常量)[线程安全]
 * @author 陈觉如
 *
 */
public class Singleton2 {
	private final static Singleton2 INSTANCE = new Singleton2();

	private Singleton2() {	
		
	}
	public static Singleton2 getInstance() {
		return INSTANCE;
	}
}
  1. 饿汉式(静态代码块)[可用] JVM加载的时候保证线程的安全性
package single;
/**
 * 饿汉式(静态代码块)[线程安全]
 * @author 陈觉如
 *
 */
public class Singleton1 {
	
	private final static Singleton1 INSTANCE ;
	
	static {
		INSTANCE = new Singleton1();
	}
	private Singleton1() {	
		
	}
	public static Singleton1 getInstance() {
		return INSTANCE;
	}
}

  1. 懒汉式(线程不安全)
package single;
/**
 * 懒汉式(线程不安全)[不可用]
 * @author 陈觉如
 *
 */
public class Singleton3 {

	private static Singleton3 instance;
	private Singleton3() {
		
	}
	public static Singleton3 getInstance() {
		if (instance == null) {
			//这里不安全,多个线程可能创建多个实例
			instance = new Singleton3();
		}
		return instance;
	}
}

  1. 懒汉式(线程安全)
package single;
/**
 * 懒汉式(线程安全)[不推荐]
 * @author 陈觉如
 * 
 * 效率太低
 */
public class Singleton4 {

	private static Singleton4 instance;
	private Singleton4() {
		
	}
	//多个线程访问的时候,无法及时相应
	public synchronized static Singleton4 getInstance() {
		if (instance == null) {
			instance = new Singleton4();
		}
		return instance;
	}
}

  1. 懒汉式(线程不安全)同步代码块
package single;
/**
 * 懒汉式(线程不安全)[不推荐]
 * @author 陈觉如
 * 
 * 效率太低
 */
public class Singleton5 {

	private static Singleton5 instance;
	private Singleton5() {
		
	}
	public static Singleton5 getInstance() {
		if (instance == null) {
			//这里不安全,多个线程到达这个,会等待创建实例
			synchronized (Singleton5.class) {
				instance = new Singleton5();
			}
		}
		return instance;
	}
}

  1. 双重检查[懒汉式]
package single;
/**
 * 双重检查(线程安全)[推荐面试使用]
 * @author 陈觉如
 * 
 * 线程安全,延迟加载,效率高
 */
public class Singleton6 {

	private volatile static Singleton6 instance;
	//新建对象实际上有三个过程,重排序会带来NPE
	//创建一个空对象,调用构造方法, 对象赋给引用
	
	private Singleton6() {
		
	}
	public static Singleton6 getInstance() {
		if (instance == null) {
			synchronized (Singleton6.class) {
				if (instance == null) {
					instance = new Singleton6();
				}
			}
		}
		return instance;
	}
}

  1. 静态内部类[懒汉式]
package single;
/**
 * 静态内部类(线程安全)[推荐使用]
 * @author 陈觉如
 * 
 * 线程安全,延迟加载,效率高
 */
public class Singleton7 {
	
	private Singleton7() {
		
	}
	private static class SingletonInstance{
		private final static Singleton7 INSTANCE = new Singleton7();
	}
	public static Singleton7 getInstance() {
		return SingletonInstance.INSTANCE;
	}
}

  1. 枚举单例
package single;
/**
 * 单例枚举(线程安全)[推荐使用]
 * @author 陈觉如
 * 
 */
public enum Singleton8 {
	INSTANCE;
	//下面定义方法
}

饿汉式的缺点、懒汉式的缺点,为什么要用double-check?为什么双重检查模式要用valatile?

上面的例子

什么是Java内存模型?

  1. 为什么需要JMM?C语音不存在内存模型的概念,依赖处理器,不同处理器结果不一样,无法保证并发安全,需要一个标准,让多线程运行的结果可预期
  2. 是一种规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序
  3. 是工具类和关键字的原理,例如:synchronized、volatile、Lock等原理,底层都是JMM指定什么时候用内存栅栏
  4. 最主要的就是重排序、内存可见性、原子性
  5. 重排序:CPU根据自己的判断可能会对某些代码的指令进行重排序,会导致实际执行顺序和代码在java文件中的顺序不一致,此外还有编译器优化、内存的“重排序”
  6. 可见性:CPU缓存结构,有多级缓存,会导致读的数据过期;

volatile 和 synchronized的异同?

  1. volatile是一种同步的机制,比synchronized或者Lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为
  2. 变量修饰成volatile,JVM就知道这个变量可能会被并发修改
  3. volatile是用来同步的保证线程安全的,做不到synchronized那样的原子保护
  4. volatile适用于一个共享变量自始至终只被各个线程赋值,而没有其他的操作,因为赋值自身有原子性;还适用于作为刷新之前变量的触发器
  5. volatile的作用:可见性:读一个valatile变量之前,需要现使相应的本地缓存失效,这样就必须到主内存读取最新的值,写一个volatile属性会立即刷入到主内存;禁止指令重排序优化
  6. valatile的读写是无锁的,不能替代synchronized,因为它没有提供原子性和互斥性

什么是原子操作?java中有哪些原子操作?生成对象的过程是不是原子操作?

  1. 一系列的操作,要么全部执行成功,要么全部不执行
  2. 除long、double之外的基本类型和所有引用类型的赋值操作、java.concurrent.Atomic.*包中的所有类的原子操作

内存可见性?

  1. 可见性:CPU缓存结构,有多级缓存,会导致读的数据过期;
  2. JMM的抽象:主内存和本地内存:所有变量都存储在主内存中,同时每个线程也有自己独立的工作内存,工作内存的变量内容是主内存中的拷贝;
  3. 线程不能直接读写主内存中的变量,而是只能操作自己工作内存中的变量,然后再同步到主内存中
  4. 主内存是多个线程共享的,但线程间不共享工作内容,线程通信通过主内存中转来完成

64位的double 和 long 写入的时候是原子的吗

  1. 对于64位的值的写入,是分为两个32位的操作进行写入
  2. 商用Java虚拟机中不会出现这种问题

写一个必然死锁的例子,生产中什么场景下会发生死锁?

package deadlock;
/**
 * 必定发生死锁的情况
 * @author 陈觉如
 *
 */
public class MustDeadLock implements Runnable{
	int flag = 1;
	static Object o1 = new Object();
	static Object o2 = new Object();
	@Override
	public void run() {
		System.out.println("flag = "+flag);
		if (flag == 0) {
			synchronized (o1) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (o2) {
					System.out.println("线程1成功拿到两把锁");
				}
			}
		}else {
			synchronized (o2) {
				synchronized (o1) {
					System.out.println("线程2成功拿到两把锁");
				}
			}
		}
	}
	public static void main(String[] args) {
		MustDeadLock mustDeadLock1 = new MustDeadLock();
		MustDeadLock mustDeadLock2 = new MustDeadLock();
		mustDeadLock1.flag = 1;
		mustDeadLock2.flag = 0;
		new Thread(mustDeadLock1).start();
		new Thread(mustDeadLock2).start();		
	}	
}

在一个方法中获取多个锁,可能会发生死锁

发生死锁的必要条件

  1. 互斥条件
  2. 请求与保持条件
  3. 不剥夺条件
  4. 循环等待条件

如何定位死锁?

  1. jstack命令对程序进行堆栈分析
  2. ThreadMXBean

有哪些解决死锁问题的策略

  1. 避免策略:哲学家就餐的换手方案,转账换序方案
  2. 检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁
  3. 鸵鸟策略

哲学家就餐问题

  1. 服务员检查
  2. 改变一个哲学家拿叉子的顺序
  3. 餐票
  4. 领导调节

实际工程中如何避免死锁?

  1. 设置超时时间(tryLock)
  2. 多使用并发类而不是自己设计锁
  3. 尽量降低锁的使用粒度:用不同的锁而不是一个锁
  4. 如果能使用同步代码块,就不使用同步方法:自己指定锁对象
  5. 给线程起有意义的名字,方便debug和排查
  6. 避免锁的嵌套
  7. 分配资源前先看能不能收回来:银行家算法
  8. 专锁专用

什么是活跃性问题?

  1. 死锁
  2. 活锁
  3. 饥饿
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值