【记录】并发编程 - 学习日志(八)

7. 共享模型之不可变

7.1 引言

7.1.1 提出问题

package com.rui.seven;

import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    log.debug("{}", sdf.parse("2023-09-19"));
                } catch (Exception e) {
                    log.error("{}", e);
                }
            }).start();
        }
    }
}
// 某次运行结果

14:15:17 [Thread-0] c.Test1 - {}
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-4] c.Test1 - {}
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-3] c.Test1 - {}
java.lang.NumberFormatException: For input string: "E.4222000222323333023"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-8] c.Test1 - {}
java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Long.parseLong(Long.java:601)
	at java.lang.Long.parseLong(Long.java:631)
	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-9] c.Test1 - {}
java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Long.parseLong(Long.java:601)
	at java.lang.Long.parseLong(Long.java:631)
	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-2] c.Test1 - {}
java.lang.NumberFormatException: For input string: "E.4222000222323333023E4"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-5] c.Test1 - {}
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-6] c.Test1 - {}
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.rui.seven.Test1.lambda$main$0(Test1.java:14)
	at java.lang.Thread.run(Thread.java:745)
14:15:17 [Thread-1] c.Test1 - Tue Sep 19 00:00:00 CST 2220
14:15:17 [Thread-7] c.Test1 - Fri Jul 19 00:00:00 CST 2024

进程已结束,退出代码 0

上述代码的某次运行结果中出现了 java.lang.NumberFormatException 和不正确的日期解析结果 

其原因是:非线程安全

7.1.2 解决思路 - 锁

package com.rui.seven;

import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (sdf) {
                    try {
                        log.debug("{}", sdf.parse("2023-09-19"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}
// 某次运行结果

14:20:10 [Thread-0] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-9] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-8] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-7] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-6] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-5] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-4] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-2] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-3] c.Test1 - Tue Sep 19 00:00:00 CST 2023
14:20:10 [Thread-1] c.Test1 - Tue Sep 19 00:00:00 CST 2023

进程已结束,退出代码 0

7.1.3 解决思路 - 不可变类

 DateTimeFormatter

package com.rui.seven;

import lombok.extern.slf4j.Slf4j;

import java.time.format.DateTimeFormatter;

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                log.debug("{}", dtf.parse("2023-09-19"));
            }).start();
        }
    }
}
// 某次运行结果

14:25:26 [Thread-3] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-5] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-4] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-9] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-8] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-6] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-1] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-2] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-0] c.Test2 - {},ISO resolved to 2023-09-19
14:25:26 [Thread-7] c.Test2 - {},ISO resolved to 2023-09-19

进程已结束,退出代码 0

7.2 不可变类的设计

通过 final、保护性拷贝保证不可变性

以 String 为例

7.2.1 final 

package java.lang;

// import...

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 修饰 value[],保证了 value[] 的引用地址不能被修改

final 修饰 String,保证了 String 不能被其他类继承 

7.2.2 保护性拷贝

final 修饰 value[],保证了 value[] 的引用地址不能被修改,但 value[] 的值仍可被修改

package java.lang;

// import...

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
}

可见,构造新字符串对象时,会生成新的 char value[] 并对旧的 char value[] 中的内容进行复制,即保护性拷贝

7.3 享元模式 

7.3.1 简介

若频繁使用保护性拷贝,则会频繁创建新的对象

抛出疑问:这些对象中的值有重复的可能吗? 

答案是:有的 

通过重用对象代替创建新的对象,即享元模式的思维模式

7.3.2 体现 

1.  S包装类 

Byte、Short、Long  缓存的范围均为 -128~127

Integer 缓存的范围为 -128~127,但最大值可以通过调整虚拟机参数来改变

-Djava.lang.Integer.IntegerCache.high 

Character 缓存的范围为 0~127

 Boolean 缓存了 true 和 false

若符合范围,则重用对象;若不符合范围,则创建新的对象

2.  String 串池
3.  BigDecimal 和 BigInteger

7.3.3 DIY 

package com.rui.seven;

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 DIY {
    public static void main(String[] args) {
        Pool p = new Pool(2);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Connection conn = p.borrow();
                    Thread.sleep(new Random().nextInt(1000));
                    p.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() throws InterruptedException {
        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];
                    }
                }
            }

            // 若没有空闲连接,则当前线程进入阻塞(wait)状态
            synchronized (this) {
                log.debug("wait...");
                this.wait();
            }
        }
    }

    // 6. 归还连接
    public void free(Connection conn) {
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == conn) {
                states.set(i, 0);
                synchronized (this) {
                    log.debug("free {}...", connections[i]);
                    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 + '\'' +
                '}';
    }

    // ...
}
// 某次运行结果

16:50:30 [Thread-1] c.Pool - wait...
16:50:30 [Thread-2] c.Pool - borrow MockConnection{name='连接1'}...
16:50:30 [Thread-0] c.Pool - borrow MockConnection{name='连接2'}...
16:50:30 [Thread-4] c.Pool - wait...
16:50:30 [Thread-3] c.Pool - wait...
16:50:30 [Thread-2] c.Pool - free MockConnection{name='连接1'}...
16:50:30 [Thread-3] c.Pool - borrow MockConnection{name='连接1'}...
16:50:30 [Thread-4] c.Pool - wait...
16:50:30 [Thread-1] c.Pool - wait...
16:50:31 [Thread-0] c.Pool - free MockConnection{name='连接2'}...
16:50:31 [Thread-1] c.Pool - borrow MockConnection{name='连接2'}...
16:50:31 [Thread-4] c.Pool - wait...
16:50:31 [Thread-1] c.Pool - free MockConnection{name='连接2'}...
16:50:31 [Thread-4] c.Pool - borrow MockConnection{name='连接2'}...
16:50:31 [Thread-3] c.Pool - free MockConnection{name='连接1'}...
16:50:31 [Thread-4] c.Pool - free MockConnection{name='连接2'}...

进程已结束,退出代码 0

7.3.4 final 原理 

1. 设置 final 变量的原理

 使用 final 关键字修饰变量时,会在对变量执行完写操作后设置写屏障

package com.rui.seven;

public class Test3 {
    final int a = 5;
}
final int a;
a = 5;
// 写屏障

防止线程读到 a = 0 的情况 

2. 获取 final 变量的原理

说些废话

本篇文章为博主日常学习记录,故而会概率性地存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值