可以理解为条件队列。与Lock相互配合。
await():放弃所有的锁标记,放弃,使线程处于等待状态。(相当于进队列),和wait()相同
signal():唤醒一个等待线程(相当于出队列,而且是头队列元素),和notify()相同。
signalAll():唤醒所有的等待线程(相当于出队列,而且是全出)
和notifyAll()相同。
Lock可以创造 多个条件队列。就可以明确的分开。
而synchronized只有一个队列,只能采用notifyAll()唤醒所有的等待线程。
synchronized不需要手动释放锁。而Lock需要手动释放锁。
Lock lock=new ReentrantLock();//创建锁对象
Condition condition=lock.newCondition();//创建条件队列
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);
}
为什么写有锁,而读却没锁。是因为写操作是在新数组进行的操作,而读操作是在原数组的操作。而写操作的最后一步是把新数组的地址赋给了原数组,但是这并不会影响读操作,因为在赋值之前和之后,读的都是有效数字。
这个效率比较高,因为只会对增删改加锁,对读不加锁,读随便读。但是所需要的内存却增加了。
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();
}
}
线程安全的队列,和普通的队列操作相同。
BlockingQueue的实现类是LinkedBlockingQueue
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算法(底层的指令)。