六、不可变设计

一、日期转换的问题

1)可变类SimpleDateFormat

有线程安全问题

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            log.debug("{}", sdf.parse("1905-04-21"));
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }).start();
}

2)不可变类DateTimeFormatter

JDK 8以后引入不可变类,没有线程安全问题

DateTimeFormatter stf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        log.debug("{}", stf.parse("1905-04-21"));
    }).start();
}

二、不可变设计

String类是不可变的

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];                   // final 不可变

    /** Cache the hash code for the string */
    private int hash; // Default to 0        没有提供set方法 不可变

	//
}

final 的使用

发现该类中所有属性包括类上都加了 final

  • 属性 用 final 保证了该属性是只读,不能修改
  • 类用 final 保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性

保护性拷贝

String.substring方法

发现内部创建了一个新的字符串,在进入构造方法,是否对 final char[] value 做出了修改:

结果发现也没有,构造新字符串对象时,会生成新的char[] value, 对内容进行拷贝。

这种通过创建副本对象来避免共享的手段称之为 【保护性拷贝(defensive copy)】

三、 享元模式

1) 简介

  • 23种设计模式之一
  • 保护性拷贝的创建的对象太多,浪费空间,所以需要考虑相同对象重用,这时候就用到享元模式

2) 体现

  • String 类 体现是 串池
  • BigDecimal BIgInteger 实现也都是使用了享元模式

这些不可变类中,单个方法使用时线程安全的,但是如果涉及到使用了方法,然后对原对象进行了赋值操作

多线程情况下,例如 AtomicReferecce 进行一个线程安全保护。

3) DIY (do it yourself)

背景:

手写简易版连接池

package com.itheima.test6;

import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class Test2Pool {
    public static void main(String[] args) {
        Pool pool = new Pool(2);
        for (int i = 0; i < 5; i ++ ) {
            new Thread(() -> {
                Connection borrow = pool.borrow();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                pool.free(borrow);
            }).start();
        }
    }
}
@Slf4j(topic = "c.Pool")
class Pool {
    // 连接池的大小
    private final int poolSize;
    // 连接池数组
    private Connection[] connection;
    // 连接池判断是否可用  0表示可用 1表示不可用
    private AtomicIntegerArray status;

    public Pool(int poolSize) {
        this.poolSize = poolSize;
        connection = new Connection[poolSize];
        status = new AtomicIntegerArray(poolSize);
        for (int i = 0; i < poolSize; i ++ ) {
            connection[i] = new MyMarkConnection("连接" + i);
        }
    }

    // 取连接
    public Connection borrow() {
        while (true) {
            for (int i = 0; i < poolSize; i ++ ) {
                if (status.get(i) == 0) {
                    if (status.compareAndSet(i, 0, 1)) {
                        log.debug("borrow");
                        return connection[i];
                    }
                }
            }
            synchronized (this) {
                try {
                    log.debug("wait ... ");
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    // 归换连接
    public void free(Connection connection) {
        for (int i = 0; i < poolSize; i ++ ) {
            if (this.connection[i] == connection) {
                status.set(i, 0);
                synchronized (this) {
                    log.debug("free");
                    this.notifyAll();
                }
                break;
            }
        }
    }
}

class MyMarkConnection implements  Connection{
	//记得补全实现一下所有方法
}

四、final原理

1)设置final 变量的原理

共享变量,静态成员变量, 成员变量

public class TestFinal{
    final int a = 20;
}

字节码

跟 volatile 一样,final也会在变量后面加一个写屏障,保证可见性和有序性

这样保证final 修改的变量的值在别的线程访问的时候已经是最新的值了

如果没有写屏障,就会有指令重排,导致先初始化空间 int = a,然后默认值是0,然后在赋值,所以就会导致获取不到最新的值。

2)获取 final 变量的原理

public class TestFinal{
    final static int A = 10;
    final static int B = Short.MAX_VALUE + 1;

    final int a = 20;
    final int b = Integer.MAX_VALUE;
}
class UseFinal {
    public static void test() {
        System.out.println(TestFinal.A);    // 字节码层面会将final修饰的复制一份到当前方法的栈中
        System.out.println(TestFinal.B);    // 字节码层面会将final修饰,但是超过了Short最大值的数设置到常量池中。
        System.out.println(new TestFinal().a);   // 同理
        System.out.println(new TestFinal().b);   // 同理
    }
}

如果没有加 final ,则会取堆中找。加 final 效率比 堆中找的效率高。

  • 数字比较小,存放在调用者的方法栈中
  • 数字超过短整型,存放在类的常量池中
  • 不加final 都在堆中,读取效率都比不上前两者

五、无状态

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栗子ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值