并发(java编程思想)笔记

package com.facaizhu.concurrency;

//实现 Runnable,只表示具有一个 run 方法,但它并没有线程能力,请看 main 方法
public class LiftOff implements Runnable {

	protected int countDown = 10;
	private static int taskCount = 0;
	//用于标记实例,一旦初始化不希望被改变
	private final int id = taskCount++;
	
	public LiftOff() {}
	public LiftOff(int countDown) {
		this.countDown = countDown;
	}
	
	public String status() {
		return "#" + id + "(" + 
						(countDown > 0 ? countDown : "LiftOff!") + "). ";
	}
	@Override
	public void run() {
		while(countDown-- > 0) {
			System.out.print(status());
			Thread.yield();
		}
	}
	
	public static void main(String[] args) {
		LiftOff lo = new LiftOff();
		//像调用其它方法一样,调用run方法
		lo.run();
	}

}

把 Runnable 附着在一个Thread对象上才会产生线程的效果,

public class BasicThreads {
  public static void main(String[] args) {
    Thread t = new Thread(new LiftOff());
    //为线程的开始做一些准备,此方法会立即返回
    t.start();
    System.out.println("Waiting for LiftOff");
  }
}
/* Output:
Waiting for LiftOff
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!),
*/


二、Java5 最新的 java.util.concurrent 包的使用(这里暂不研究)

Executor/ExecutorService/Callable 等 java.util.concurrent 包的内容现在只需有个了解,下面是一些简单例子:

//: concurrency/CachedThreadPool.java
import java.util.concurrent.*;

public class CachedThreadPool {
  public static void main(String[] args) {
    ExecutorService exec = Executors.newCachedThreadPool();
    for(int i = 0; i < 5; i++)
      exec.execute(new LiftOff());
    exec.shutdown();
  }
} /* Output: (Sample)
#0(9), #0(8), #1(9), #2(9), #3(9), #4(9), #0(7), #1(8), #2(8), #3(8), #4(8), #0(6), #1(7), #2(7), #3(7), #4(7), #0(5), #1(6), #2(6), #3(6), #4(6), #0(4), #1(5), #2(5), #3(5), #4(5), #0(3), #1(4), #2(4), #3(4), #4(4), #0(2), #1(3), #2(3), #3(3), #4(3), #0(1), #1(2), #2(2), #3(2), #4(2), #0(Liftoff!), #1(1), #2(1), #3(1), #4(1), #1(Liftoff!), #2(Liftoff!), #3(Liftoff!), #4(Liftoff!),
*///:~

从任务中产生返回值

//: concurrency/CallableDemo.java
import java.util.concurrent.*;
import java.util.*;
//Callable和Runnable接口类似
class TaskWithResult implements Callable<String> {
  private int id;
  public TaskWithResult(int id) {
    this.id = id;
  }
  public String call() {
    return "result of TaskWithResult " + id;
  }
}

public class CallableDemo {
  public static void main(String[] args) {
    ExecutorService exec = Executors.newCachedThreadPool();
    //Future也是一个接口,能够通过此接口获取任务的执行情况及结果
    ArrayList<Future<String>> results =
      new ArrayList<Future<String>>();
    for(int i = 0; i < 10; i++)
      results.add(exec.submit(new TaskWithResult(i)));
    for(Future<String> fs : results)
      try {
        // get() blocks until completion:
        //也可以用 isDone()来查询任务是否已经完成,然后再用get获取结果
        System.out.println(fs.get());
      } catch(InterruptedException e) {
        System.out.println(e);
        return;
      } catch(ExecutionException e) {
        System.out.println(e);
      } finally {
        exec.shutdown();
      }
  }
} /* Output:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
*///:~

三、 Thead.sleep( ) 方法,使当前线程睡一些时间

注意 InterrupteException 及 TimeUnit.MILLISECOND.sleep( ) 的方式, TimeUnite是一个 enum 类型

线程可以设置优先级 Thread.currentThread().setPriority(Thread.MAX_PRIORITY)  还有 Thread.NORMAL_PRIORITY/Thead.MIN_PRIORITY  但设置级程的优先级效果不大,所以很少用到

Thread.yield( ) 表示当前线程的工作已经做了一些,如果其它线程需要处理器可以拿走,但不要依靠此方法产生可靠的效果

后台线程  Thread.setDaemon(true)


四、在构造一个线程时设置其名称

