• Java并发:五种线程安全类型、线程安全的实现、枚举类型


    1. Java中的线程安全

    • Java线程安全:狭义地认为是多线程之间共享数据的访问。
    • Java语言中各种操作共享的数据有5种类型:不可变、绝对线程安全、相对线程安全、线程兼容、线程独立

    ① 不可变

    • 不可变(Immutable) 的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。
    • 只要能正确构建一个不可变对象,该对象永远不会在多个线程之间出现不一致的状态。
    • 多线程环境下,应当尽量使对象成为不可变,来满足线程安全。

    实现不可变=========》

    • 如果共享数据是基本数据类型,使用final关键字对其进行修饰,就可以保证它是不可变的。
    • 如果共享数据是一个对象,要保证对象的行为不会对其状态产生任何影响。
    • String是不可变的,对其进行substring()、replace()、concat()等操作,返回的是新的String对象,原始的String对象的值不受影响。而如果对StringBuffer或者StringBuilder对象进行substring()、replace()、append()等操作,直接对原对象的值进行改变。
    • 要构建不可变对象,需要将内部状态变量定义为final类型。如java.lang.Integer类中将value定义为final类型。=====》private final int value;

    常见的不可变的类型:

    • final关键字修饰的基本数据类型
    • 枚举类型、String类型
    • 常见的包装类型:Short、Integer、Long、Float、Double、Byte、Character等
    • 大数据类型:BigInteger、BigDecimal

    对于集合类型,可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。

    • 通过Collections.unmodifiableMap(map)获的一个不可变的Map类型。
    • Collections.unmodifiableXXX() 先对原始的集合进行拷贝,需要对集合进行修改的方法都直接抛出异常。

    例如,如果获得的不可变map对象进行put()、remove()、clear()操作,则会抛出UnsupportedOperationException异常

     

    ② 绝对线程安全

    绝对线程安全的实现,通常需要付出很大的、甚至不切实际的代价。

    Java API中提供的线程安全,大多数都不是绝对线程安全。

    例如,对于数组集合Vector的操作,如get()、add()、remove()都是有synchronized关键字修饰。有时调用时也需要手动添加同步手段,保证多线程的安全。

     

    下面的代码看似不需要同步,实际运行过程中会报错。

    import java.util.Vector;

    /**
     * @Author: lucy
     * @Version 1.0
     */
    public class VectorTest {
        public static void main(String[] args) {
            Vector<Integer> vector = new Vector<>();
            while(true){
                for (int i = 0; i < 10; i++) {
                    vector.add(i);
                }
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < vector.size(); i++) {
                            System.out.println("获取vector的第" + i + "个元素: " + vector.get(i));
                        }
                    }
                }).start();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i=0;i<vector.size();i++){
                            System.out.println("删除vector中的第" + i+"个元素");
                            vector.remove(i);
                        }
                    }
                }).start();
                while (Thread.activeCount()>20)
                    return;
            }
        }
    }

    出现ArrayIndexOutOfBoundsException异常,原因:某个线程恰好删除了元素i,使得当前线程无法访问元素i。

    Exception in thread "Thread-1109" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 1
     at java.util.Vector.remove(Vector.java:831)
     at VectorTest$2.run(VectorTest.java:28)
     at java.lang.Thread.run(Thread.java:745)

    需要将对元素的get和remove构造成同步代码块:

    synchronized (vector){
        for (int i = 0; i < vector.size(); i++) {
            System.out.println("获取vector的第" + i + "个元素: " + vector.get(i));
        }
    }
    synchronized (vector){
        for (int i=0;i<vector.size();i++){
            System.out.println("删除vector中的第" + i+"个元素");
            vector.remove(i);
        }
    }


    ③ 相对线程安全

    • 相对线程安全需要保证对该对象的单个操作是线程安全的,在必要的时候可以使用同步措施实现线程安全。
    • 大部分的线程安全类都属于相对线程安全,如Java容器中的Vector、HashTable、通过Collections.synchronizedXXX()方法包装的集合。

    ④ 线程兼容

    • Java中大部分的类都是线程兼容的,通过添加同步措施,可以保证在多线程环境中安全使用这些类的对象。
    • 如常见的ArrayList、HashTableMap都是线程兼容的。

    ⑤ 线程对立

    • 线程对立是指:无法通过添加同步措施,实现多线程中的安全使用。
    • 线程对立的常见操作有:Thread类的suspend()和resume()(已经被JDK声明废除),System.setIn()System.setOut()等。

    2. Java的枚举类型

    通过enum关键字修饰的数据类型,叫枚举类型。

    • 枚举类型的每个元素都有自己的序号,通常从0开始编号。
    • 可以通过values()方法遍历枚举类型,通过name()或者toString()获取枚举类型的名称
    • 通过ordinal()方法获取枚举类型中元素的序号

    public class EnumData {
        public static void main(String[] args) {
            for (Family family : Family.values()) {
                System.out.println(family.name() + ":" + family.ordinal());
            }
        }
    }

    enum Family {
        GRADMOTHER, GRANDFATHER, MOTHER, FATHER, DAUGHTER, SON;
    }

    3. Java线程安全的实现

    ① 互斥同步

    互斥同步(Mutex Exclusion & Synchronization)是一种常见的并发正确性保障手段。

    • 同步:多个线程并发访问共享数据,保证共享数据同一时刻只被一个(或者一些,使用信号量)线程使用。
    • 互斥:互斥是实现同步的一种手段,主要的互斥实现方式:临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)。

    同步与互斥的关系:

    • 互斥是原因,同步是结果。
    • 同步是目的,互斥是方法。

    Java中,最基本的实现互斥同步的手段是synchronized关键字,其次是JUC包中的ReentrantLock。

     
  • 相关阅读:
    PHP简单模拟登录功能实例分享
    一个form表单,多个提交按钮
    jquery validation验证身份证号、护照、电话号码、email
    MockMvc和Mockito之酷炫使用
    Java8 Stream API
    第一章 Lambda表达式
    Java中线程顺序执行
    单元测试之获取Spring下所有Bean
    iBatis之type
    json解析之jackson ObjectMapper
  • 原文地址:https://www.cnblogs.com/KL2016/p/15508914.html
Copyright © 2020-2023  润新知