【Java编程的思想】同步和协作工作类

读写锁ReentrantReadWriteLock

之前介绍了两种锁:synchronized和显式锁ReentrantLock,对于同一受保护对象的访问,无论是读还是写,它们都要求获得相同的锁。但在一些场景中,多个线程的读操作完全可以并行。

在Java并发包中,接口ReadWriteLock表示读写锁,主要实现类是可重入读写锁ReentrantReadWriteLock。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

通过一个ReadWirteLock产生两个锁:一个读锁,一个写锁。读操作使用读锁,写操作使用写锁。“读-读”操作是可以并行的,“读-写”,“写-写”操作不可以

只有一个线程可以进行写操作,在获取写锁时,只有没有任何线程持有任何锁才可以获取到,在持有写锁时,其他任何线程都获取不到任何锁。
在没有其他线程持有写锁的情况下,多个线程可以获取和持有读锁。

内部,它们使用同一个整数变量表示锁的状态,16位给读锁用,16位给写锁用,使用一个变量便于进行CAS操作,锁的等待队列其实也只有一个。

信号量Semaphore

之前介绍的锁都是限制只有一个线程可以同时访问一个资源。
有的情况下,单个资源可以即使可以被并发访问,但并发访问数多了可能影响性能,所以希望限制并发访问的线程数。

信号量Semaphore可以限制对资源的并发访问数

public Semaphore(int permits)
public Semplhore(int permits, boolean fair)

fair表示是否公平,permits表示许可数量

主要方法有两类:获取许可和释放许可

// 阻塞获取许可,可响应中断
public void acquire() throws InterruptedException;
// 阻塞获取许可,不响应中断
public void acquireUninterruptibly();
// 批量获取多个许可
public void acquire(int permits) throws InterruptedException;
public void acquireUninterruptibly(int permits);
// 尝试获取
public boolean tryAcquire();
// 限定等待时间获取
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException;

// 释放许可
public void release();

信号量的基本原理比较简单,基于AQS实现的,permits表示共享的锁个数,acquire方法就是检查锁个数是否大于0,大于则减一,获取成功,否则就等待,release就是将锁个数加一,唤醒第一个等待的线程。

倒计时门闩CountDownLatch

CountDownLatch相当于一个门闩,一开始是关闭的,所有希望通过该门的线程都需要等待,然后开始倒计时,倒计时变为0后,门闩打开,等待的所有线程都可以通过,它是一次性的,打开后就不能再关上了。

CountDownLatch的两种应用场景:

线程同时开始

public class RacerWithCountDownLatch {
    static class Racer extends Thread {
        CountDownLatch latch;
        public Racer(CountDownLatch latch) {
            this.latch = latch;
        }
        @Override
        public void run() {
            try {
                // 检查计数是否为0,如果大于0就等待,await可以被中断
                this.latch.await();
                System.out.println(getName() + " start Run ");
            } catch (InterruptedException e) {
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num = 10;
        CountDownLatch countDownLatch = new CountDownLatch(1);
        Thread[] racers = new Thread[num];
        for (int i = 0; i < racers.length; i++) {
            racers[i] = new Racer(countDownLatch);
            racers[i].start();
        }
        Thread.sleep(2000);
        // 检查计数,如果已经为0,直接返回;否则减少计数。 如果新的计数变为0,则唤醒所有等待的线程
        countDownLatch.countDown();
    }
}

主从协作

public class MasterWorkerDemo {
    static class Worker extends Thread {
        CountDownLatch latch;
        public Worker(CountDownLatch latch) {
            this.latch = latch;
        }
        @Override
        public void run() {
            try {
                Thread.sleep((long) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                this.latch.countDown();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int workerNum = 100;
        CountDownLatch countDownLatch = new CountDownLatch(workerNum);
        Worker[] worker = new Worker[workerNum];
        for (int i = 0; i < worker.length; i++) {
            worker[i] = new Worker(countDownLatch);
            worker[i].start();
        }
        System.out.println("Main wait");
        countDownLatch.await();
        System.out.println("Main run");
    }
}

循环栅栏CyclicBarrier

CyclicBarrier相当于是一个栅栏,所有线程在到达该栅栏后都需要等待其他线程,等所有线程都到达后再一起通过,它是循环的,可以用作重复的同步。

// 表示参与的线程个数
public CyclicBarrier(int parties)
// 接口一个Runnable参数,当所有线程到达栅栏后,在所有线程执行下一步动作前,运行参数中的动作,这个动作由最后一个到达栅栏的线程执行
public CyclicBarrier(int parties, Runnable barrierAction)

// 表示自己已经到达,如果自己是最后一个到达的,就执行可选的命令,执行后,唤醒所有等待的线程,最后重置内部的同步计数器。
public int await() throw InterruptedException, BrokenBarrierException;
// 可以限定最长等待时间
public int await() throw InterruptedException, BrokenBarrierException, TimeoutException;

在CyclicBarrier中,参与的线程是互相影响的,只要其他一个线程在调用await时被中断了,或者超时了,栅栏就会被破坏。如果栅栏动作抛出了异常,栅栏也会被破坏。被破坏后,所有在调用await的线程就会退出,抛出BrokenBarrierException

CyclicBarrier与CountDownLatch的区别:
1. CyclicBarrier可以重复利用,CountDownLatch是一次性的
2. CyclicBarrier的参与线程角色是一样的,用于同一角色线程间的协调一致,大家相互等待。CountDownLatch的参与线程是不同角色,有的负责倒计时,有的在等待。
3. CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数

ThreadLocal

直接看用法吧

public class ThreadLocalDemo {
    static ThreadLocal<Integer> local = new ThreadLocal<Integer>();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("child thread init : " + local.get());
                local.set(998);
                System.out.println("child thread final : " + local.get());
            }
        };
        local.set(10);
        System.out.println("main thread:" + local.get());
        thread.start();
        thread.join();
        System.out.println("main thread final :" + local.get());
    }
}

local是一个静态变量,main方法创建了一个子线程,在主线程和子线程都访问了local。输出如下:

main thread:10
child thread init : null
child thread final : 998
main thread final :10

说明它们访问的虽然是同一个变量local,但每个线程都有自己独立的值,这就是ThreadLocal的作用。

除了get/set方法,还有两个常用方法

// 用于提供初始值,当调用get方法时,如果之前没有设置过,会调用该方法获取初始值,默认返回null
protected T initialValue();
// 删除当前线程对应的值
public void remove();

ThreadLocal原理

public ThreadLocal() {
}

public void set(T value) {
    Thread t = Thread.currentThread();
    // 获取ThreadLcalMap,ThreadLcalMap是一个内部类,与一般的map不同,它的键类型为WeakReference<ThreadLocal>,利于回收内存
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 在线程自己的ThreadLcalMap中设置一个条目,键为当前的ThreadLocal对象,值是value
        map.set(this, value);
    else
        // 创建ThreadLcalMap并存入值
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


public T get() {
    Thread t = Thread.currentThread();
    // 获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 调用setInitialValue
    return setInitialValue();
}

private T setInitialValue() {
    // 调用initialValue获取值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值