public class SimpleThread extends Thread {
  private int countDown = 5;
  private static int threadCount = 0;
  public SimpleThread() {
    // Store the thread name: 设置线程的名子,可以通过 getName()得到,如果不设置则系统会自动分配一个可读性不强的名子
    super(Integer.toString(++threadCount));
    start();
  }
  public String toString() {
    return "#" + getName() + "(" + countDown + "), ";
  }
  public void run() {
    while(true) {
      System.out.print(this);
      if(--countDown == 0)
        return;
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new SimpleThread();
  }
} /* Output:
#1(5), #1(4), #1(3), #1(2), #1(1), #2(5), #2(4), #2(3), #2(2), #2(1), #3(5), #3(4), #3(3), #3(2), #3(1), #4(5), #4(4), #4(3), #4(2), #4(1), #5(5), #5(4), #5(3), #5(2), #5(1),
*///:~

五、Thread.join()

在线程 Thread1 的 run 方法中调用 Thread2.join() 则表明一直等待 Thread2 执行结束后Thread1才开始执行.

被join 或 sleep 的Thread t, 可以执行 t.interrupt() 来打断它的 sleep或join行为


、其它线程的异常不能在当前线程进行捕获,这个异常会直接导致程序结束,Java5提供了一种有效的方法解决这种情况,具体示例请参考 《Java编程思想第四版21.2.14节》

七、共享的资源

多个线程共享同一个资源,如果不采取措施就会产生资源冲突的问题。一般情况下共享资源为一个对象,当这个对象有一个成员变量时,那么这个对象就不是线程安全的。(方法都是线程安全的)


有时也存在这样一种情况,一个线程获得了一个对象上的多次锁,如一个线程调用一个加锁的方法1,而方法1内部又调用了加锁的方法2,方法2 又调用了加锁的方法3,这样这个线程对这个对象的锁计数就会3,每退出一个方法锁计数减1,当锁计数为0时表明这个线程不再拥有这个对象的锁.


Java5增加了另外一种加锁机制,不再使用 synchronized 关键字,而是使用显示的Lock. 一般情况下使用 synchronized使代码量更小且更优美,但如果你需要对加锁解锁有更详细的控制就需要使用显式的Lock了,看下面的例子:

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class AttemptLocking {
	private ReentrantLock lock = new ReentrantLock();

	public void untimed() {
                //如果能够获得锁则获得,不然就立刻返回
                boolean captured = lock.tryLock();
		try {
			System.out.println("tryLock(): " + captured);
		} finally {
			if (captured)
				lock.unlock();
		}
	}

	public void timed() {
		boolean captured = false;
		try {
                     //连续等待2秒,如果能够获得锁则获得,不然2秒后返回
                     captured = lock.tryLock(2, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		try {
			System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured);
		} finally {
			if (captured)
				lock.unlock();
		}
	}

	public static void main(String[] args) {
		final AttemptLocking al = new AttemptLocking();
		al.untimed(); // True -- lock is available
		al.timed(); // True -- lock is available
		// Now create a separate task to grab the lock:
		new Thread() {
			{
				setDaemon(true);
			}

			public void run() {
				al.lock.lock();
				System.out.println("acquired");
			}
		}.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} // Give the 2nd task a chance
		al.untimed(); // False -- lock grabbed by task
		al.timed(); // False -- lock grabbed by task
	}
} /*
 * Output: tryLock(): true tryLock(2, TimeUnit.SECONDS): true acquired
 * tryLock(): false tryLock(2, TimeUnit.SECONDS): false
 */// :~

另外关于原子性及Java SE5引入的 AtomicInteger/AtomicLong/AtomicReference 等原子类在性能调优时会用到.

上面交到的  synchronized 关键字是用于一个方法的,这个关键字也可以用到一段代码且用于一段代码而不是整个方法的效率要高很多,看下面一个例子:

package com.facaizhu.concurrency;

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.*;

class Pair { // Not thread-safe
	private int x, y;

	public Pair(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public Pair() {
		this(0, 0);
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}

	public void incrementX() {
		x++;
	}

	public void incrementY() {
		y++;
	}

	public String toString() {
		return "x: " + x + ", y: " + y;
	}

	public class PairValuesNotEqualException extends RuntimeException {
		public PairValuesNotEqualException() {
			super("Pair values not equal: " + Pair.this);
		}
	}

	// Arbitrary invariant -- both variables must be equal:
	public void checkState() {
		if (x != y)
			throw new PairValuesNotEqualException();
	}
}

// Protect a Pair inside a thread-safe class:
abstract class PairManager {
	AtomicInteger checkCounter = new AtomicInteger(0);
	protected Pair p = new Pair();
	private List<Pair> storage = Collections
			.synchronizedList(new ArrayList<Pair>());

