在java集合中,有些容器是线程安全的(Vector,ConcurrentLinkedQueue等),有些则不是(list等),对线程不安全的容器,可以利用类似
private static List<Task> taskQueue = Collections.synchronizedList(new LinkedList<Task>());的方法得到本身不是线程安全的容器的线程安全的状态。
但是要注意的一点是,无论是哪一种情况,线程安全仅仅指的是如果直接使用它提供的函数,比如:queue.add(obj);
但如果是非原子操作,比如:
这种非原子的操作,就不是线程安全的。
所以对于这种情况,我们还是需要自己同步:
在这种同步的过程中,经常会出现使用了不同的锁导致的错误情况:
下面是一个错误例子:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent(E x){
boolean absent = !list.contains(x);
if(absent)
list.add(x);
return absent;
}
}
上面的例子中,虽然putIfAbsent方法使用了synchronized,但是synchronized使用的锁是ListHelper对象的内置锁,跟Collections.synchronizedList使用的锁是不一样的锁,所以会出现当一个线程进入putIfAbsent方法后,还是会有线程对list列表进行修改,这样在putIfAbsent方法中的非原子操作方法中,会出现线程不同步的情况。
要改正这种情况,就要使用同一锁,由于public List<E> list = Collections.synchronizedList(new ArrayList<E>());使用的锁是list对象(这等下会在下面写一个类来证明),所以在putIfAbsent方法中,我们要使用list对象来当锁:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent(E x){
synchronized(list){
boolean absent = !list.contains(x);
if(absent)
list.add(x);
return absent;
}
}
}
使用同一个锁后,只要是进入了同步代码块,另一个线程想要再修改list就会等待释放锁后才执行,这就能保证线程同步。
下面来证明下public List<E> list = Collections.synchronizedList(new ArrayList<E>());使用的锁是list对象。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Vector1 {
public static void main(String[] args) {
ListHelper lh = new ListHelper();
new Thread(new Thread1(lh)).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lh.list.add(2);
System.out.println("+++++++++++++++++");
}
}
class Thread1 implements Runnable{
ListHelper lh;
public Thread1(ListHelper lh){
this.lh = lh;
}
@Override
public void run() {
lh.putIfAbsent(10);
}
}
class ListHelper{
public List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
public boolean putIfAbsent(Integer x){
synchronized (list) {
System.out.println("===============");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------------------------");
return true;
}
}
}
上面的执行结果是:
===============
-------------------------
+++++++++++++++++
由于子线程先执行了putIfAbsent方法,进入了同步代码块,这时list锁被子线程拿走了,所以即使主线程休眠时间先结束要执行lh.list.add(2);时,由于子线程还没执行完,不能拿到锁,所以这时主线程等待,直道子线程执行完后,才返回锁给主线程,才接着输入+++
如果把要拿锁才能执行的一行代码注释掉://lh.list.add(2);
然后再执行,输入结果如下:
===============
+++++++++++++++++
-------------------------