文章目录
一、创建线程的几种方式?
1.继承Thread类,默认是实现Runnable接口的,重写run方法。
2.实现Runnable接口,重写run方法,无返回值。
3.实现Callable接口,重写call方法,有返回值。
4.通过线程池启动多线程
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
二、线程池基本原理
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
三、线程池有哪些(创建线程池的几种方式)
创建可缓存线程池(newCachedThreadPool)
创建指定线程数量的线程池(newFixedThreadPool)
创建指定长度的线程池(newScheduleThreadPool)
创建单线程化的工作者(newSingleThreadExecutor)
四、线程池参数
核心线程数、最大线程数、线程存活时间、线程存活时间的单位、线程工厂、任务队列、拒绝策略
常见的拒绝策略:
1.直接抛出异常,默认策略
2.直接丢弃任务
3.用调用者所在的线程来执行任务
4.丢弃阻塞队列中最靠前的任务,并执行当前任务
如果提交任务时线程池队列已满会时发会生什么?
线程队列满了,但是最大线程数没有满的话,就会新建一个非核心线程去执行该任务。如果核心线程数、最大线程数、任务队列都满了的话,就会执行线程池的拒绝策略,如果一个任务不能被调度执行那么submit()方法将会抛出一个异常
五、线程安全问题的解决方案
三种方案:
1. 同步代码块
syncnized(锁对象){
可能出现线程安全问题的代码,访问了共享数据的daima
}
注意:a.代码块中的锁对象,可以使用任意对象
b.必须保证多线程使用的锁对象是同一个
c.锁对象将同步代码块锁住,只让一个线程在同步代码块中执行
同步中的线程,没有执行完毕不会释放锁,,同步外的线程没有锁对象,进不去同步
thread.sleep() 进入休眠后会释放CPU的执行权,但不会释放锁对象,只有出了同步代码块才会释放锁对象
2. 同步方法
同步方法也会把方法内部的代码锁住,只让一个线程执行,锁对象就是实现类对象(new Runable(),也就是this)
静态的同步方法,锁对象不能是this,this是创建对象之后产生的,静态优先于对象,静态方法的锁对象是本类的class属性–>class文件对象
3. 加锁
Synchronized和Lock:
- Lock加锁解锁都是由native方法实现的,Synchronized的加锁解锁过程都是由JVM管理的。
- 当一个线程使用synchronized获取锁时,而锁被其他线程占用着,那么只能被阻塞,只到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式,在未能获取锁的情况下提供一种退出机制。
- synchronized不需要手动释放锁,Lock需要在Finally中释放锁,不然容易会造成死锁。
- synchronized是非公平锁,ReentrantLock默认是非公平锁,在构造方法传入参数true。
- 公平锁:一个线程池里,保证每个线程都能拿到锁,这个锁就是公平锁。
volatile关键字
- 保证内存可见性:可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。
- 不保证变量的原子性
- 禁止指令重排:指令重排序是JVM为了优化指令、提高程序运行效率。
顺序性:happends-before
synchronized 和 volatile 的区别是什么?
- volatile比synchronized更轻量级。
- volatile没有synchronized使用的广泛。
- volatile不需要加锁,比synchronized更轻量级,不会阻塞线程。
- 从内存可见性角度看,volatile读相当于加锁,volatile写相当于解锁。
- synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。
sleep和wait的区别
- 其他线程调用对象的notify()或者notifyAll()方法式,使用wait()
- 超时使用sleep()
- sleep()不会释放锁,wait()会释放锁
- 有什么办法让wait自己醒过来去抢锁 -----设置时间
悲观锁和乐观锁
悲观锁:悲观的认为总想着有人会修改我的数据,所以为了防止别人修改,每次都需要上锁。导致每次想要拿到数据必须等待拿到锁,所以很浪费时间,如果出现高并发,就会有致命缺陷。
乐观锁:乐观锁总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
六、GUC提供的多线程工具类有哪些
- CountDownLatch 闭锁
- CyclicBarrier 循环栅栏
- Semaphore 信号量
- Exchanger 线程数据交换器
七、多线程情况下,如何保证数据一致性
1.使用synchronized
2.使用Lock锁
3.使用Atomic原子类
4.volatile关键字
八、对线程安全理解(如何保证多线程运行安全)
主要体现在三个方面:原子性、可见性、有序性
- 原子性:同一时间只能有一个线程对数据进行操作,其他线程阻塞等待。
- 可见性:一个线程修改的状态对另一个线程是可见的。
- 有序性:程序执行的顺序按照代码的先后顺序执行 。
导致原因:
- 线程切换带来的原子性问题
- 缓存导致的可见性问题
- 编译优化带来的有序性问题
解决办法:
- 解决原子性:JDK Atomic开头的原子类、synchronized、LOCK
- 解决可见性:synchronized、volatile、LOCK
- 解决有序性:Happens-Before 规则
问题:for循环多次,怎么使用线程保持数据一致
- 保证内存可见性:
可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。 - 禁止指令重排:指令重排序是JVM为了优化指令、提高程序运行效率。