public class ListHelper<Integer> { public List<Integer> list = Collections .synchronizedList(new ArrayList<Integer>()); // private List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean put(Integer i) { list.add(i); System.out.println(Thread.currentThread().getName() + " --- " + i); return true; } }
2)ListHepler的方法都用了synchronized来进行加锁,用来同步。
3)注意list变量的访问权限是public!
public class A extends Thread { private ListHelper<Integer> lh; public A(ListHelper<Integer> lh) { super("A"); this.lh = lh; } public void run() { synchronized (lh) { for (int i = 0; i < 9; i++) { lh.put(i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
客户端的代码:
public class Client { public static void main(String args[]) throws InterruptedException{ ListHelper<Integer> lh = new ListHelper<Integer>(); A a = new A(lh); B b = new B(lh); a.start(); b.start(); } }运行结果如下:
从运行结果来看,一切都是那么顺利,当线程A执行的时候,B阻塞;然后A执行完毕释放锁,B获取锁并运行。看起来很安全的样子。
但是下面在提供一个thread C:
public class C extends Thread { private ListHelper<Integer> lh; public C(ListHelper<Integer> lh){ super("C"); this.lh = lh; } public void run() { synchronized (lh.list) {//添加了list锁 for (int i = 0; i < 9; i++) { System.out.println(Thread.currentThread().getName() + "------" + i); lh.list.add(i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
public static void main(String args[]) throws InterruptedException{ ListHelper<Integer> lh = new ListHelper<Integer>(); A a = new A(lh); B b = new B(lh); C c = new C(lh); a.start(); b.start(); c.start(); }
A --- 0
C------0
C------1
C------2
C------3
C------4
C------5
C------6
C------7
C------8
A --- 1
A --- 2
A --- 3
A --- 4
A --- 5
A --- 6
A --- 7
A --- 8
B --- 0
B --- 1
B --- 2
B --- 3
B --- 4
B --- 5
B --- 6
B --- 7
B --- 8
发现线程A在执行的时候,由于A获取了ListHelper的锁,导致B线程的阻塞,当线程A在执行完的时候释放锁,然后B获取锁得到执行。但是有如下问题出现了
1)变量list不是被Collectoion.synchronizedList加过锁了么?
2)在A获取锁并执行的时候C怎么可以执行呢?
3)并且A只执行了一次put方法之后等着C执行完毕后才得到运行呢?
解答:
A获取的是ListHelper对象锁,而Collection.synchronizedList为list添加的锁是另外一个锁,也就是说两个锁不是一回事儿。所以问题3就可以迎刃而解了:
1)A执行一次put方法,释放了list锁。此时A仍然拥有ListHelper锁,B在等待获取ListHelper锁,所以B仍然阻塞
2)C获取到了list锁执行完for循环并释放list锁,A得到list锁并运行完for循环,释放ListHelper锁。
3)B得到ListHelper锁,并运行完毕,程序退出
所以A线程获取ListHelper对象锁执行的并执行的时候是没法阻塞C线程的执行的,除非A线程也获取了list上的锁!!!
所以可以把A和B的run方法代码改成:
public void run() { synchronized (lh) { synchronized (lh.list) {//添加了list锁 for (int i = 0; i < 9; i++) { lh.put(i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
当然要把ListHelper设置成线程安全的类,最简单的是不发布list变量,可以把list的改成private同时不提供getList()方法,不过这样的话Collection.synchronizedList就失去了它的作用。怎么在public访问权限不变的情况下,只利用Collection.synchronizedList提供的锁来控制线程同步的呢?上面的代码A和B明显用到了两个锁,一个是ListHelper锁,另一个是Collection.synchronizedList提供的锁,其实完全可以利用后者而不用ListHelper锁来完成上述的更能。
修改ListHelper代码如下:
public class ListHelper<Integer> { public List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>()); // private List<E> list = Collections.synchronizedList(new ArrayList<E>()); public boolean put(Integer i) { synchronized(list){//在list上加锁 list.add(i); System.out.println(Thread.currentThread().getName() + " --- " + i); return true; } } }
public void run() { //synchronized (lh) { synchronized (lh.list) { for (int i = 0; i < 9; i++) { lh.putIfAbsent(i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } //} }