使用正确的锁对象来保证线程安全

在java集合中,有些容器是线程安全的(Vector,ConcurrentLinkedQueue等),有些则不是(list等),对线程不安全的容器,可以利用类似

private static List<Task> taskQueue = Collections.synchronizedList(new LinkedList<Task>());的方法得到本身不是线程安全的容器的线程安全的状态。

但是要注意的一点是,无论是哪一种情况,线程安全仅仅指的是如果直接使用它提供的函数,比如:queue.add(obj); 或者 queue.poll(obj);,这样我们自己不需要做任何同步。 但如果是非原子操作,比如: if(!queue.isEmpty()) { queue.poll(obj); }

这种非原子的操作,就不是线程安全的。 所以对于这种情况,我们还是需要自己同步: synchronized(queue) { if(!queue.isEmpty()) { queue.poll(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);

然后再执行,输入结果如下:

=============== +++++++++++++++++ -------------------------

经验分享 程序员 微信小程序 职场和发展