如何"锁"以及jdk1.7中成员内部类访问成员变量时为什么对成员变量加上final关键字?

基于Lock实现模拟List集合

Posted by MasterJen on November 22, 2018

Hey Lock

All things come to those who wait.–苍天不负有心人.

最近做项目时,有个需求就是实现线程安全的List集合,那么什么是线程安全的呢? 无非就是 保证其在增删改的操作中保持原子性罢了.

举个案例实现并发执行List的 add方法

public class TestList {
    //    实现多线程并发执行 添加操作
    public static void main(String[] args) {
//        创建  Mylist 实例对象  并声明为 Final  jdk1.7中必须声明  jdk1.8之后,当成员内部类 调用成员属性时,默认也是 final
        final MyList ml = new MyList();

        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(new Runnable() {
            @Override
            public void run() {
                ml.add("D");
            }
        });

        es.submit(new Runnable() {
            @Override
            public void run() {
                ml.add("E");
            }
        });

        for (int i = 0; i < ml.size(); i++) {
            System.out.println(ml.get(i));
        }

    }

}

class MyList {
    //    数组容器
    private Object elements[] = {
            "A", "B", "C", "", ""
    };
    //    当前数组内的有效 长度
    int size;
    //    Lock 锁
    private Lock locker;

    MyList() {
        size = 3;
        locker = new ReentrantLock();
    }

    public void add(Object obj) {
        locker.lock();
        try {
            elements[size] = obj;
            size++;
        } finally {
            locker.unlock();
        }
    }

    public Object get(int i) {
        return elements[i];
    }

    public int size() {
        return size;
    }
}

本案例中的 成员变量 ml 为什么在 jdk 1.7中要加上 final 呢? 再举个例子

/**
 *  为什么 要加上 final 呢?jdk 1.7 之前 当成员内部类需要访问成员变量时 需要加上final     目的是 使 内部类对象的生命周期大于 目标方法的局部变量的生命周期.
 */
public class TestFinal {
    public static void main(String[] args) throws Exception {
        Outer outer = new Outer();
//     先执行  outMethod 方法
        outer.outerMethod();
//     再执行 p.print()  如果 jdk 1.7不加 final 就会出现 打印时 没有 变量 a ,所以出错.声明周期不一样了.
        outer.p.print();

    }
}

class Outer {
    PrintTest p;
    /*
/    jdk 1.7 之前 当成员内部类需要访问成员变量时 需要加上final     目的是 使 内部类对象的生命周期大于 目标方法的局部变量的生命周期.
 *
 *  但 是 jdk 1.8 之后就不用加 final 了
 *
 *   final修饰符可省略
    内部类在访问 它所在的方法中的变量时 该变量必须是常量(被final修饰的)
    jdk1.8之后 省略了final修饰符 但是本质上该变量还是final的
 */
    public void outerMethod() throws Exception {
        final int a = 10;
        // 成员内部类
        class InnerTest implements PrintTest{
            @Override
            public void print() throws Exception {
                System.out.println("a is " + a);
            }
        }
        p = new InnerTest();
    }
// 接口
    interface PrintTest {
        void print() throws Exception;
    }
}

由此大家也许知道了为什么要加上 final了吧,再拉回到我们的MyList案例中,大家都知道ArrayList中的add方法实际上是没有加锁的,所以当并发访问时 也许会出现错误,当一个线程执行add方法时,另一个线程也抢到了临界资源,那么也会执行add操作,所以会造成size错误,所以在本案例中加了锁,目的是演示临界资源的问题,虽然解决了锁的问题,但是这还不是最优方法,因为对性能会有一定的影响,下篇文章中会详细介绍如何得到高效率的线程安全集合List Set Map,并且会对其源码进行解析.