	//这个方法为什么也要同步呢?
	//因为在调用该方法的同时,p的值可能还在变化,这样就会导致p的x,y 值的不等
	//同时我们也应该知道此方法的锁是当前对象,那么其它的同步方法的锁也应该是当前对象
	public synchronized Pair getPair() {
		// Make a copy to keep the original safe:
		return new Pair(p.getX(), p.getY());
	}

	// Assume this is a time consuming operation
	protected void store(Pair p) {
		storage.add(p);
		try {
			TimeUnit.MILLISECONDS.sleep(50);
		} catch (InterruptedException ignore) {
		}
	}

	public abstract void increment();
}

// Synchronize the entire method:
class PairManager1 extends PairManager {
	//同步整个方法,锁为当前对象
	public synchronized void increment() {
		p.incrementX();
		p.incrementY();
		store(getPair());
	}
}

// Use a critical section:
class PairManager2 extends PairManager {
	public void increment() {
		Pair temp;
		//同步代码块,锁为 this 即当前对象,这里只能为this不能为其它对象,不然的话就会和
		//getPair 的锁不配套
		synchronized (this) {
			p.incrementX();
			p.incrementY();
			temp = getPair();
		}
		store(temp);
	}
}

class PairManipulator implements Runnable {
	private PairManager pm;

	public PairManipulator(PairManager pm) {
		this.pm = pm;
	}

	public void run() {
		while (true)
			pm.increment();
	}

	public String toString() {
		return "Pair: " + pm.getPair() + " checkCounter = "
				+ pm.checkCounter.get();
	}
}

class PairChecker implements Runnable {
	private PairManager pm;

	public PairChecker(PairManager pm) {
		this.pm = pm;
	}

	public void run() {
		while (true) {
			pm.checkCounter.incrementAndGet();
			pm.getPair().checkState();
		}
	}
}

public class CriticalSection {
	// Test the two different approaches:
	static void testApproaches(PairManager pman1, PairManager pman2) {
		ExecutorService exec = Executors.newCachedThreadPool();
		PairManipulator pm1 = new PairManipulator(pman1), pm2 = new PairManipulator(
				pman2);
		PairChecker pcheck1 = new PairChecker(pman1), pcheck2 = new PairChecker(
				pman2);
		exec.execute(pm1);
		exec.execute(pm2);
		exec.execute(pcheck1);
		exec.execute(pcheck2);
		try {
			TimeUnit.MILLISECONDS.sleep(500);
		} catch (InterruptedException e) {
			System.out.println("Sleep interrupted");
		}
		System.out.println("pm1: " + pm1 + "\npm2: " + pm2);
		System.exit(0);
	}

	public static void main(String[] args) {
		PairManager pman1 = new PairManager1(), pman2 = new PairManager2();
		testApproaches(pman1, pman2);
	}
} /*
 * Output: (Sample) pm1: Pair: x: 15, y: 15 checkCounter = 272565 pm2: Pair: x:
 * 16, y: 16 checkCounter = 3956974
 */// :~



当线程被阻塞时(如调用了wait/sleep等方法时),可以使用 Thread.interrupt()方法打断阻塞的状态,当调用此方法后 Thread的 interrupted 标记被设置为 true. 此时如果你调用 interrupted()方法来测试中断状态(此方法会清空中断状态,也可以使用isInterrupted()来测试中断状态,这时中断状态不会被清空).

当调用 interrupte()方法时会抛出 InterruptedException,这时中断状态被清空.


线程被 sleep/wait 阻塞时可以被 interrupt,但如果线程被 IO或synchronized 阻塞则不可以被 interrupt, 如下面的例子所示:

import java.util.concurrent.*;
import java.io.*;

//可以通过Thread.interrupt()方法中断阻塞
class SleepBlocked implements Runnable {
	public void run() {
		try {
			TimeUnit.SECONDS.sleep(100);
		} catch (InterruptedException e) {
			System.out.println("InterruptedException");
		}
		System.out.println("Exiting SleepBlocked.run()");
	}
}
//通过Thread.interrupt()方法不能中断阻塞
class IOBlocked implements Runnable {
	private InputStream in;

	public IOBlocked(InputStream is) {
		in = is;
	}

	public void run() {
		try {
			System.out.println("Waiting for read():");
			in.read();
		} catch (IOException e) {
			if (Thread.currentThread().isInterrupted()) {
				System.out.println("Interrupted from blocked I/O");
			} else {
				throw new RuntimeException(e);
			}
		}
		System.out.println("Exiting IOBlocked.run()");
	}
}
//通过Thread.interrupt()方法不能中断阻塞
class SynchronizedBlocked implements Runnable {
	public synchronized void f() {
		while (true)
			// Never releases lock
			Thread.yield();
	}

