SimpleDateFormat 不是线程安全的,有很大几率出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (Exception e) {
log.error("{}", e);
}
}).start();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 50; i++) {
new Thread(() -> {
synchronized (sdf) {
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (Exception e) {
log.error("{}", e);
}
}
}).start();
}
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LocalDate date = dtf.parse("2018-10-01", LocalDate::from);
log.debug("{}", date);
}).start();
}
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
// ...
}
发现该类中所有属性都是 final 的
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
发现其内部是调用 String 的构造方法创建了一个新字符串,
再进入这个构造看看,是否对 final char[] value 做出了修改:
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
结果发现也没有,构造新字符串对象时,会生成新的 char[] value,对内容进行复制 。
这种通过创建副本对象来避免共享的手段称之为**【保护性拷贝(defensive copy)】**
package com.technologystatck.juc.synchronizeds;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class ConnectionPool {
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();
}
}
}
@Slf4j(topic = "c.Pool")
class Pool{
//1.连接池大小
private final int poolSize;
//2.连接对象数组
private Connection[] connections;
//3.连接状态的数组 0-空闲 1-占用
private AtomicIntegerArray states;
//4.构造方法初始化
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)) {
log.debug("borrow {}",connections[i]);
return connections[i];
}
}
}
//若没空闲连接,则让当前线程进入等待
synchronized (this){
try {
log.debug("wait...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//6.归还连接
public void free(Connection connection){
for(int i=0;i<poolSize;i++){
if(connections[i] == connection){
states.set(i,0);
synchronized (this){
log.debug("free {}",connection);
this.notifyAll();
}
break;
}
}
}
}
class MockConnection implements Connection{
private String name;
public MockConnection(String name) {
this.name = name;
}
@Override
public String toString() {
return "MockConnection{" +
"name='" + name + '\'' +
'}';
}
@Override
public Statement createStatement() throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return null;
}
//因实现的Connection方法太多,只显示部分实现的方法
... ...
... ...
... ...
}
理解了 volatile 原理,再对比 final 的实现就比较简单了
public class TestFinal {
final int a = 20;
}
发现 final 变量的赋值也会通过 putfifield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到
它的值时不会出现为 0 的情况