1、转换问题
SimpleDataFormat不安全
package com.sharing_model.immutable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* 共享模型之不可变
* SimpleDataFormat不安全
*/
public class SimpleDataFormatTest {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
System.out.println(sdf.parse("1951-04-21"));
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
}
除了加锁还可以使用java8中新出现的DateTimeFormatter来解决,这个类是不可变的
package com.sharing_model.immutable;
import java.text.ParseException;
import java.time.format.DateTimeFormatter;
/**
* java8新提供的格式化日期类
* 线程安全,不可变
*/
public class DateTimeFormatterTest {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(dtf.parse("1951-04-21"));
}).start();
}
}
}
2、不可变类的设计
String类也是不可变的,以他为例,说明一下不可变设计的要素
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
priavte final char value[];
private int hash;
}
final 的使用
发现该类,类中的所有属性都是final的
属性用final修饰保证了该属性是只读的,不能修改
类用final修饰保证了该类的所有的方法不能被覆盖,防止子类无意间破坏不可变性
保护性拷贝
发现内部是调用String的构造方法创建了一个新的字符串,再进这个构造看看,是否已对final char[] value做出了修改
结果发现没有,构造新字符串对象时,会生成新的char[] value,对内容进行复制。这种通过创建副本对象来避免共享的手段称之为【保护性拷贝】
3、享元模式
当需要重用数量有限的同一类对象时
3.1 体现
包装类:在JDK中Boolean,Byte,Short,Integer,Long,Character等包装类中都提供了valueOf方法,例如Long的valueOf会缓存-128 ~ 127 之间的Long对象,在这个范围之间会重用对象。
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <=127) {
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
3.2 DIY 数据库连接池
例如:一个线上商城应用,QPS达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。这时预先创建一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回到连接池 ,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
package com.sharing_model.immutable;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* DIY 数据库连接池
*/
class PoolTest {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Connection connection = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(connection);
}).start();
}
}
}
class Pool {
//1、连接池大小
private final int poolSize;
//2、连接对象数组
private Connection[] connections;
//3、连接状态数组 0:表示空闲 1:表示繁忙
private AtomicIntegerArray states;
//4、构造方法
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 MockConnection("连接" + (i+1));
}
}
//5、借连接
public Connection borrow() {
while (true) {
for (int i = 0; i < poolSize; i++) {
if (states.get(i) == 0) {
if (states.compareAndSet(i,0 ,1)) {
return connections[i];
}
}
}
//如果没有空闲连接,当前线程进入等待
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//6、还连接
public void free(Connection conn) {
for (int i = 0; i < poolSize; i++) {
if (connections[i] == conn) {
states.set(i, 0);
synchronized (this) {
this.notifyAll();
}
break;
}
}
}
}
class MockConnection implements Connection {
@Override
public Statement createStatement() throws SQLException {
return null;
}
}
4、final原理
4.1 设置final变量的原理
public class TestFinal {
final int a = 20;
}
4.2 获取final变量的原理
https://www.bilibili.com/video/BV16J411h7Rd?p=198
5、无状态
在web学习阶段,设计Servlet时为了保证其线程安全,都会有这样的建议,不要为Servlet设置成员变量,这种没有任何的成员变量的类是线程安全的,因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称为【无状态】