	public SynchronizedBlocked() {
		new Thread() {
			public void run() {
				f(); // Lock acquired by this thread
			}
		}.start();
	}

	public void run() {
		System.out.println("Trying to call f()");
		f();
		System.out.println("Exiting SynchronizedBlocked.run()");
	}
}

public class Interrupting {
	private static ExecutorService exec = Executors.newCachedThreadPool();

	static void test(Runnable r) throws InterruptedException {
		//关于Future/Executor/ExecutorService/FutureTask/shutdown()/shutdownNow()及 java.util.concurrent 包里面的类等找到工作后研究
                Future<?> f = exec.submit(r);
		TimeUnit.MILLISECONDS.sleep(100);
		System.out.println("Interrupting " + r.getClass().getName());
		f.cancel(true); // Interrupts if running
		System.out.println("Interrupt sent to " + r.getClass().getName());
	}

	public static void main(String[] args) throws Exception {
		//可以中断
		test(new SleepBlocked());
		//不可以中断IO型的阻塞
		test(new IOBlocked(System.in));
		//不可以中断 synchronized 型的阻塞
		test(new SynchronizedBlocked());
		TimeUnit.SECONDS.sleep(3);
		System.out.println("Aborting with System.exit(0)");
		System.exit(0); // ... since last 2 interrupts failed
	}
}

那么如果想中断被IO阻塞的线程的方法为: 关闭低层的IO,这样就会抛出IOException,如 System.in.close( ) 则IOBlocked 类的run方法就会抛出异常,这样线程的IO阻塞就被打断了。

阻塞的IO线程可以通过关闭低层的IO来中断,那么 阻塞的synchronized线程如何被中断呢? 可以用 Lock,如下代码所示:

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

class BlockedMutex {
  private Lock lock = new ReentrantLock();
  public BlockedMutex() {
    // Acquire it right away, to demonstrate interruption
    // of a task blocked on a ReentrantLock:
    lock.lock();
  }
  public void f() {
    try {
      // This will never be available to a second task
      lock.lockInterruptibly(); // Special call
      System.out.println("lock acquired in f()");
    } catch(InterruptedException e) {
      System.out.println("Interrupted from lock acquisition in f()");
    }
  }
}

class Blocked2 implements Runnable {
  BlockedMutex blocked = new BlockedMutex();
  public void run() {
    System.out.println("Waiting for f() in BlockedMutex");
    //blocked对象执行f()时要首先获取锁,在这里阻塞,等待获取锁
    blocked.f();
    System.out.println("Broken out of blocked call");
  }
}

public class Interrupting2 {
  public static void main(String[] args) throws Exception {
    Thread t = new Thread(new Blocked2());
    t.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Issuing t.interrupt()");
    t.interrupt();
  }
}

在一个线程结束时它应该清空它拥有的资源,但当 interrupt 一个线程时,它正在执行什么操作是不一定的,所以这时就需要设计良好的 try-finally语句,如下段代码所示:

import java.util.concurrent.*;

class NeedsCleanup {
	private final int id;

	public NeedsCleanup(int ident) {
		id = ident;
		System.out.println("NeedsCleanup " + id);
	}

	public void cleanup() {
		System.out.println("Cleaning up " + id);
	}
}

class Blocked3 implements Runnable {
	private volatile double d = 0.0;

