进程:操作系统中资源分配的基本单位.
线程:是在进程中任务调度和执行的基本单位.
继承Thread(重写run方法,启动线程)
实现Runnable
线程池Executors : 1. newFixedThreadPools()创建最大线程数为5
2.newCacheThreadpool()足够多的线程 有空闲任务就会创建
3.newSingleThreadExecutor()只创建一个线程
线程池pool配合 execute()将数据丢进线程池 没有返回值
Callable/future: future 凭证小票
Future与submit()方法进行配和使用,也是将数据丢进线程池返回一个凭证小票.
如果是实现Runnable则返回的小票没有结果,执行future.get()方法只是进行线程暂停进行下一项,
如果是实现了callable 则有返回值 并且可以获取返回值.
Callable与Runnable的区别:Runnable 不能抛异常,不能有返回值
Callable是在Runnable上可以抛出异常,可以有返回值
线程的5种状态: 新建,可执行,执行,阻塞,销毁
当新建的线程执行了start()方法的时候 进入可运行线程池中,变的可运行,等待获取CPU的使用权,执行状态就是线程获取了CPU的使用权.阻塞状态就是线程因为某种原因放弃了CPU的使用权,暂时停止了运行,直到线程重新就绪,才可以继续执行 销毁状态就是 正常结束 或者 出现异常 return
阻塞的情况有三种:
1. 等待阻塞:运行的线程执行了 wait()方法 进入了等待池,只有等到 notify() notifyAll()才重新获取锁 运行
2.同步阻塞:运行的线程在获取对象的锁的时候,若该对象的同步锁被别的线程占用,会将该线程放进锁池中
3.其他的阻塞:运行的线程执行了sleep()或者join()方法或者发出了IO请求时
Thread.currentThread() 获取当前线程的实例
Thread.sleep()让线程暂停
Thread.yield() 放弃CPU时间
GetName() setName() 获取线程的名字和设置
Interrupt() 打断线程的执行
Join() 一个线程等待另一个线程结束
SetDaemon()后台线程
Synchronized的三种方式:
1. synchronized (对象){} 共享代码
2. Synchronized void f(){} 抢当前实例的锁
3. Static synchronized void f(){} 抢当前类的锁
Synchronized 与lock
Synchronized (悲观锁)
Lock(乐观锁)
|=>>ReentrantLock 重入锁
|=>>>ReentrantReadWriteLock 重入读写锁
方法: Lock()
unLock()
JVM 指令重排序 instance=new singleton04() ;
系统底层会执行的指令
1)分配空间
2)对象属性的初始化
3)执行构造方法
4)将对象赋值给instance变量
JVM 默认会将性能优化的顺序为 1 4 2 3 也就是多线程访问时 对象的属性可能还没有初始化 出现 出现高并发时线程不安全的问题
解决方法有两种 1.关键字 volatile 的作用 :
1.禁止指令的重排序 (JVM 对java 程序进行编译时会有指令重排)
2.保证内存可见性(多CPU 一个线程对共享变量的修改,另一个线程是可见的)
3.但不保证原子性(synchronized 可以保证原子性)
两种说法:一种是 volatile 不使用 cache 直接修改了 内存中的数据
另一种说法是volatile可以实现两个线程之间的检测功能 cpu2会检测CPU1 如果它修改数据的时候
2.ThreadLocal作用: 提供了一种线程绑定机制 能够将某个对象绑定到当前线程也可以从当前线程获取某一对象,借助此对象可以实现线程内部的单例
应用场景:线程内部单例 取消线程共享
例如存在的问题:SimpleDateFarmat 是一个线程不安全 放在方法外实现共享 线程不安全 放在方法中 会出现内存问题 ,每次调用时都会new 一个新的 对象
在阿里的开发手册中写到 使用时需要加锁 加锁会影响性能 或使用 DateTimeFormetter代替 SimpleDateFarmat 会使用ThreadLocal 泛型 的方式
Private static ThreadLocal td =new ThreadLocal (){
@override
Initiatvalue(){
return new SimpleDateFarmat (“yyyy—”)
}
};
常用方法:get /set
应用的原理:
ThreadLocal的原理 :是将当前的线程作为key值 将对象作为value值 实现线程的绑定 (只能存一个Entry) 并且 Entry 还是一个弱引用 具体的实现如下:
Sleep()是线程(Thread)类的方法,导致此线程的暂停执行指定时间,把执行机会给其他的线程,但是监控状态依然保持,到时候会自动回复.调用sleep()不会释放对象锁.
Wait()是Object类的方法,对此对象调用的wait()方法导致本线程放弃了对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify()方法(或notifyAll())后线程才进入对象锁定池准备获得对象锁进入运行状态.
什么是死锁
产生死锁的四个必要条件
解决死锁的办法
如何避免死锁
什么是线程死锁?
一个对象中多把锁,多线程并发时多线程同时被阻塞,它们中有一个或多个都在等待某一个资源的释放,因为线程无限期的阻塞,因此程序不可能终止
如图所示:
/**
* @author renjiaxing
*测试死锁的情况
*/
public class TestThreadDeadLock {
private static Object resource1=new Object();
private static Object resource2=new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized(resource1){
System.out.println(Thread.currentThread()+"我突然想喝娃哈哈");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待你给我买娃哈哈的时间");
synchronized (resource2) {
System.out.println(Thread.currentThread()+"其实营养快线也不是不可以");
}
}
},"线程A").start();
new Thread(()->{
synchronized(resource2){
System.out.println(Thread.currentThread()+"其实营养快线也不是不可以");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待你给我买娃哈哈的时间");
synchronized (resource1) {
System.out.println(Thread.currentThread()+"我一定要最少喝到一样,哼!!!");
}
}
},"线程B").start();
}
}
输出的结果:
线程A获得资源1的锁,线程B获得资源2的锁(线程休眠是为了让两个线程都可以获取到一个锁).当休眠结束后,都开始获取对方的资源,却都被占用,陷入等待模式,产生了死锁
如何避免死锁?
1.破坏互斥的条件:这个条件我们没法达到,因为我们使用锁,就是想让它们互斥(需要互斥访问)
2.破坏循环等待的条件:按某一个固定的顺序去执行申请资源
3.占用资源的线程进一步申请其他的资源时,如果申请不到,可以主动的释放自己的资源
4.一次性申请所有的资源
我们将线程B修改为:
new Thread(()->{
synchronized(resource1){
System.out.println(Thread.currentThread()+"我突然想喝娃哈哈");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待你给我买娃哈哈的时间");
synchronized (resource2) {
System.out.println(Thread.currentThread()+"其实营养快线也不是不可以");
}
}
},"线程B").start();
输出结果为:
大家可以看到程序已经执行完了.
再举一个关于项目中死锁的情况:
/**
* @author renjiaxing
*测试工作中的死锁的案例
*/
public class TestLock {
//假如这个方法上有一个事物注解
public void updateBusiness(List<Long> goodIds){
if (goodIds==null||goodIds.isEmpty()) {
return;
}
IGoodsDao goodDao =null;
for(Long gId: goodIds){
goodDao.updatedateGoods(gId,1) ;
}
}
}
interface IGoodsDao{
void updatedateGoods(Long gId, int nums);
}
这是一个商品入库的操作,如果这是有一个客户购买id为1和3的商品,而另一位客户需要购买3和1 的商品 此时就会同时调用updateBusiness方法:
解决的方法:
在入库是将商品id进行排序,使得按顺序去执行如一下代码:
//假如这个方法上有一个事物注解
public void updateBusiness(List<Long> goodIds){
if (goodIds==null||goodIds.isEmpty()) {
return;
}
IGoodsDao goodDao =null;
Collections.sort(goodIds);
for(Long gId: goodIds){
goodDao.updatedateGoods(gId,1) ;
}
}
}