您的位置:首页 > 职场人生

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

2016-04-05 22:52 218 查看
前言: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里面的代码可以看出,
Thread
target
属性是指向h对象的。

灵光一闪

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

所以,假如两个
Thread
实例使用同一个
haha
对象,这个锁就会有用了!

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

总结

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

有一个问题我一直都不是很明白,为什么说
Arraylist
是非线程安全的呢?如何从内存角度分析线程安全问题呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: