多线程高级使用工具

1、高级同步机制

使用synchronized关键词的问题在于加锁范围是固定的,无法把锁在对象之间进行传递,使用起来不灵活,但是也不容易出错。

(1)tryLock

无法获取锁时,tryLock返回false,不会阻塞当前线程


(2)ReadWriteLock

读取时使用共享锁,写入时使用排它锁。适合解决与常见的读者-作者问题类似的场景。在没有线程写入时,多个读线程可以获取读锁;而在写入操作进行时,只能有一个线程获取锁。


(3)ReentrantLock

允许一个线程多次获取同一个锁。

public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private int value = 0;
    public int getNext() {
        lock.lock();
        try {
            return value++;
        } finally {
            lock.unlock();
        }
    }
}


(4)condition

condition的await相当于Object的wait方法,而signal和signalAll相当于notify和notifyAll方法


2、底层同步器

若程序中的同步可以抽象成为对有限个资源的同步访问,可以使用java.util.concurrent.locks.AbstractQueuedSynchronizer类作为实现的基础。

(1)静态内部类集成它

(2)选择共享模式还是排他模式

(3)实现对资源的获取和释放

public class QueuedSynchronizerDemo {

    private final InnerSynchronizer synchronizer;

    private static class InnerSynchronizer extends AbstractQueuedSynchronizer {
        InnerSynchronizer(int numOfResources) {
            setState(numOfResources);
        }
        protected int tryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                        compareAndSetState(available, remaining)) {
                    return remaining;
                }
            }
        }
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int available = getState();
                int next = available + releases;
                if (compareAndSetState(available, next)) {
                    return true;
                }
            }
        }
    }

    public QueuedSynchronizerDemo(int numOfResources) {
        synchronizer = new InnerSynchronizer(numOfResources);
    }
    public void acquire() throws InterruptedException {
        synchronizer.acquireSharedInterruptibly(1);
    }
    public void release() {
        synchronizer.releaseShared(1);
    }

    public static void main(String[] args) {
        final QueuedSynchronizerDemo manager = new QueuedSynchronizerDemo(2);
        for (int i = 0; i < 5; i++) {
            new Thread() {
                public void run() {
                    try {
                        manager.acquire();
                        String name = Thread.currentThread().getName();
                        System.out.println(name + " ACQUIRE");
                        Random random = new Random();
                        Thread.sleep(random.nextInt(5000));
                        manager.release();
                        System.out.println(name + " RELEASE");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
        try {
            Thread.sleep(300 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



3、高级同步对象

(1)信号量

信号量在操作系统中一般用来管理数量有限的资源。信号量的值表示资源的可用数量。使用资源时,先从信号量上获取一个使用许可,成功获取许可之后,资源的可用数减1。完成对资源的使用之后,需要在信号量上释放一个使用许可。

当资源可用数为0的时候,可以以阻塞的方式等待资源变为可用,也可以过段时间后再检查资源是否变为可用。

Semaphore可指定资源的可用数,acquire以阻塞方式获取,tryAcquire以非阻塞方式获取,release释放资源许可。acquireUninterruptibly方法可以不被中断。

public class SemaphoreDemo {

    private final Semaphore semaphore;
    private final List<Printer> printers = new ArrayList<>();

    public SemaphoreDemo(Collection<? extends Printer> printers) {
        this.printers.addAll(printers);
        this.semaphore = new Semaphore(this.printers.size(), true);
    }
    public Printer acquirePrinter() throws InterruptedException {
        semaphore.acquire();
        return getAvailablePrinter();
    }
    public void releasePrinter(Printer printer) {
        putBackPrinter(printer);
        semaphore.release();
    }

    //注意同步,semaphore只是资源数量的抽象,不负责资源本身的同步
   private synchronized Printer getAvailablePrinter() {
        Printer result = printers.get(0);
        printers.remove(0);
        return result;
    }
    private synchronized void putBackPrinter(Printer printer) {
        printers.add(printer);
    }
}


(2)倒数闸门

CountDownLatch相当于多个线程等待开启的一个闸门,只有在其他线程完成任务之后,闸门才会开启,等待的线程才能运行。当执行任务的线程完成之后,调用countDown方法使待完成的任务数减1,等待任务完成的线程使用await方法进入阻塞状态直到待完成的任务数变为0。当所有任务都完成时,等待任务完成的线程会从await方法返回,可以继续执行后续代码。注意它的使用是一次性的。

public class CountDownLatchDemo {

    private static final ConcurrentHashMap<String, Integer> sizeMap = new ConcurrentHashMap<>();
    private static class GetSizeWorker implements Runnable {
        private final String urlString;
        private final CountDownLatch signal;
        public GetSizeWorker(String urlString, CountDownLatch signal) {
            this.urlString = urlString;
            this.signal = signal;
        }
        public void run() {
            try {
                InputStream is = new URL(urlString).openStream();
                int size = IOUtils.toByteArray(is).length;
                sizeMap.put(urlString, size);
            } catch (IOException e) {
                sizeMap.put(urlString, -1);
            } finally {
                signal.countDown();
            }
        }
    }
    private void sort() {
        List<Map.Entry<String, Integer>> list = new ArrayList<>(sizeMap.entrySet());
        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
            public int compare(Map.Entry<String, Integer> o1,
                               Map.Entry<String, Integer> o2) {
                return Integer.compare(o2.getValue(), o1.getValue());
            }
        });
        System.out.println(Arrays.deepToString(list.toArray()));
    }
    public void sortPageSize(Collection<String> urls) throws InterruptedException {
        CountDownLatch sortSignal = new CountDownLatch(urls.size());
        for (String url : urls) {
            new Thread(new GetSizeWorker(url, sortSignal)).start();
        }
        sortSignal.await();
        sort();
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatchDemo pso = new CountDownLatchDemo();
        List<String> urls = Arrays.asList(new String[] {
                "http://www.baidu.com",
                "http://www.sina.com.cn",
                "http://www.163.com"
        });
        pso.sortPageSize(urls);
    }
}


(3)循环屏障

类似倒数闸门,但是可以循环使用,另外使用循环屏障的线程之间是相互平等的,彼此都需要等待对方完成。

CyclicBarrier类的await方法进入等待状态,在此状态中出现异常,所有线程都将会结束。

public class CyclicBarrierDemo {

    private static final int TOTAL_COUNT = 5000;
    private static final int RANGE_LENGTH = 200;
    private static final int WORKER_NUMBER = 5;
    private static volatile boolean done = false;
    private static int rangeCount = 0;
    private static final List<Long> results = new ArrayList<Long>();

    private static final CyclicBarrier barrier = new CyclicBarrier(WORKER_NUMBER, new Runnable() {
        public void run() {
            if (results.size() >= TOTAL_COUNT) {
                done = true;
            }
        }
    });
    private static class PrimeFinder implements Runnable {
        public void run() {
            while (!done) {
                int range = getNextRange();
                long start = range * RANGE_LENGTH;
                long end = (range + 1) * RANGE_LENGTH;
                for (long i = start; i < end; i++) {
                    if (isPrime(i)) {
                        updateResult(i);
                    }
                }
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    done = true;
                }
            }
        }
    }
    private synchronized static void updateResult(long value) {
        results.add(value);
    }
    private synchronized static int getNextRange() {
        return rangeCount++;
    }
    private static boolean isPrime(long number) {
        if (number == 2) {
            return true;
        }
        if (number <= 1 || number % 2 == 0)
            return false;
        for (int i = 3; i * i <= number; i += 2) {
            if (number % i == 0) {
                return false;
            }
        }
        return true;
    }
    public void calculate() {
        Thread thread1 = new Thread(new PrimeFinder());
        thread1.start();
        for (int i = 0; i < WORKER_NUMBER - 1; i++) {
            new Thread(new PrimeFinder()).start();
        }
        if (!done) {
            thread1.interrupt();
        }
        while (!done) {

        }
        System.out.println(results.size());
        System.out.println(Arrays.deepToString(results.toArray()));
    }
    public static void main(String[] args) {
        CyclicBarrierDemo pn = new CyclicBarrierDemo();
        pn.calculate();
    }
}


(4)对象交换器

对象交换器适合于两个线程需要进行数据交换的场景。exchange方法的返回结果是另外一个线程所提供的相同类型的对象,如果另外一个线程尚未完成对数据的处理,那么exchange方法会使当前线程进入等待状态,直到另外一个线程也调用了exchange方法来进行数据交换。

public class ExchangerDemo {

    private final Exchanger<StringBuilder> exchanger = new Exchanger<StringBuilder>();
    private class Sender implements Runnable {
        public void run() {
            try {
                StringBuilder content = new StringBuilder("Hello");
                content = exchanger.exchange(content);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    private class Receiver implements Runnable {
        public void run() {
            try {
                StringBuilder content = new StringBuilder("World");
                content = exchanger.exchange(content);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public void exchange() {
        new Thread(new Sender()).start();
        new Thread(new Receiver()).start();
    }

    public static void main(String[] args) {
        ExchangerDemo prog = new ExchangerDemo();
        prog.exchange();
    }
}


4、数据结构

(1)队列
A、BlockingQueue接口

线程安全式的阻塞式队列。封装了生产者-消费者场景所需的语义。当队列满的时候,向队列中添加数据的方法会阻塞当前线程,当队列空,从队列获取数据的方法会阻塞当前线程。

它支持阻塞和非阻塞方式的操作,take和put方法是阻塞的(offer和poll是非阻塞的)。

具体实现有基于数组固定元素个数的ArrayBlockingQueue和基于链表结构的不固定元素个数的LinkedBlockingQueue的实现。


B、BlockingDeque接口

与BlockingQueue类似,不过它是双向的,可以对头尾进行添加和删除操作。

标准库中提供了基于链表的实现:LinkedBlockingDeque类。


C、非阻塞队列

BlockingQueue和BlockingDeque接口的实现都是阻塞式队列,如果队列中所包含的元素数量没有限制,可以使用ConcurrentLinkedQueue和ConcurrentLinkedDeque类,它们在实现中使用了非阻塞式算法,避免了使用锁机制,性能比较好。


(2)集合类
A、ConcurrentHashMap

putIfAbsent方法只有在散列表中不包含给定的键时,才会把给定的值放入散列表中。

使用iterator创建迭代器,该迭代器只与集合保持弱一致性,不一定可以反映出迭代器创建之后散列表所发生的修改。


B、CopyOnWriteArrayList

对列表的更新操作会重新建一个底层的副本,使用这个副本来存储数据,对更新操作是加锁的,读取操作是不加锁的。一般适用于读取操作多于写操作的场景。

使用iterator创建迭代器,如果创建之后list有更新操作,该迭代器指向的是旧的底层数组。


5、任务执行

(1)Future的get方法

Future的get方法可以获取异步任务的执行结果,如果任务没有完成,那么调用get方法的线程会处于等待状态,直到任务完成或被取消。


(2)invokeAll和invokeAny

invokeAll会等待所有任务完成之后返回每个任务对应的Future接口实现对象的列表

invokeAny是任何一个任务成功完成之后,都会把执行结果返回给调用者


(3)shutdown和shutdownNow

shutdown方法只是不允许新的任务被提交

shutdownNow会试图终止正在运行和等待运行的任务,并返回已经提交但还没有被运行的任务列表。(通常是向线程发出中断请求,因此确保提交的任务实现了正确的中断处理逻辑,能够在收到中断请求时进行必要的清理工作并结束任务)

这两个方法都不会等待服务真正关闭,只是发出关闭请求。

awaitTermination方法使当前线程在一定时间内等待服务完成关闭。


(4)schedule、scheduleAtFixedRate与scheduleWithFixedDelay

schedule可以调度一个任务在延迟若干时间之后再执行

scheduleAtFixedRate则调度一个任务在初始的延迟时间后,每隔一段时间重复执行,在下一次执行开始时,上一次执行可能还没有结束(执行任务的重叠)

scheduleWithFixedDelay则是在上一个任务执行完成之后,经过给定的时间间隔才开始下一次的执行,不会造成执行任务的重叠。


public class FileDownloader {

    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    public boolean download(final URL url, final Path path) {
        Future<Path> future = executor.submit(new Callable<Path>() {
            public Path call() {
                try {
                    InputStream is = url.openStream();
                    Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
                    return path;
                } catch (IOException e) {
                    return null;
                }
            }
        });
        try {
            return future.get() != null ? true : false;
        } catch (InterruptedException | ExecutionException e) {
            return false;
        }
    }

    //需要关闭ExecutorService接口的实现对象,否则虚拟机不会退出,占用的内存不会释放
    public void close() {
        //先停止接收新任务,发出关闭请求
        executor.shutdown();
        try {
            //在指定时间等待服务关闭
            if (!executor.awaitTermination(3, TimeUnit.MINUTES)) {
                //指定时间服务未关闭,则强制结束
                executor.shutdownNow();
                //再等待一定时间使得被强制结束的任务完成必要的清理工作
                executor.awaitTermination(1, TimeUnit.MINUTES);
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) throws MalformedURLException {
        FileDownloader downloader = new FileDownloader();
        downloader.download(new URL("http://www.baidu.com"), Paths.get("baidu.txt"));
        downloader.download(new URL("http://www.sina.com.cn"), Paths.get("sina.txt"));
        downloader.close();
    }
}



你可能感兴趣的:(多线程高级使用工具)