一道JAVA面试,线程安全和静态内部类

前言:4月1号去一家互联网公司面试,做了一份笔试。考察的内容也非常基础,但是里面却充满着各种各样的扩展。但是这份题我做得并不好,平时用框架什么的用多了,反而基础显得非常不扎实。凭着记忆写起最后一套题目。记一下,扎实一下自己的基础。

代码

/**
 * declaration: 
 *
 * author wenkangqiang
 * date   2016年4月1日
 */
public class FankeTest {

    static class haha implements Runnable{

        public haha(List<String> list) {
            // TODO Auto-generated constructor stub
            this.list = (ArrayList<String>)list;
        }

        @Override
        public void run() {
            while(log != null){
                log = list.remove(0); //报下标错误
                System.out.println(log);
            }

        }

        private List<String> list;
        private String log = "";
    }

    public static void main(String[] args) {
        String[] strs = new String[]{"aa", "bb", "cc"};
        ArrayList<String> list = new ArrayList<String>(Arrays.asList(strs));
        Thread t1 = new Thread(new haha(list));
        Thread t2 = new Thread(new haha(list));
        t1.start();
        t2.start();
    }

}

题目是要求我们寻找出里面的错误或不妥的地方。
1、线程安全:这是比较明显的,因为ArrayList是非线程安全的,所以在多进程进行的时候,而这个有两个线程共享了静态代码。所以线程安全问题是非常明显的。

2、数组下标溢出异常:list.remove(0); 在发现数组中已经没有数据的时候,会直接报下标溢出异常IndexOutOfBoundsException() ,并不会返回null值给log

尝试修改:

增加判空判断,为循环写一个出口。为了观看方便,我为两个线程加了名字,并且在run方法中输出,为了避免处理速度过快而看不到效果,我可以在线程中添加sleep 方法。

while(log != null){
    if (list.size() > 0) {
            log = list.remove(0);
            try {
                //放慢线程
                Thread.currentThread().sleep(1);
                System.out.println(
                    Thread.currentThread().getName() + log);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        } else {
            break;
        }
}

在还没有添加同步机制时,上面的代码会容易产生数据错误。

OUTPUT:
t2aa
t1aa
t1cc
t2cc

为了去除脏数据,就应该为代码添加同步机制。我先尝试第一种,同步方法。

@Override
public synchronized void run() 

我把run方法直接写成同步方法。但,仍然会出现脏数据!

OUTPUT:
t1aa
t2aa
t2cc
t1cc

这就证明,synchronized 锁不住这个方法。

在解释这个问题前,必需先清楚一些同步方法的原理
1、如果是一个非静态的同步函数的锁,锁对象是this对象。
2、如果是静态的同步函数的锁,锁对象是该类的字节码对象。

Thread t1 = new Thread(new haha(list));

代码中是通过这一种方式去实现线程创建的。也就是说,有两个不同的。
Thread对象共享着一个Runnable 接口下的静态实例?这样说对吗?做一个测试了解一下。
我们在静态内部类中添加了一个int i 结果让人很意外,原本认为两个new haha(list) 会指向同一片内存,实际上我错了。每一个线程都享用这一个haha实例,只有把i设为static才能出现我们想要的共享内存。
这里写图片描述

我尝试使用一种新的方法去创建线程,如图:
这里写图片描述
我先创建出一个实例h,然后再传过去给两个线程创建实例。这时候两个Thread实例是共享一段代码的。根据Thread里面的代码可以看出,Threadtarget属性是指向h对象的。

灵光一闪

现在终于知道里面是什么情况了。静态内部类是可以拥有多个实例的。因为我们两个线程分别拥有两个不同的haha对象。同步方法中的synchronized的锁分别是两个haha对象的。所以这时候,两个线程根本没有被锁住。
所以,假如两个Thread实例使用同一个haha对象,这个锁就会有用了!

同步块比较容易理解,主要就是关注锁对象。

总结

首先要承认,这是一道不普通的题。我是一边写博客,一边解决问题的。此前看来对静态内部类有点误解了,认为它只能创建一个实例。对比其与非静态内部类,就可以很容易理解它的异同了。线程锁对象及同步原理也是解决这个题目最关键的地方。

有一个问题我一直都不是很明白,为什么说Arraylist是非线程安全的呢?如何从内存角度分析线程安全问题呢?

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值