Java并发编程实践札记(一)

Java并发编程实践笔记(一)
先如下代码:
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;
    }
}



代码说明1)变量list用Collections.synchronizedList的作用是把本身不是线程安全的容器ArrayList变成线程安全的

                2)ListHepler的方法都用了synchronized来进行加锁,用来同步。

                3)注意list变量的访问权限是public!


现在提供两个线程类来提供模拟这个同步:

线程A和线程B的代码如下(两个代码除了类名字完全一样,所以在此仅贴出来A的)

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 --- 0
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执行的时候,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();
					}
				}
			}
			
		}

	}

再次运行获得结果:

A --- 0
A --- 1
A --- 2
A --- 3
A --- 4
A --- 5
A --- 6
A --- 7
A --- 8
C------0
C------1
C------2
C------3
C------4
C------5
C------6
C------7
C------8
B --- 0
B --- 1
B --- 2
B --- 3
B --- 4
B --- 5
B --- 6
B --- 7
B --- 8


当然要把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;
		}
		
	}
}


这样的话上面的A和B的run方法就可以去掉synchronized (lh) 而只用synchronized (lh.list) 使得程序安全并发的运行了

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();
					}
				}
			}
			
		}

	//}
}