使用synchronized关键词的问题在于加锁范围是固定的,无法把锁在对象之间进行传递,使用起来不灵活,但是也不容易出错。
无法获取锁时,tryLock返回false,不会阻塞当前线程
读取时使用共享锁,写入时使用排它锁。适合解决与常见的读者-作者问题类似的场景。在没有线程写入时,多个读线程可以获取读锁;而在写入操作进行时,只能有一个线程获取锁。
允许一个线程多次获取同一个锁。
public class ReentrantLockDemo { private final ReentrantLock lock = new ReentrantLock(); private int value = 0; public int getNext() { lock.lock(); try { return value++; } finally { lock.unlock(); } } }
condition的await相当于Object的wait方法,而signal和signalAll相当于notify和notifyAll方法
若程序中的同步可以抽象成为对有限个资源的同步访问,可以使用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(); } } }
信号量在操作系统中一般用来管理数量有限的资源。信号量的值表示资源的可用数量。使用资源时,先从信号量上获取一个使用许可,成功获取许可之后,资源的可用数减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); } }
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); } }
类似倒数闸门,但是可以循环使用,另外使用循环屏障的线程之间是相互平等的,彼此都需要等待对方完成。
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(); } }
对象交换器适合于两个线程需要进行数据交换的场景。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(); } }
线程安全式的阻塞式队列。封装了生产者-消费者场景所需的语义。当队列满的时候,向队列中添加数据的方法会阻塞当前线程,当队列空,从队列获取数据的方法会阻塞当前线程。
它支持阻塞和非阻塞方式的操作,take和put方法是阻塞的(offer和poll是非阻塞的)。
具体实现有基于数组固定元素个数的ArrayBlockingQueue和基于链表结构的不固定元素个数的LinkedBlockingQueue的实现。
与BlockingQueue类似,不过它是双向的,可以对头尾进行添加和删除操作。
标准库中提供了基于链表的实现:LinkedBlockingDeque类。
BlockingQueue和BlockingDeque接口的实现都是阻塞式队列,如果队列中所包含的元素数量没有限制,可以使用ConcurrentLinkedQueue和ConcurrentLinkedDeque类,它们在实现中使用了非阻塞式算法,避免了使用锁机制,性能比较好。
putIfAbsent方法只有在散列表中不包含给定的键时,才会把给定的值放入散列表中。
使用iterator创建迭代器,该迭代器只与集合保持弱一致性,不一定可以反映出迭代器创建之后散列表所发生的修改。
对列表的更新操作会重新建一个底层的副本,使用这个副本来存储数据,对更新操作是加锁的,读取操作是不加锁的。一般适用于读取操作多于写操作的场景。
使用iterator创建迭代器,如果创建之后list有更新操作,该迭代器指向的是旧的底层数组。
Future的get方法可以获取异步任务的执行结果,如果任务没有完成,那么调用get方法的线程会处于等待状态,直到任务完成或被取消。
invokeAll会等待所有任务完成之后返回每个任务对应的Future接口实现对象的列表
invokeAny是任何一个任务成功完成之后,都会把执行结果返回给调用者
shutdown方法只是不允许新的任务被提交
shutdownNow会试图终止正在运行和等待运行的任务,并返回已经提交但还没有被运行的任务列表。(通常是向线程发出中断请求,因此确保提交的任务实现了正确的中断处理逻辑,能够在收到中断请求时进行必要的清理工作并结束任务)
这两个方法都不会等待服务真正关闭,只是发出关闭请求。
awaitTermination方法使当前线程在一定时间内等待服务完成关闭。
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(); } }