0
点赞
收藏
分享

微信扫一扫

保护性拷贝+亨元模式

Ichjns 2022-04-19 阅读 30

目录

 通过反射修改String值

保护性拷贝

那么什么是保护性拷贝呢?

这里举个例子:

作用

String是如何保证通过保护性拷贝实现不可变的?

亨元模式

为什么说BigDecimal是线程安全的?

自定义连接池


 

可变的一个线程安全使用: 

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 {
    ........
    }
}

 

举报

相关推荐

0 条评论