JAVA多线程工具应用

1. 线程简介及安全性

1.1 线程的优点

  • 发挥多处理器的强大功能
  • 建模的简单性 为模型中的每种类型任务都分配一个专门的线程
  • 异步事件的简化处理
  • 响应更灵敏的用户界面

1.2 线程带来的风险

  • 安全性问题
  • 活跃性问题 如发生死锁,无限等待
  • 性能问题 如频繁的上下文切换:保存与恢复上下文,丢失局部性,CPU花在线程切换上更多开销而不是线程上;如同步机制,抑制某些编译器优化,使内存缓存区内存无效,增加共享内存总线流量

1.3 线程安全性

如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。有三种方式可以修复这个问题:
√ 不在线程之间共享该变量
√ 将状态变量修改为不可变变量
√ 在访问状态变量时使用同步

无状态对象一定是线程安全的。
无状态对象:他既不包含任何域,也不包含对其他类中域的引用
竞态条件: 由于不恰当的执行时序而出现的不正确的结果
竞态条件出现常见操作: 先检查后执行,如延迟初始化; 读取-修改-写入, 如i++

1.3.1 内置锁与重入

JAVA中提供了一种内置锁机制支持原子性:同步代码块
同步代码块分为两部分:
1)锁的对象引用 synchronized (lock) { }
2)锁保护的代码块

public void synchronized count() {
	.....
}

内置锁是可重入的,如果某个线程试图获得该线程已经获得的锁,那么该请求会成功
重入实现方式: 为每个锁关联一个获取计数器与一个所有者线程,线程进入则+1,线程退出则-1。当计数器为0,该锁将被释放

1.3.2 活跃性与性能

当实现某个同步策略时,一定不要盲目为了性能而牺牲简单性(这可能会破环安全性)

当执行时间较长的计算或者可能无法完成的操作时(例如,网络I/O或控制台I/O),一定不要持有锁

2. CompletionService优点及其应用

2.1 需求描述

某一图片网站首页有许多图片,渲染时间较长,给用户带来较差体验,为提高用户体验度,图片需缓存且无需等待所有图片全部准备完毕后,进行渲染

2.2 问题分析与实现

显而易见,我们可以想到一边获取图片,一边进行渲染,并行操作。但是每张图片获取的时间无法预知,即任务的执行时长不一致,有的可能几毫秒,有的可能几秒, 我们如何才能做到先获取的图片先进行渲染呢?幸好CompletionService适合该场景: 将Executor与BlockingQueue的功能融合在一起,将Callable任务提交给它执行,然后类似队列中的take与poll获取已经完成的任务。

2.3 CompletionService源码分析

public interface CompletionService<V> {
        Future<V> submit(Callable<V> task);

        Future<V> take() throws InterruptedException;

        Future<V> poll();

        ......
}

CompletionService唯一实现类: ExecutorCompletionService。 在构建函数中创建一个BlockingQueue保存结果。任务提交后,将任务包装成QueueingFuture(FutureTask的一个子类),任务完成后,调用QueueingFuture的done方法,将结果放入BlockingQueue中。

public class ExecutorCompletionService<V> implements CompletionService<V> {
    	private final BlockingQueue<Future<V>> completionQueue;

    	public ExecutorCompletionService(Executor executor) {
                if (executor == null)
                    throw new NullPointerException();
                this.executor = executor;
                this.aes = (executor instanceof AbstractExecutorService) ?
                    (AbstractExecutorService) executor : null;
                this.completionQueue = new LinkedBlockingQueue<Future<V>>();
        }

    	public Future<V> submit(Callable<V> task) {
                if (task == null) throw new NullPointerException();
                RunnableFuture<V> f = newTaskFor(task);
                executor.execute(new QueueingFuture(f)); // 任务包装成QueueingFuture
                return f;
        }

        private class QueueingFuture extends FutureTask<Void> {
                QueueingFuture(RunnableFuture<V> task) {
                    super(task, null);
                    this.task = task;
                }
                protected void done() { completionQueue.add(task); } // 任务完成后,调用done方法,放入队列
                private final Future<V> task;
        }

        public Future<V> take() throws InterruptedException {
                return completionQueue.take();
        }

        public Future<V> poll() {
                return completionQueue.poll();
        }
}

2.4 代码实现

/**
 * 使用CompletionService实现页面渲染器
 */
public class Renderer {

        private ExecutorService executor;

        public Renderer(ExecutorService executor) {
                this.executor = executor;
        }

        public void renderPage(CharSequence source) {
                List<ImageInfo> info = scanImageInfo(source);
                CompletionService<ImageData> completionService = new ExecutorCompletionService<>(executor);

                for (final ImageInfo imageInfo : info) {
                        completionService.submit(() -> {
                                return imageInfo.downloadImage();
                        });
                }

                renderText(source);

                try {
                        for (int i = 0; i < info.size(); i++) {
                                Future<ImageData> future = completionService.take();
                                ImageData imageData = future.get();
                                renderImage(imageData);
                        }
                } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                } catch (ExecutionException e) {
                        e.printStackTrace();
                }
        }
}

3. CopyOnWriteArrayList实现原理

3.1 实现原理

CopyOnWriteArrayList是线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略。对于写操作,比如向容器中添加一个元素,首先将当前容器复制一份,然后在新副本上执行写操作,然后将原容器的引用指向新容器

3.2 优点

读操作不加锁,性能很高,适合读多写少的并发场景,使用迭代器遍历时候,不会抛出ConcurrentModificationException异常

3.3 缺点

1)内存占用问题 每次写操作都要创建底层数组的新副本,当数据量大时,内存压力较大,可能引起频繁GC
2)无法保证实时性 写和读操作作用在不同的容器上,在写做操作执行的过程中,读操作不会阻塞但是会读取到老容器的数据

3.4 源码分析

add/remove方式使用ReentrantLock,操作前lock(),操作时Arrays.copyOf进行数组拷贝,操作后unlock()

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
   public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

读操作时无需加锁,直接读取

private E get(Object[] a, int index) {
        return (E) a[index];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法小生Đ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值