	public void run() {
		try {
			while (!Thread.interrupted()) {
				// point1
				NeedsCleanup n1 = new NeedsCleanup(1);
				// Start try-finally immediately after definition
				// of n1, to guarantee proper cleanup of n1:
				try {
					System.out.println("Sleeping");
					TimeUnit.SECONDS.sleep(1);
					// point2
					NeedsCleanup n2 = new NeedsCleanup(2);
					// Guarantee proper cleanup of n2:
					try {
						System.out.println("Calculating");
						// A time-consuming, non-blocking operation:
						for (int i = 1; i < 2500000; i++)
							d = d + (Math.PI + Math.E) / d;
						System.out.println("Finished time-consuming operation");
					} finally {
						n2.cleanup();
					}
				} finally {
					n1.cleanup();
				}
			}
			System.out.println("Exiting via while() test");
		} catch (InterruptedException e) {
			System.out.println("Exiting via InterruptedException");
		}
	}
}

public class InterruptingIdiom {
	public static void main(String[] args) throws Exception {
		if (args.length != 1) {
			System.out.println("usage: java InterruptingIdiom delay-in-mS");
			System.exit(1);
		}
		Thread t = new Thread(new Blocked3());
		t.start();
		TimeUnit.MILLISECONDS.sleep(new Integer(args[0]));
		t.interrupt();
	}
}

八、线程间的协作

我们上面讲的主要是如何让线程互斥的访问一个共享的资源,现在要研究如何让线程间进行协作,也就说二个线程有个先后,必须第一个线程完成后,第二个线程才能开始执行(像生产者与消费者)。

要完成上面所说的就要用到 Object.wait()/notify()/notifyAll()了.

wait/notify/notifyAll 的调用都必须在获得这个对象的 monitor 时.

当wait 被调用时,当前线程被加入 wait set,这时这个线程不再被系统调度,直到有其它线程调用特定对象的notify或notifyAll方法时,此线程从wait set 中移除,开始被系统调度,但它必须再次获得特定对象的monitor才能继续。当获得对象的monitor后,wait方法会返回,这时此线程会恢复到调用wait方法前的状态.

注意: 当调用 wait()时会释放对象锁,而 sleep/yield 等方法不会释放锁.

wait/notify/notifyAll 及 synchronized 都是相对于一个对象来说的,都需要这个对象的锁.

下面是一个示例用法:

import java.util.concurrent.*;
/*
 * 汽车可以被打蜡,然可以被擦掉,打蜡前汽车必须是光的,擦蜡时汽车必须已经打完蜡
 * */
class Car {
	// false 表示没有打蜡,true表示已经打完蜡
	private boolean waxOn = false;

	public synchronized void waxed() {
		waxOn = true; // Ready to buff(准备擦掉)
		notifyAll();
	}

	public synchronized void buffed() {
		waxOn = false; // Ready for another coat of wax
		notifyAll();
	}

	public synchronized void waitForWaxing() throws InterruptedException {
		/* 为什么用while呢? 下面有详述 */
		while (waxOn == false)
			wait(); //wait 在synchronized 调用才是对的,因为wait会释放锁
	}

	public synchronized void waitForBuffing() throws InterruptedException {
		/* 为什么用while呢? 下面有详述 */
		while (waxOn == true)
			wait();
	}
}

class WaxOn implements Runnable {
	private Car car;

	public WaxOn(Car c) {
		car = c;
	}

	public void run() {
		try {
			while (!Thread.interrupted()) {
				System.out.println("Wax On! ");
				TimeUnit.MILLISECONDS.sleep(200);//表示打蜡的过程
				car.waxed();
				car.waitForBuffing();
			}
		} catch (InterruptedException e) {
			System.out.println("Exiting via interrupt");
		}
		System.out.println("Ending Wax On task");
	}
}

class WaxOff implements Runnable {
	private Car car;

	public WaxOff(Car c) {
		car = c;
	}

	public void run() {
		try {
			while (!Thread.interrupted()) {
				car.waitForWaxing();
				System.out.println("Wax Off! ");
				TimeUnit.MILLISECONDS.sleep(200);
				car.buffed();
			}
		} catch (InterruptedException e) {
			System.out.println("Exiting via interrupt");
		}
		System.out.println("Ending Wax Off task");
	}
}

public class WaxOMatic {
	public static void main(String[] args) throws Exception {
		Car car = new Car();
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(new WaxOff(car));
		exec.execute(new WaxOn(car));
		TimeUnit.SECONDS.sleep(5); // Run for a while...
		exec.shutdownNow(); // Interrupt all tasks
	}
}
上面的代码中的waitForWaxing()和waitForBuffing() 方法中用的是 while(...) wait, 这种用法是一个种常见的方式,原因如下:

假设: 涂-线程1,涂-线程2,涂-线程3  都会涂同一个 Car 对象,同时有一个 擦-线程 来擦Car对象。 当擦-线程 完成擦车动作时,它会调用 Car.notifyAll 来唤醒所有等待于此对象上的线程,这时 涂-线程1,涂-线程2,涂-线程3 三个线程都被加入系统调度中,来争抢对象锁,假设 涂-线程2 抢到锁,当把车涂好后 涂-线程2 也会调用 Car.notifyAll, 这时 擦-线程也加入了调试,如果此时 涂-线程1 获得对象锁,它从 wait 方法返回,并开始了后续的涂车操作,因为车已经被 涂-线程2 涂好了,所以 涂-线程1 的操作是非法的。

如果加入 while(条件)就可以使 获得对象锁 的 涂-线程1 再次调用 wait 加入 wait set.

九、java.util.concurrent 包中的部分类


十、本章剩余内容以后再学


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值