同步(二)

一、原子变量:

1、AtomicInteger:原子Integer类型

构造:

public AtomicInteger(int initialValue)
public AtomicInteger()
复制代码

方法:

//以原子方式获取旧值并设置新值
public final int getAndSet(int newValue)
//以原子方式获取旧值并给当前值加1
public final int getAndIncrement()
//以原子方式获取旧值并给当前值减1
public final int getAndDecrement()
//以原子方式获取旧值并给当前值加delta
public final int getAndAdd(int delta)
//以原子方式给当前值加1并获取新值
public final int incrementAndGet()
//以原子方式给当前值减1并获取新值
public final int decrementAndGet()
//以原子方式给当前值加delta并获取新值
public final int addAndGet(int delta)
复制代码

例如:incrementAndGet方法以原子方式将变量自增,并返回自增后的值。也就是说,获得值、增1并设置然后生成新值的操作不会中断。

相关原理:

private volatile int value;
复制代码

通过volatile进行修饰,保证内存可见性

其他类型:

  • AtomicBoolean:原子Boolean类型
  • AtomicLong:原子Long类型
  • AtomicReference:原子引用类型 ......

2、AtomicIntegerArray:原子数组类型

构造:

public AtomicIntegerArray(int length) {
    array = new int[length];
}

public AtomicIntegerArray(int[] array) {
    this.array = array.clone();
}

复制代码

常用方法:

public final boolean compareAndSet(int i, int expect, int update)
public final int getAndIncrement(int i)
public final int getAndAdd(int i, int delta)
复制代码
二、并发容器

1、CopyOnWriteArrayList

特点:

  • 它是线程安全的,可以被多个线程并发访问
  • 它的迭代器不支持修改操作
  • 它以原子方式支持一些复合操作
private transient volatile Object[] elements;

复制代码

通过volatile进行修饰保证内存可见性

存入元素

/**
 * 不存在就添加,并返回true
 */
public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}


private boolean addIfAbsent(E e, Object[] snapshot) {
    //通过synchronized实现线程同步
    synchronized (lock) {
        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]
                    && Objects.equals(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;
    }
}
复制代码

获取元素:

public E get(int index) {
    return get(getArray(), index);
}
private E get(Object[] a, int index) {
    return (E) a[index];
}
复制代码

总结:CopyOnWriteArrayList数组通过volatile修饰保证内存可见,写操作通过synchronized实现同步,性能会低一些,读操作不需要同步。适用于多线程操作写操作比较少,读操作比较多的情况。

2、ConcurrentHashMap

特点:

  • 线程安全
  • 分段锁
  • 读不需要加锁

原理:

  • ConcurrentHashMap采用分段锁技术。根据哈希值将将数据分为多段,而每个段有一个独立的锁,每一个段相当于一个独立的哈希表。 无论是保存键值对还是根据键查找,都先根据键的哈希值映射到段,再在段对应的哈希表上进行操作。
  • 对于写操作,需要获取锁,不能并行,但是读操作可以,多个读可以并行,写的同时也可以读。
三、阻塞队列

1、阻塞队列使用场景:

  • 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞,直到有数据放入队列。
  • 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞,直到队列中有空的位置,线程被自动唤醒。

2、常见阻塞队列

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  • DelayQueue:使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue:不存储元素的阻塞队列。
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

3、使用:

public class BlockTest{

  private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

  public static void main(String[] args) {
    BlockTest test = new BlockTest();
    Product product = test.new Product();
    product.start();
    Consume consume = test.new Consume();
    consume.start();
  }

  class Product extends Thread {
    @Override
    public void run() {
      try {
        queue.put(1);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  class Consume extends Thread {
    @Override
    public void run() {
      try {
        queue.take();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
复制代码

4、原理:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    ......
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;
    
    //获取元素
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //元素被空进行等待
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length) takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        //唤醒其他线程
        notFull.signal();
        return x;
    }
    
    //存储元素
    public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        //元素个数等于数组长度进行等待,当被其他线程唤醒时插入元素
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
    
    private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length) putIndex = 0;
        count++;
        //唤醒其他线程
        notEmpty.signal();
    }
    
    ......
}
复制代码

总结:主要通过ReentrantLock加锁,避免多线程引起的线程安全问题。通过await和signal进行消费者线程和生产者线程的相互切换。

四、同步器:

1、信号量Semaphore

//传入参数为许可数
private static Semaphore semaphore = new Semaphore(2);

public static void main(String[] args) {
    for(int i = 0; i < 5; i ++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //获取许可
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " " + new Date());
                    Thread.sleep(5000l);
                    //释放许可
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.err.println(Thread.currentThread().getName() + " interrupted");
                }
            }
        }).start();
    }
}
复制代码

限制对资源的并发访问数

2、倒计时门栓CountDownLatch

它相当于是一个门栓,一开始是关闭的,所有希望通过该门的线程都需要等待,然后开始倒计时,倒计时变为0后,门栓打开,等待的所有线程都可以通过。

public static void main(String[] args) {
    CountDownLatch countDownLatch = new CountDownLatch(5);
    for(int i = 0; i < 5; i ++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " " + new Date() + " run");
                try {
                    Thread.sleep(5000l);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }
        }).start();
    }
    try {
        //await()检查计数是否为0,如果大于0,就等待
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
复制代码

3、障栅CyclicBarrier

CyclicBarrier阻塞调用的线程,直到条件满足时,阻塞的线程同时被打开。

public static void main(String[] args) {
    Random random = new Random();
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    for(int i = 0; i < 5; i ++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int secs = random.nextInt(5);
                System.out.println(Thread.currentThread().getName() + " " + new Date() + " run, sleep " + secs + " secs");
                try {
                    Thread.sleep(secs * 1000);
                    //调用await后,表示自己已经到达,如果自己是最后一个到达的,就执行可选的命令,执行后,唤醒所有等待的线程
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " " + new Date() + " runs over");
            }
        }).start();
    }
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值