part 01:Java线程
1、进程和线程的区别:
2、Java开启线程的方式:
3、四种常用的线程池:
-
newCachedThreadPool:可缓存线程池,灵活回收空闲线程,若无可回收,则新建线程。没有核心线程,线程数量没有上线,默认消亡时间为60秒,阻塞队列是SynchronousQueue,没有存储性质的阻塞队列,取值操作和放入操作必须是互斥的,可以理解为每当有任务放入时立即有线程将它取出执行。
-
优点:提高了线程的复用率,当第二个任务开始,第一个任务已经结束,那么第二个任务会服用第一个任务创建的线程,并不会重新创建新的线程。
-
缺点:无法控制最多需要多少个线程同时处理,它会自动扩展线程数
-
newScheduledThreadPool:固定调度线程池。有固定的核心线程,线程的数量没有限制,支持定时以及周期性任务执行,可以延迟任务的执行时间,也可以设置一个周期性的时间让任务重复执行。两种延迟方法:
-
scheduleAtFixedRate:当前任务时间小于间隔时间,每次到点即执行;当前任务执行时间大于等于间隔时间,任务执行后立即执行下一次任务,相当于连续执行。
-
scheduledWithFixedDelay:每当上次任务执行完毕后,每隔一段时间执行。(可以做每天几点执行任务)
4、线程池参数:
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
ThreadFactory springThreadFactory = new CustomizableThreadFactory("springThread-pool-");
ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
.namingPattern("basicThreadFactory-").build();
5、线程工具类:
part 02:Java锁
1、synchronized和reentrantLock的区别:
-
synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。
-
Lock等待可中断,而synchronized只会让等待的线程一直等待下去。如果线程A正在执行锁中代码,线程B正在等待获取该锁。时间太长,线程B不想等了,可以让它中断自己。
-
synchronized是非公平锁,reentrantLock可以设置是否为公平锁,通过构造方法new ReentrantLock时传入值进行选择,true为公平锁,false为非公平锁
-
一个ReentrantLock可以绑定多个Condition对象,结合await()/singal()方法实现线程的精确唤醒,而synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机幻想一个线程要么唤醒全部线程
-
synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。
-
通过Lock可以知道有没有成功获取到锁
大量线程同时竞争,ReentrantLock要远胜于synchronized。
JDK5中,synchronized是性能低效的,因为这是一个重量级操作,对性能的最大影响是阻塞的实现,挂起线程和恢复线程的操作,都需要转入内核态中完成,给并发带来了很大压力。
JDK6中synchronized加入了自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁等一系列优化,官方也支持synchronized,提倡在synchronized能实现需求的前提下,优先考虑synchronized来进行同步。
2、锁升级过程:
当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。(先看对象头中的threadID是否一致,不一致看持有锁的对象是否存活,存活看是否还持有锁,还持有锁就升级为轻量级锁)
线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;
如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。 自旋锁简单来说就是让线程2在循环中不断CAS
但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
3、threadlocal
-
每个Thread都维护了自己的threadLocals变量
-
使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值
-
ThreadLocal的key被设计成weakReference弱引用,在没有被外部强引用时,发生GC会被回收,
如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。怎么解决:在使用的最后remove把值清空
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)