对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后, 新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式, 这些类同样位于JUC包下的atomic包下,发展到JDk1.8,该包下共有17个类, 囊括了原子更新基本类型、原子更新数组、原子更新属性、原子更新引用。
原子类,不需要加同步资源锁
/**
* 原子类
*/
public class Demo1 {
private static AtomicInteger sum = new AtomicInteger(0);
public static void inCreament(){
sum.incrementAndGet(); // 自增
}
public static void main(String[] args){
for(int i =0;i<10;i++){
new Thread(
()->{
for(int j = 0;j<10;j++){
inCreament();
System.out.println(sum);
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下4个类进行原子字段更新AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference、AtomicReferenceFieldUpdater
/**
* AtomicLongFieldUpdaterDemo
*/
public class AtomicLongFieldUpdaterDemo {
public static void main(String[] args) {
AtomicLongFieldUpdater<Student> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(Student.class, "id");
Student xdclass = new Student(1L, "xdclass");
longFieldUpdater.compareAndSet(xdclass, 1L, 100L);
System.out.println("id="+xdclass.getId());
AtomicReferenceFieldUpdater<Student, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
referenceFieldUpdater.compareAndSet(xdclass, "xdclass", "wiggin");
System.out.println("name="+xdclass.getName());
}
}
class Student{
volatile long id;
volatile String name;
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
容器:包括同步容器以及并发容器
注意,此处所指的不是dock容器,而是jdk里面的容器,包括同步容器和并发容器
同步容器
Vector、HashTable -- JDK提供的同步容器类 Collections.synchronizedXXX 本质是对相应的容器进行包装。
同步容器类的缺点:
在单独使用里面的方法的时候,可以保证线程安全,但是,复合操作需要额外加锁来保证线程安全 使用Iterator迭代容器或使用使用for-each遍历容器,在迭代过程中修改容器会抛出
ConcurrentModificationException异常。想要避免出现ConcurrentModificationException,就必须在迭代过程持有容器的锁。但是若容器较大,则迭代的时间也会较长。那么需要访问该容器的其他线程将会长时间等待。从而会极大降低性能。 若不希望在迭代期间对容器加锁,可以使用"克隆"容器的方式。使用线程封闭,由于其他线程不会对容器进行修改,可以避免ConcurrentModificationException。但是在创建副本的时候,存在较大性能开销。 toString,hashCode,equalse,containsAll,removeAll,retainAll等方法都会隐式的Iterate,也即可能抛出ConcurrentModificationException。
public class VectorDemo {
public static void main(String[] args) {
Vector<String> stringVector = new Vector<>();
for(int i =0;i<100;i++){
stringVector.add("demo"+i);
}
// 获取strignvector里面的元素必须使用迭代器
// 多线程的时候需要对迭代器加锁
Iterator<String> iterator = stringVector.iterator();
while(iterator.hasNext()){
String next = iterator.next();
if(next.equals("demo2")){
iterator.remove();
}
}
}
}
并发容器CopyOnWrite、Concurrent、BlockingQueue 根据具体场景进行设计,尽量避免使用锁,提高容器的并发访问性。 ConcurrentBlockingQueue:基于queue实现的FIFO的队列。队列为空,取操作会被阻塞ConcurrentLinkedQueue,队列为空,取得时候就直接返回空。
LinkedBlockingQueue的使用:
在并发编程中,LinkedBlockingQueue使用的非常频繁。因其可以作为生产者消费者的中间商
add 实际上调用的是offer,区别是在队列满的时候,add会报异常
offer 对列如果满了,直接入队失败
put("111"); 在队列满的时候,会进入阻塞的状态
remove(); 直接调用poll,唯一的区别即使remove会抛出异常,而poll在队列为空的时候直接返回null
poll(); 在队列为空的时候直接返回null
take(); 在队列为空的时候,会进入等待的状态
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingDeque<String> string = new LinkedBlockingDeque<>();
// 往队列里面存放元素 put 和take使用比较多
string.add("a");
string.offer("b");
string.put("c");
// 从队列中取元素
String remove = string.remove();
String poll = string.poll();
String take = string.take();
}
}