目录
可变的一个线程安全使用:
package com.example.juc.Automic.Date;
import lombok.extern.slf4j.Slf4j;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author diao 2022/4/19
*/
@Slf4j(topic = "c.TestDate01")
public class TestDate01 {
static ReentrantLock lock=new ReentrantLock();
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// synchronized (sdf) {
// try {
// log.debug("{}", sdf.parse("1951-04-21"));
// } catch (ParseException e) {
// log.debug("{}", e);
// }
// }
// }).start();
// }
test();
}
static void test(){
lock.lock();
for (int i = 0; i < 10; i++) {
new Thread(()->{
synchronized (sdf) {
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (ParseException e) {
log.debug("{}", e);
}
}
}).start();
}
lock.unlock();
}
}
不可对象的使用的线程安全
package com.example.juc.Automic.Date;
import lombok.extern.slf4j.Slf4j;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
/**
* @author diao 2022/4/19
*/
@Slf4j(topic = "c.DateTest02")
public class DateTest02 {
public static void main(String[] args) {
//1.利用DateTimeFormatter获取时间格式(不可变的)
DateTimeFormatter stf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//2.线程遍历
for (int i = 0; i < 10; i++) {
new Thread(()->{
TemporalAccessor parse = stf.parse("1999-05-14");
log.debug("{}",parse);
}).start();
}
}
}
String无法被修改,就是因为成员变量以及本身被final修饰了;
但是通过观察源码可以发现,字符串是以char[]的形式保存在value中的;
然后我们可以先得到这个成员变量,value数组,然后通过set方法设置新的值
package com.example.juc.CAS.modify;
import java.lang.reflect.Field;
/**
* @author diao 2022/4/19
*/
public class modifyString {
public static void main(String[] args) {
//实践传值修改String
String string = "我不该生气吗?";
System.out.println(string);
modifyString(string);
System.out.println(string);
}
public static void modifyString(String string) {
try {
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
field.set(string, field.get("我该如何面对你?"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
比如Date就是一个可变类,会被用户修改;
我们可以利用保护性拷贝来保护他
那么什么是保护性拷贝呢?
在对资源进行操作时,不对操作资源本身进行修改,而是对资源内容进行复制,通过创建副本对象来避免共享安全问题.
而拷贝:就是成员变量,也就是成员引用不要被外部引用直接赋值,而是先拷贝外部引用,将它们封装到一个盒子里面再赋值;
这里举个例子:
因为我们这里是传入的引用,被修饰的也是不可变的引用,所以说外部引用指向的值还是可以变的;
然后再main中外部引用的值就被修改了;
出现bug;
private final Date start;
private final Date end;
public Period(Date start, Date end){
if(start.compareTo(end) > 0){
throw new IllegalArgumentException(start + " after " + end);
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);//这里改变了外部引用指向的值
System.out.println(p.getStart());
System.out.println(p.getEnd());
}
解决:采用新的构造器和新的访问方法
public Period(Date start, Date end){
this.start = new Date(start.getTime());//把外部引用拷贝后再赋值
this.end = new Date(start.getTime());
if(start.compareTo(end) > 0){
throw new IllegalArgumentException(start + " after " + end);
}
/*******************修改前代码如下*****************/
// if(start.compareTo(end) > 0){
// throw new IllegalArgumentException(start + " after " + end);
// }
// this.start = start;//不要被外部引用直接赋值
// this.end = end;
}
因此你main中的end.setYear(78)就没有效果了,因为我构造器中的end引用指向的值是:外部引用已经拷贝好了的值,所以是定下来的了(就比如一个礼物盒子,他里面的内容时已经定下来了,你就算有盒子里面类似的内容,但是你在盒子外面,所以是改不了的);
简而言之,就是防止外部引用指向值的变化来修改内部的数据;
能创建对象的话就创建对象,保证安全;
不可变类条件: 类、类中所有属性都是 final 的,保证了属性是只读的,不能修改,保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性.
substring: 也是利用保护性拷贝,它会创建一个新的字符串,然后指向他;
内部其实就是调用了一个String构造方法创建了一个新的字符串,对于新创建的String中的value属性也是按照条件赋值原来的值返回给char数组,所以保证了不可变性;
试图修改内容时,会创建一个新的对象;
作用:主要是减少创建对象的数量,以减少内存和提高性能;
场景:有大量对象的时候,可能会造成内存溢出,我们把公共部分抽离出来,如果相同,以后就返回内存中存在的对象避免重复创建;
调用范围内的值,就会从缓存池LongCache中获取值,重用对象,只有大于这个范围内的值,才会创建新的对象;
为什么说BigDecimal是线程安全的?
其实还是涉及到保护性拷贝,它每次进行操作,都会创建一个新的BigDecimal,将新的值封装到里面,所以他是线程安全的,也就是原子性的;
问题来了,之前取款的案例中为何我们要用AtomicReference呢?
package com.example.juc.Automic.UnSafeOrSafe;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author diao 2022/4/17
*/
public class Referencetest {
static AtomicReference<BigDecimal> number=new AtomicReference<BigDecimal>(BigDecimal.ONE);
public static void main(String[] args) throws InterruptedException {
Runnable run=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
while(true){
BigDecimal pre = number.get();
BigDecimal next = number.get().add(BigDecimal.ONE);
if(number.compareAndSet(pre,next)){
break;
}
}
}
}
};
Thread thread1 = new Thread(run);
thread1.start();
Thread t2 = new Thread(run);
t2.start();
thread1.join();
t2.join();
System.out.println(number);
}
}
原因:因为你BigDecimal单个方法是原子性的,但是你多个方法组合起来就不一定是原子性的了,所以需要AtomicReference来进行保护;
package com.example.juc.Automic.ConnPool;
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;
/**
* @author diao 2022/4/19
*/
public class PoolConn {
public static void main(String[] args) {
Pool pool=new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(()->{
//借连接
Connection conn = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//还连接
try {
pool.free(conn);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
@Slf4j(topic = "c.Pool")
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){
//进行cas操作,对比状态,更新为1,若已经占用就下一个
if (states.compareAndSet(i, 0, 1)) {
log.debug("获取连接{}",connections[i]);
return connections[i];
}
}
}
//如果没有空闲,就陷入等待,进入waitSet等待,如果没有下面的代码,那么线程就会占用cpu资源空转;
synchronized (this){
try {
log.debug("开始进入等待wait...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//6.归还连接
public void free(Connection conn) throws InterruptedException {
for (int i = 0; i < poolSize; i++) {
/*如果归还的连接是连接池中的话,就将状态设置为0*/
if(connections[i]==conn){
states.set(i,0);
/*唤醒其他线程,唤醒xxx之间会有个竞争,所以要加锁*/
log.debug("开始归还连接{}",connections[i]);
synchronized (this){
this.wait();
}
break;
}
}
}
private class MockConnection implements Connection {
........
}
}