Java基础学习day_13 集合的线程安全

Condition(接口)

可以理解为条件队列。与Lock相互配合。
await():放弃所有的锁标记,放弃,使线程处于等待状态。(相当于进队列),和wait()相同
signal():唤醒一个等待线程(相当于出队列,而且是头队列元素),和notify()相同。
signalAll():唤醒所有的等待线程(相当于出队列,而且是全出)
和notifyAll()相同。
Lock可以创造 多个条件队列。就可以明确的分开。
而synchronized只有一个队列,只能采用notifyAll()唤醒所有的等待线程。
synchronized不需要手动释放锁。而Lock需要手动释放锁。

Lock lock=new ReentrantLock();//创建锁对象
Condition condition=lock.newCondition();//创建条件队列

copyOnWriteArrayList(实现类)

1.线程安全的ArrayList,加强版读写分离
2. 使用方式与ArrayList无异
3. 写入时,先copy一个容器副本、再添加新元素,最后替换引用
4. 写有锁,读无锁,读写之间不阻塞,优于读写锁

CopyOnWriteArrayList<String> copy=new CopyOnWriteArrayList<>();
copy.add("e");
//写操作
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();//加锁
    try {
        Object[] elements = getArray();//把当前数组地址给elenents
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);//对新数组扩容
        newElements[len] = e;
        setArray(newElements);//赋值
        return true;
    } finally {
        lock.unlock();
    }
}
final void setArray(Object[] a) {//新数组赋给就数组
    array = a;
}
//读操作 是没有锁的
public E get(int index) {
    return get(getArray(), index);
}

为什么写有锁,而读却没锁。是因为写操作是在新数组进行的操作,而读操作是在原数组的操作。而写操作的最后一步是把新数组的地址赋给了原数组,但是这并不会影响读操作,因为在赋值之前和之后,读的都是有效数字。
这个效率比较高,因为只会对增删改加锁,对读不加锁,读随便读。但是所需要的内存却增加了。

CopyOnWriteArraySet(实现类)

1.线程安全的Set,底层使用CopyOnWriteArrayList实现
2.唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
3如存在元素,则不添加(扔掉副本)。
4.重复的依据是equals();

private final CopyOnWriteArrayList<E> al;//私有属性
public CopyOnWriteArraySet() {//构造方法
    al = new CopyOnWriteArrayList<E>();
}
public boolean add(E e) {//调用add方法
    return al.addIfAbsent(e);//再调CopyOnWriteArrayList的方法
}
private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

ConcurrentHashMap(实现类)

Java基础学习day_13 集合的线程安全_第1张图片

CAS算法

Java基础学习day_13 集合的线程安全_第2张图片
Java基础学习day_13 集合的线程安全_第3张图片

Queue接口(队列)

collection的子接口,先进先出。
Java基础学习day_13 集合的线程安全_第4张图片

ConcurrentLinkedQueue实现类

线程安全的队列,和普通的队列操作相同。

BlockingQueue接口(阻塞队列)

可以设置Queue的大小。 解决生产者消费者的问题
Java基础学习day_13 集合的线程安全_第5张图片

实现类(都可以设置大小)

BlockingQueue的实现类是LinkedBlockingQueue
Java基础学习day_13 集合的线程安全_第6张图片

多线程的三个特性

1.原子性(互斥性):一个或多个操作不能被分割,要么全部执行,要么都不执行。
2.可见性:多个线程访问同一个变量,一个线程修改了这个变量,别的线程能立即看到修改的值。
volatile:保证内存可见性。
synchronized:也能保证内存的可见性。
3.有序性:程序执行的顺序按照代码的先后顺序执行,但是处理器为了提高程序运行的效率,可能会对输入代码进行优化,他不保证程序种各个语句的执行顺序和编写循序一致,但最终结果是一致的。(单线程没有问题,多线程可能会有问题)
所以对多线程来说,要禁止重排。
volatile:保证指令不会重排。

synchronized:可保证原子性和可见性。但不能保证有序性。
volatile:可保证可见性和禁止指令重排,但不能保证原子性。
Lock接口间接借助了volatile关键字间接的是实现了可见性和有序性

i++不是原子性
i++分三步操作:读(读取i),改(+1操作) 写(赋值i)。
解决方法:
1.加锁
2.使用原子变量
在java.util.atomic包下。

private AtomicInteger num=new AtomicInteger(0);赋值
num.getAndIncrement();//获取并自增  使用的是CAS算法(底层的指令)。

你可能感兴趣的:(java)