17、线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
注意:这个提交任务的两个方法的区别。、
几个线程池的区别:
见博客:
https://www.cnblogs.com/ljp-sun/p/6580147.html
注意 这个 可以执行定时任务的线程池
Executors.newScheduledThreadPool(4);
ervice.scheduleAtFixedRate(()->{
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}, 1, 3000, TimeUnit.MILLISECONDS);
延迟1秒之后,每隔3秒去执行线程池里面的任务。。。。可以定时
这个线程池和Timer非常像, Timer相当于是一个定时器,定时去执行某些任务,也可以指定参数,只不过它不是多线程的。
![这里写图片描述](https://img-blog.csdn.net/20180419225228441?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI1MzIwOTI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
18、 配置线程池
线程池不能滥用,,它的任务调度和任务管理 ,也要很大的开销。。。
19、死锁
所谓死锁:指的是两个或者两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象。。。
如果没有外界作用,他们就无法继续下去。。
一个线程要锁定两个对象才能完成,比较有意思的是 ,另外一个线程也要锁定两个对象。
所以解决死锁办法之一:别锁定俩对象,把锁的粒度变加粗一些,锁定当前整个对象,别锁定当前对象里面的两个小对象
//尽量只锁定一个对象
记一个例子就行
import lombok.extern.slf4j.Slf4j;
/**
* 一个简单的死锁类
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒
* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/
@Slf4j
public class DeadLock implements Runnable {
public int flag = 1;
//静态对象是类的所有对象共享的 ---- 注意这里是static的才可能形成死锁。
private static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
log.info("flag:{}", flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
log.info("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
log.info("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
//td2的run()可能在td1的run()之前运行
new Thread(td1).start();
new Thread(td2).start();
}
}
20、多线程并发最佳实践
1、
2、不可变类,比如String,一旦创建就不可改变了,不可变类可以降低代码中需要的同步数量
3、
有关并行的两个重要公式
【1】S = 1/F+1/n(1-F) Amdal 安达尔定律
【2】S = n - F(n-1) Gustafson 古斯塔夫森定律
S:加速比 n:处理器个数 a:串行时间 b:并行时间 F = a/(a+b):串行比例
21、spring与线程安全、
1、
spring作为一个IOC容器,帮我们管理了许许多多的bean,但是spring并没有保证这些对象的线程安全,这需要程序员自己编写解决线程问题的代码,spring对每个bean提供了scope属性来表示该bean的作用域,它是这个bean的生命周期。scope默认值是singleton、单例,单例的生命周期和spring的IOC容器是一致的。它会在第一次被注入时,创建。
prototype则是在每每次请求都会创建并返回一个新的实例。。
2、
被spring管理的大多数都是无状态的对象。(重要)
总之:spring根本就没有对bean的多线程安全问题作出任何的保证与措施,对个每个bean的线程安全问题,根本原因是因为每个bean的设计 没有在bean中声明任何有状态的实例变量或者类变量。、
也就是说无状态对象不管是单例、多例那都是线程安全的。。不过单例能够节省不断创建的对象与GC的开销,我们平时 使用单例的spring bean没有什么线程问题,很关键一点是,我们只是简单的使用,不涉及到修改这个bean的内部属性。。不会出现多个线程去修改bean中相同的变量的场景。。
(感觉这里写的不严谨)什么是无状态对象呢? 比如我们经常使用的DTO、VO,这些实体类对象。还有我们常用的service、dao、controller啊,都是,这些对象只是用来执行某些操作的,并没有自己的状态。比如dao就是执行数据库的CRUD操作,并且每个数据库的connection就是作为函数的局部变量,局部变量是分配在栈上的,是线程独享的。
关于有状态变量,看看博客:
https://www.cnblogs.com/xubiao/p/6567349.html
还有这个博客:这一篇写的比较详细和系统,优先看这个博客
https://blog.csdn.net/a236209186/article/details/61460211
插一句:springMVC对每个请求的拦截粒度是基于每个方法的,而strut2是基于每个类的.
如果把controller设置为多例的话,那将会频繁的创建和回收对象,这将严重的影响系统的开销。
如果必须要在bean中加入有状态的实例变量的时候,那么这时候就需要用threadlocal把变量变成线程私有的。
如果bean的实例变量需要在多个线程之间共享呢? 那就用synchronized或者lock 这些来实现线程同步。
如果对这些单例的bean不用做什么特殊处理,就不用关心多线程问题了,spring能做到原本的线程安全,就是因为它无状态的设计。。。。。
22、HashMap和ConcurrentHashMap
补充一下:2的N次方问题
因为计算机中取模的代价要远远高于位操作的代价,所以hashmap要求数组的长度必须是2的N次方,此时它将key的hash值和2的N次方-1 进行与运算。(用一种位运算,而不是取模)
补充:
HashMap计算索引的方式是h&(length-1),而Hashtable用的是模运算,效率上是低于HashMap的。
https://blog.csdn.net/chdjj/article/details/38581035
如果你传入的容量不是2的N次方,那hashmap会自动算出一个容量值。
haspmap在多线程 再哈希的 时候 容易出现死循环。原因就是扩容的方法没有加锁,多线程访问就会出问题,至于为什么出现死循环,画图说明一下即可。。(可能有点复杂)
而ConcurrentHashMap就是线程安全的 并发容器。。
JDK7中ConcurrentHashMap的结构仍然是数组+链表。。。
不同的是,它的外部不是一个大的数组,而是一个segment数组,每个segment包含一个和hashmap中差不多的链表数组,结构图见图:
在读写的时候,先取出key的hash值,将hash值的高N位对segment的个数取模,从而得到该key属于哪个segment。接着就像操作hashmap一样操作segment,为了保证不同的值均匀的分布到segment里,它计算哈希值也做了优化。。。这个segment是继承于JUC里的ReentrantLock,所以可以对每个segment加锁。就保证了线程安全。。
总之:JDK7的ConcurrentHashMap是基于分段锁来处理的。。
区别:
1、
ConcurrentHashMap:线程安全 key和value不 可以为null。。它可以在遍历的时候修改
HashMap:非线程安全。key和value可以为null。遍历时修改会出现ConcurrentModificationException。
java8当链表长度超过指定数默认8时,用红黑树实现。寻址复杂度从o(n)转换成了olog(n)
2、JDK8 为了进一步提高 并发度,废弃了分段锁的方案,
关于ConcurrentHashMap具体是怎么实现线程安全的:
可以看博客:
http://www.importnew.com/23610.html
也可以看博客: (
其实就是对散列的每个节点加锁,而且锁用的是synchronized )
代码中加锁片段用的是synchronized关键字,而不是像1.7中的ReentrantLock。这一点也说明了,synchronized在新版本的JDK中优化的程度和ReentrantLock差不多了。
https://blog.csdn.net/fouy_yun/article/details/77816587