SImpleDateFormat格式转换
在多线程下,格式转化使用SimpleDateFormat可能会报错。这是因为线程之间互相影响导致。具体原因请查看另一篇博客SimpleDateFormat在多线程下的安全问题-CSDN博客
public class test {
public static void main(String[] args) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
Date parse = simpleDateFormat.parse("2003-6-03");
System.out.println(parse);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
}
因为SimpleDateFormat类中的calendar属性可以在多个线程中共享,可以在多个线程中同时进行改变,因此产生了线程安全问题。
多线程下安全格式转化
在JDK8之后提供了线程安全的格式转换DateTimeFormatter类。使用方法如下。
public class test {
public static void main(String[] args) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
TemporalAccessor parse = dateTimeFormatter.parse("2003-06-03");
System.out.println(parse);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
不可变类的设计
在Java中,有许多类都采用了不可变设计来避免线程不安全问题。
字符串使用char数组来进行存储。hash是用来存储哈希值,当第一次调用hashCode时会存储在hash变量中做缓存。
这里需要搞清楚,String中value虽然添加了final来修饰,代表的是char数组的引用不可变,不代表char数组中的值不可变。而对于基本类型来说,使用final修饰才代表值不可变。
保护性拷贝
以下是String类的构造方法。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
对于char数组,如果将参数value的引用直接赋值给this.value的话,实际上this.value存储的是一个数组地址,而不是存储的值。当外部还存在一个char数组引用了String当中的参数数组时,当外部的char数组中的值发生改变时,String对象中的值也跟着改变。因此要拷贝出一样的char数组让value来引用拷贝出来的地址。
public class Test {
public static void main(String[] args) {
//定义一个char数组
char[] name = new char[]{'z','s'};
//另一个数组也引用name的地址
char[] changeName = name;
Student student = new Student(name);
System.out.println(student.name);
//修改changName的值
changeName[0]='l';
System.out.println(student.name);
}
static class Student{
char[] name;
public Student(char[] name) {
this.name = name;
}
}
}
zs
ls
我们修改的是对象外char数组的值,但是对象中的值也随之发生改变,从此我们可以看出保护性拷贝的重要性。
类似于String类容易频繁创建对象,这时通常会用享元模式来减少对象的创建。对于享元模式不熟悉的小伙伴可以查看我的另一篇文章。JDK使用了享元模式的源码解析
DIY一个连接池
连接池作用是可以避免在高并发的情况下反复建立连接浪费系统性能,实现连接复用。基于享元模式实现的。
public class Pool {
//连接池大小
private final int poolSize;
//连接对象数组
private Connection[] connections;
//连接对象状态 0表示空闲。1是繁忙
private AtomicIntegerArray states;
public Pool(int poolSize) {
this.poolSize = poolSize;
this.connections = new Connection[poolSize];
this.states = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
connections[i] = new MyConnect("nameIs"+i);
}
}
//获取连接
public Connection getConnect(){
while (true){
for (int i = 0; i < poolSize; i++) {
//如果该连接空闲
if (states.get(i)==0){
//修改该连接的状态
if (states.compareAndSet(i,0,1)){
System.out.println("获取连接"+connections[i]);
return connections[i];
}
}
}
//如果没有空闲连接
synchronized (this){
try {
System.out.println("没有空闲连接");
//进入阻塞,等待其他线程释放来连接
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//释放连接
public void freeConnect(Connection connection){
//判断传过来的连接是否是连接池中的。
for (int i = 0; i < poolSize; i++) {
if (connections[i]==connection){
//因为只有一个线程拿到该连接,因此不会发生线程安全问题,直接使用set即可
states.set(i,0);
System.out.println("释放连接:"+connection);
synchronized (this){
//唤醒其他阻塞的线程
this.notifyAll();
}
break;
}
}
}
}
public class Test{
public static void main(String[] args) {
//连接池大小为3
Pool pool = new Pool(3);
for (int i = 0; i < 5; i++) {
new Thread(()->{
Connection connect = pool.getConnect();
try {
//拿到连接的线程进行随机休眠
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//释放连接
pool.freeConnect(connect);
}).start();
}
}
}
获取连接MyConnect{name='nameIs0'}
获取连接MyConnect{name='nameIs1'}
没有空闲连接
获取连接MyConnect{name='nameIs2'}
没有空闲连接
释放连接:MyConnect{name='nameIs0'}
获取连接MyConnect{name='nameIs0'}
没有空闲连接
释放连接:MyConnect{name='nameIs2'}
获取连接MyConnect{name='nameIs2'}
释放连接:MyConnect{name='nameIs0'}
释放连接:MyConnect{name='nameIs2'}
释放连接:MyConnect{name='nameIs1'}
原文地址:https://blog.csdn.net/zmbwcx/article/details/134655204
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_22134.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!