多线程
如何创建线程
- 继承Thread类 重写run方法
- 实现Runnable接口,重写run方法. 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的
- 实现Callable
- 线程池
start()和run ()的区别
run方法是在当前主线程中执行没有开启新的子线程,start是开启子线程执行任务
Thread和Runable的区别:
- java是单继承,如果一个类继承了父类就没办法再去继承Threa类,所以是需要实现Runable
- Tread实现了Runnable接口
- 实现Runnable资源可以共享
实现Runnable接口比继承Tread类所具有的优势
- 适合多个相同的程序代码的线程去共享一个资源
- 可以避免java中单继承的局限性
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立
- 线程池只能放入实现Runable或callable类线程,不能直接放入继承Tread类
线程的状态
- New(新建) : 线程刚被创建,但是并未启动
- Runnable(可运行) : 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,者取决于操作系统CPU处理器
- BLOCKED(锁阻塞) : 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入blocked状态; 当该线程持有锁时,该线程将变成Runnable状态
- WAITING(无线等待) : 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态. 进入这个状态后市不能自动唤醒的,必须等待另一个线程调用nottify或者notifyAll方法才能唤醒.
- TIMED_WAITING(计时等待) : 同waiting状态, 有几个方法有超时参数,调用他们将进入Timed Waiting状态.这一状态将一直保持到超时期满或者接受到唤醒通知,带有超时参数的常用方法有 Thread.sleep , Object.wait.
- TERMINATED(被终止) : 因为run方法正常退出而死亡, 或者因为没有捕获的异常终止了run方法而死亡
线安全:
多个线程同时操作一个变量(对象), 导致数据不一致
判断当前程序中是否存在线程安全问题
- 是否存在多线程环境
- 在多线程环境下是否存在共享变量
- 在多线程环境下是否存在共享变量"写"操作
死锁:
嵌套两把不同的锁,造成了死锁的出现
JMM内存模型
- JMM让java程序与硬件指令进行了隔离
- JMM把内存分为两块,一块是私有线程的工作区域(工作内存/本地内存),一块是所有线程的共享区域(主内存)
- 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
Java并发编程三大特性:
- 原子性 : 一个线程在cpu中操作不可暂停,也不可中断,要不执行完成,要不不执行
- 内存可见性: 多个线程访问同一变量时, 一个线程修改这个变量的值,其他线程能够看到修改之后的值, 默认jmm对工作内存中的变量是不可见的
加volatil关键字 - 有序性 : 程序的执行顺序按照代码的先后顺序执行
java线程六种状态
wait来自object类,sleep来自thread类,wait会释放锁,sleep不会释放锁,wait必须在同步代码块中使用,sleep在任何地方都可以睡,
线程池:
java的线程池支持主要通过ThreadPoolExecutor来实现,我们使用的ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的
线程池同样有五种状态:Running, SHUTDOWN, STOP, TIDYING, TERMINATED。
变量ctl定义为AtomicInteger ,记录了“线程池中的任务数量”和“线程池的状态”两个信息。共32位,其中高3位表示”线程池状态”,低29位表示”线程池中的任务数量”。
● RUNNING:处于RUNNING状态的线程池能够接受新任务,以及对新添加的任务进行处理。
● SHUTDOWN:处于SHUTDOWN状态的线程池不可以接受新任务,但是可以对已添加的任务进行处理。
● STOP:处于STOP状态的线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
● TIDYING:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
● TERMINATED:线程池彻底终止的状态
线程池核心参数
corepoolsize 核心线程数目 :最多保留的线程数
maximumpoolsize:最大线程数目:核心线程数+救急线程
keppAlivetime生存时间:针对急救线程
4.unit时间单位 :针对救急线程
workqueue:阻塞队列
thrradFactory:线程工厂:可以为线程创建时起个好听的名字
handler:拒绝策略:四种 抛异常,由调用者执行,直接丢弃,把队列最老的任务丢弃的
sleep和wait区别
共同点wait(),wait(long)和sleep(long)的效果都让当前线程暂时放弃cpu的使用权,进入阻塞状态归属不同
1.sleep(long)是thrrad的静态方法
2.而wait,wait(long)都是object的成员方法,每个对象都有醒来时间不同
1.执行sleep(long)和wait(long)的线程都会在等待响应毫秒后醒来
2.wait(long)和wait还可以被notifly唤醒,wait如果不唤醒就一直等待
3他们都可以被打断唤醒
索特性不同
1.wait方法的调用必须获取wait对象的锁.而sleep则无此限制
2.wait方法执行后会释放对象锁,允许其他线程获得该对象锁
3.而sleep如果在sunchronized代码块中执行,并不会释放该对象锁
sychronized 和lock区别
sychronized 是关键字,源码在jvm中,用c++语言实现
lock是接口,源码由jdk提供,用java语言实现
使用sychronized 时,退出同步代码块会自动释放,而使用lock时协议手动调用unlock方法释放锁
功能层面
二者都属于悲观锁,具备剧本的互斥,同步,所重入功能
lock提供了许多sychronized 不具备的功能,列如获取等待状态,公平锁,可以被打断,多条件变量
性能层面
在没有竞争时sychronized 做了很多优化,列如偏向锁清亮级锁性能不错
在竞争激烈时lock的实现通常会提供更好发性能
synchronized和volatile比较
a)volatile不需要加锁,比synchronized更轻便,不会阻塞线程
b)synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性
c ) volatile解决了内存可见性,指令重排序的
与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件(变量真正独立于其他变量和自己以前的值 ) 在某些情况下可以使用 volatile 代替 synchronized 来优化代码提升效率。(布尔)
解决原子性:
1.使用synchronized
2.使用Reentrantl (可重入锁)
3.使用Atomicinteger(原子操作)
Lock锁与Synchronized比较(面试题)
a. 原始构成
■ Synchronized 是关键字,属于JVM层面,底层是通过 monitorenter 和 monitorexit 完成,依赖于 monitor 对象来完成。由于 wait/notify 方法也依赖于 monitor 对象,因此只有在同步块或方法中才能调用这些方法。
■ Lock 是 java.util.concurrent.locks.lock 包下的,是 api层面的锁。
b. 使用方法
■ Synchronized 不需要用户手动释放锁,代码完成之后系统自动让线程释放锁
■ ReentrantLock 需要用户手动释放锁,没有手动释放可能导致死锁。
c. 等待是否可以中断
■ Synchronized 不可中断,除非抛出异常或者正常运行完成
■ ReentrantLock 可以中断。一种是通过 tryLock(long timeout, TimeUnit unit),另一种是lockInterruptibly()放代码块中,调用interrupt()方法进行中断。
d. 加锁是否公平
■ synchronized 是非公平锁
■ ReentrantLock 默认非公平锁,可以在构造方法传入 boolean 值,true 代表公平锁,false 代表非公平锁。
e. 锁绑定多个 Condition
■ Synchronized 只有一个阻塞队列,只能随机唤醒一个线程或者唤醒全部线程。
■ ReentrantLock 用来实现分组唤醒,可以精确唤醒。