对Java多线程在Netty中的应用的理解

来源:互联网 发布:java自动包装功能 编辑:程序博客网 时间:2024/05/29 18:44

对共享的可变数据同步

经常使用Java的老司机都知道,synchronized关键字可以保证同一时刻只有一个线程执行某个方法或者代码块。同步的作用不仅仅是互斥,也是共享可变数据。当一个线程修改可变数据并释放锁后,其他线程可以获得修改后的新值。
下面以ServerBootstrap类为例来分析,该类的初始变量有如下代码:

private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();    private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();

而私有构造函数和使用函数如下:

private ServerBootstrap(ServerBootstrap bootstrap) {        super(bootstrap);        childGroup = bootstrap.childGroup;        childHandler = bootstrap.childHandler;        synchronized (bootstrap.childOptions) {            childOptions.putAll(bootstrap.childOptions);        }        synchronized (bootstrap.childAttrs) {            childAttrs.putAll(bootstrap.childAttrs);        }    } /**     * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created     * (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set     * {@link ChannelOption}.     */    public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {        if (childOption == null) {            throw new NullPointerException("childOption");        }        if (value == null) {            synchronized (childOptions) {                childOptions.remove(childOption);            }        } else {            synchronized (childOptions) {                childOptions.put(childOption, value);            }        }        return this;    }

由于使用非线程安全的LinkedHashMap,所以当多线程创建、访问和修改LinkedHashMap时,必须在外部进行同步。
由于ServerBootstrap是被外部创建使用的,即我们无法保证它的方法和成员变量不被并发访问,因此需要在它的成员变量childOption上进行正确的同步。由于考虑锁的粒度尽可能小,对参数childOption和value合法性判断不需要加锁。因此,代码对两个判断分支独立加锁,保证锁的范围尽可能的小。

Netty中锁的使用技巧

对应许多刚刚接触编程的人来说,虽然意识到并发访问时变量需要加锁,但是对加锁的范围、时机及锁之间的协同不清楚,这样往往会出现一些问题。结合Netty中中锁的使用,来说说锁的使用技巧。
1. wait方法用来使线程等待某个条件,它必须在同步块内被调用,这个同步块通常会锁定当前对象实例。
2. 始终使用wait循环来调用wait方法,永远不要在循环之外调用wait方法。这样做的原因是尽管不满足被唤醒条件,但是由于其他线程调用notifyAll()方法会导致被阻塞线程意外唤醒,从而破坏被锁保护的约定关系,引起不可预估的错误。
3. 唤醒线程,保守的做法是使用notifyAll()方法唤醒所有等待的线程,从优化的角度来讲,如果处于等待的所有线程都在等待统一条件,而每次只有一个线程可以被唤醒,那么就应该使用notify()。

volatile的使用

理解volatile很简单,它是Java提供的轻量级同步机制,Java内存模型中对volatile专门定义了一些特殊访问规则:
1. 线程可见性,即一个线程修改volatile变量之后,其他线程可以立即看到最新的修改。
2. 禁止指令重排序优化,普通的变量仅仅保证该方法的执行过程中使用依赖赋值结果的地方都能正确的获取结果,而不能保证变量赋值操作的顺序与程序代码的顺序一致。而使用volatile可以实现。
volatile最适合是一个线程写,其他线程读的场合,如果有多个线程可以写操作,则仍然需要锁机制来实现!

原创粉丝点击