在这里插入图片描述java
在这里插入图片描述web
ThreadLocal提升一个线程的局部变量,访问某个线程拥有本身局部变量。咱们经常使用使用ThreadLocal 用来存储用户信息,可是发现ThreadLocal 有时获取到的用户信息是别人的,数据库
咱们知道,ThreadLocal适用于变量在线程间隔离,而在方法或类间共享的场景。若是用户信息的获取比较昂贵(好比从数据库查询用户信息),那么在 ThreadLocal中缓存数据是比较合适的作法。但,这么作为何会出现用户信息错乱的 Bug ?编程
private ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
@ApiOperation(value = "test2")
@GetMapping("/test2")
public ResponseMessage test2(@ApiParam(value = "id")@RequestParam(required = false) Integer id) {
//设置用户信息以前先查询一次ThreadLocal中的用户信息
String before = Thread.currentThread().getName() + ":" + currentUser.get();
currentUser.set(id);
String after = Thread.currentThread().getName() + ":" + currentUser.get();
//汇总输出两次查询结果
Map result = new HashMap();
result.put("before", before);
result.put("after", after);
return ResultBody.success(result);
在这里插入图片描述数组
在设置用户信息以前第一次获取的值始终应该是 null,但咱们要意识到,程序运行在 Tomcat 中,执行程序的线程是 Tomcat 的工做线程,而 Tomcat 的工做线程是基于线程池的。缓存
由于线程的建立比较昂贵,因此web服务器每每会使用线程池来处理请求,就意味着线程会被重用。这是,使用相似ThreadLocal工具来存放一些数据时,须要特别注意在代码运行完后,显式的去清空设置的睡觉。若是在代码中使用来自定义线程池,也一样会遇到这样的问题安全
@ApiOperation(value = "test2")
@GetMapping("/test2")
public ResponseMessage test2(@ApiParam(value = "id")@RequestParam(required = false) Integer id) {
//设置用户信息以前先查询一次ThreadLocal中的用户信息
String before = Thread.currentThread().getName() + ":" + currentUser.get();
currentUser.set(id);
Map result = new HashMap();
try {
String after = Thread.currentThread().getName() + ":" + currentUser.get();
//汇总输出两次查询结果
result.put("before", before);
result.put("after", after);
}finally {
//在finally代码块中删除ThreadLocal中的数据,确保数据不串
currentUser.remove();
}
return ResultBody.success(result);
}
JDK 1.5 后推出的 ConcurrentHashMap,是一个高性能的线程安全的哈希表容器。“线程安全”这四个字特别容易让人误解,由于 ConcurrentHashMap 只能保证提供的原子性读写操做是线程安全的。服务器
public class Test {
private ConcurrentHashMap<String, Integer> map=new ConcurrentHashMap<String, Integer>();
public static void main(String[] args) {
final Test t=new Test();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
t.add("key");
}
}).start();
}
}
public void add(String key){
Integer value=map.get(key);
if(value==null)
map.put(key, 1);
else
map.put(key, value+1);
System.out.println(map.get(key));
}
}
在这里插入图片描述多线程
解决:并发
public class Test {
private ConcurrentHashMap<String, Integer> map=new ConcurrentHashMap<String, Integer>();
public static void main(String[] args) {
final Test t=new Test();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
t.add("key");
}
}).start();
}
}
public synchronized void add(String key){
Integer value=map.get(key);
if(value==null)
map.put(key, 1);
else
map.put(key, value+1);
System.out.println(map.get(key));
}
}
若是只是调用put或者get方法,ConcurrentHashMap是线程安全的,可是若是调用了get后在调用map.put(key, value+1)以前有另外的线程去调用了put,而后你再去执行put,就有可能将结果覆盖掉,但这个其实也不能算ConcurrentHashMap线程不安全,ConcurrentHashMap内部操做是线程安全的,可是外部操做仍是要靠本身来保证同步,即便在线程安全的状况下,也是可能违反原子操做规则。。。
除了 ConcurrentHashMap 这样通用的并发工具类以外,咱们的工具包中还有些针对特殊场景实现的生面孔。通常来讲,针对通用场景的通用解决方案,在全部场景下性能都还能够,属于“万金油”;而针对特殊场景的特殊实现,会有比通用解决方案更高的性能,但必定要在它针对的场景下使用,不然可能会产生性能问题甚至是 Bug。
CopyOnWrite 是一个时髦的技术,不论是 Linux 仍是 Redis 都会用到。在 Java 中, CopyOnWriteArrayList 虽然是一个线程安全的 ArrayList,但由于其实现方式是,每次 修改数据时都会复制一份数据出来,因此有明显的适用场景,即读多写少或者说但愿无锁读的场景。
测试写的性能
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> cowal = new CopyOnWriteArrayList<Integer>();
ArrayList<Integer> list = new ArrayList<Integer>();
int count = 500;
long time1 = System.currentTimeMillis();
while (System.currentTimeMillis() - time1 < count) {
cowal.add(1);
}
time1 = System.currentTimeMillis();
while (System.currentTimeMillis() - time1 < count) {
list.add(1);
}
System.out.println("CopyOnWriteArrayList在" + count + "毫秒时间内添加元素个数为: "
+ cowal.size());
System.out.println("ArrayList在" + count + "毫秒时间内添加元素个数为: "
+ list.size());
}
在这里插入图片描述
读性能比较
public static void main(String[] args) throws InterruptedException {
// create object of CopyOnWriteArrayList
List<Integer> ArrLis = new ArrayList<>();
List<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
System.gc();
for (int i = 0; i < 100000; i++) {
ArrLis.add(i);
}
for (int i = 0; i < 100000; i++) {
copyOnWriteArrayList.add(i);
}
Thread.sleep(500);
long startTime = System.currentTimeMillis(); //获取开始时间
// print CopyOnWriteArrayList
System.out.println("ArrayList: "
+ ArrLis);
// 2nd index in the arraylist
System.out.println(" index: "
+ ArrLis.get(5000));
long endTime = System.currentTimeMillis(); //获取结束时间
System.out.println(" ArrayList : 程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
Thread.sleep(500);
long startTime2 = System.currentTimeMillis(); //获取开始时间
// print CopyOnWriteArrayList
System.out.println("copyOnWriteArrayList: "
+ copyOnWriteArrayList);
// 2nd index in the arraylist
System.out.println(" index: "
+ copyOnWriteArrayList.get(5000));
long endTime2 = System.currentTimeMillis(); //获取结束时间
System.out.println(" copyOnWriteArrayList : 程序运行时间:" + (endTime2 - startTime2) + "ms"); //输出程序运行时间
System.gc();
}
在这里插入图片描述