Java线程(1)

1、创建线程的三种方法

  ①、创建Thread类的子类对象,实现run()方法(run()是函数式接口Runnable中的抽象方法,Thread类实现了Runnable接口),调用Thread对象的start()来启动线程,线程会执行run()方法:

public class Test extends Thread 
{
	Test()
	{
		super("Thread name");
	}
	public void run()//run为Thread中的线程方法
	{
		String strThreadName = getName();
		System.out.println(strThreadName + "thread execute");
	}
	public static void fun1() throws Exception
	{
		//定义一个Thread对象,只启动一个线程:一个Thread对象代表一个线程,
		Test t = new Test();
		t.start(); //调用start()启动线程
		t.start(); //error!再次调用start()会抛出异常,一个Thread对象不能启动两个线程
		
		t.join();
	}
	public static void fun2()
	{
		//定义两个Thread对象,启动两个线程,因为是两个Thread对象,所以线程函数中访问的是各自的实例变量,各线程不能共享自己的实例变量
		new Test().start();
		new Test().start();
	}
	public static void main(String[] args) throws Exception
	{	
		fun1();
		fun2();
	}
}

  

public class Test
{
	public static void main(String[] args) throws Exception
	{	
		Thread t = new Thread() {
			@Override
			public void run() { System.out.println("thread run"); }
		};
		
		t.start();
		t.join();
	}
}

  ②、创建实现Runnable接口的类的对象,将该对象赋给Thread,调用Thread对象的start()来启动线程,线程会执行run()方法,如下所示。这种方法使多个线程可以共享同一个线程执行类对象(Runnable子类对象),适合多线程处理同一资源的情况。

    示例1:

class ThreadRunner implements Runnable
{
	public ThreadRunner(String str)
	{
		_str = str;
	}
	private String _str;
	public void run() //run为Runnable中的线程方法
	{
		System.out.println(_str);
	}
}

public class Test 
{
	
	public static void main(String[] args)
	{
		ThreadRunner st = new ThreadRunner(new String("ThreadRunner Test"));
		
		//定义两个Thread对象,启动两个线程,因为各线程使用的是一个Runnable对象所以可以共享该对象的实例变量
		new Thread(st).start();
		new Thread(st , "线程名称").start();
	}
}

    示例2:

public class Test 
{
	
	public static void main(String[] args)
	{
		new Thread(()->{System.out.println("Thread execute");}).start();
		new Thread(()->{System.out.println("hello world");},"线程名称").start();
	}
}

  可以在线程里直接引用局部对象和成员对象:

public class Foo {
    public void func(){
        int n = 100;
        new Thread(()->{
            System.out.println(n);
            System.out.println(m_n);
        }).start();
    }
    public int m_n = 99;
}

  ③、实现Callable接口,使用FutureTask对象来包装该Callable对象,以该FutureTask对象作为Thread类的target来创建线程,这种方式是以Callable接口的call方法来作为线程的执行体,优点是call方法可以有返回值,还可以声明抛出异常。

  类似Runnable,Callable只包含一个抽象方法call,所以它也是一个函数式接口,可以使用Lambda表达式来创建Callable对象。Callable接口的泛型类型应该与call方法返回值类型相同。

  FutureTask对象之所以可以作为Thread类的target是因为FutureTask类实现了Runnable接口,在Runnable的run()中会调用Callable的call()方法。

  FutureTask还实现了Future接口,Future代表线程结束后的返回值。Future接口中包含了一些控制它关联的Callable任务的方法,如get(获得Callable任务里call方法的返回值)、cancel(试图取消关联的Callable任务)、isCancelled(关联的Callable任务在正常完成前是否被取消)、isDone(关联的Callble任务是否已完成)。

import java.util.concurrent.*;
public class Test
{
	public static void main(String[] args)
	{
		// 使用Lambda表达式创建Callable<Integer>对象
		// 使用FutureTask来包装Callable对象

        /*
        Callable<Integer> call = (Callable<Integer>)() -> {
			int i = 0;
			for ( ; i < 100 ; i++ )
			{
				System.out.println(Thread.currentThread().getName()
					+ " 的循环变量i的值:" + i);
			}
			// call()方法可以有返回值
			return i;
		};
		FutureTask<Integer> task = new FutureTask<Integer>(call);
        */
		FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
			int i = 0;
			for ( ; i < 100 ; i++ )
			{
				System.out.println(Thread.currentThread().getName()
					+ " 的循环变量i的值:" + i);
			}
			// call()方法可以有返回值
			return i;
		});
		
		//以FutureTask对象作为Thread的target,实质还是以Callable对象来创建、并启动线程
		new Thread(task , "有返回值的线程").start();
		
		try
		{
			// get方法可以获取线程函数返回值,该方法可能会抛出异常,并且为阻塞等待线程结束
			System.out.println("子线程的返回值:" + task.get());
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
}

2、线程状态和Thread中的方法

  与C++不同,Java中主线程结束后其创建的子线程仍然能运行,但如果子线程是守护线程的话那么主线程结束后子线程也会结束。JVM启动后不只有一个主线程,还会有垃圾收集、内存管理等线程。

  线程的状态:

   阻塞(Blocked):调用了sleep()、wait()等阻塞方法、调用suspend()。

   就绪(Runnable):调用了Thread线程对象的start()方法,等待调度器执行该线程;sleep()时间到;阻塞方法返回;调用resume();调用yield()。

   运行(Running):被调度器调度,得到CPU时间。

   死亡:run()/call()返回、调用stop()、Exception或error。

Thread中的方法:

  静态方法currentThread():获得当前Thread线程对象。
  成员方法getName()/setName():获得/设置线程名,默认主线程名为main,子线程名为Thread-n。

   成员方法join()/join(long millis):等待线程结束。

  成员方法isAlive():线程已死亡的话返回false,否则返回true。
  成员方法setDaemon():设置为后台线程(守护线程),应该在start()之前调用该方法,当前台线程都死亡后后台线程自动死亡。默认从Daemon线程产生的线程也是Daemon线程。
  静态方法sleep():睡眠执行毫秒或微妙,该方法声明抛出异常,所以调用它时要么捕获该异常,要么显示声明抛出该异常。

  成员方法interrupt():一个进入Blocked状态的线程,可以由另一个线程调用该线程的interrupt()让它离开Blocked状态,而且此时线程会抛出InterruptedException异常,该异常是受检异常,必须对其捕获处理。比如一个进入sleep()的线程,其它线程调用该线程的interrupt()可以唤醒它:

public class Test
{
	public static void main(String[] args)
	{	
		Thread thread = new Thread() 
		{
			@Override
			public void run() 
			{
				try {
					Thread.sleep(99999);
				}catch(InterruptedException ex){
					System.out.println("I woke up");
					throw new RuntimeException(ex);
				}
			}
		};
		
		thread.start();
		thread.interrupt();
	}
}


  静态方法yield():让运行状态的线程转入就绪状态,使当前线程暂停一下,与当前线程优先级相同或更高的其它就绪线程会获得执行机会。
  setPriority()/getPriority():设置/获得线程的优先级,可以有十档优先级,默认是5(Thread.NORM_PRIORITY),推荐使用Thread类的静态常量MAX_PRIORITY(10)、MIN_PRIORITY(1)。线程默认与创建其的父线程优先级相同,可以使用优先级高的线程将会得到更多的执行机会。

  成员方法suspend():挂起线程,不推荐使用。
  成员方法resume():恢复挂起的线程,不推荐使用。
  成员方法stop():结束线程,不推荐使用。

3、线程同步

  ①、同步代码块——使用synchronized(obj){ ... },同步监视器为一个obj对象,线程进入同步代码块之前先获得对于同步监视器的锁定,多个线程使用同一个同步监视器,同步代码块执行结束或代码块中break、return终止了代码块的执行或出现Exception或error的时候当前线程释放释放同步监视器。需要注意的一点是如果线程执行当中调用了同步监视器obj对象的wait()方法,那么也会释放同步监视器。Object的wait()是阻塞方法,会导致当前线程暂停,另一线程可以调用对象的notify()或notifyAll()来唤醒暂停的线程。

  如下所示的示例,程序会在5秒钟后打印"main thread",然后才会打印"sub thread":

class Foo
{
	Integer m_lock = 0;
	public void fun()
	{ 	
		Thread t = new Thread() {
			@Override
			public void run()
			{
				try {
					Thread.sleep(1000);
				}catch(Exception ex) {
					ex.printStackTrace();
				}
				
				synchronized(m_lock) {
					System.out.println("sub thread");
				}
			}
		};
		t.start();
		
		synchronized(m_lock) {
			try {
				Thread.sleep(5000);
			}catch(Exception ex) {
				ex.printStackTrace();
			}
			
			System.out.println("main thread");
		}
	}
}

public class Test
{
	public static void main(String[] args)
	{	
		Foo f = new Foo();
		f.fun();
	}
}

   下面为使用synchronized(obj){ ... }同步代码块来实现购票程序:

class TicketAccount
{
	private int balance = 1; //余票
	public int getBalance(){return balance;} //查询余票
	public void setBalance(int buyCnt) {balance -= buyCnt;}//购票
}

class BuyTicket implements Runnable
{
	private TicketAccount account;
	private int buyCnt = 1; //购票数
	BuyTicket(TicketAccount account)
	{ 
		this.account = account;
	}
	public void run()
	{
		synchronized(account)
		{
			if(account.getBalance() >= buyCnt) //余票足够的话就购票
			{
				account.setBalance(buyCnt);
				System.out.println(Thread.currentThread().getName() + "购票成功,余票为:" + account.getBalance());
			}
			else
			{
				System.out.println(Thread.currentThread().getName() + "购票失败:无票");
			}
		}
	}
}

public class Test
{
	public static void main(String[] args)throws Exception
	{
		BuyTicket ticket = new BuyTicket(new TicketAccount());
		//开启两个购票的线程
		Thread t1 = new Thread(ticket, "线程1");
		t1.start();
		Thread t2 = new Thread(ticket, "线程2");
		t2.start();
		t1.join();
		t2.join();
	}
}

  ②、同步方法——使用synchronized,此时的同步监视器为被synchronized修饰的方法的this实例,如下所示的两个方法是相同的效果:

	public synchronized void func()
	{
		...... 
	}
	
	public void func()
	{
		synchronized(this) {
			......
		}
	}

 下面是使用synchronized声明同步方法实现购票的示例:

class BuyTicket
{
	private int balance = 1; //余票
	private int getBalance(){return balance;} //查询余票
	private void setBalance(int buyCnt) {balance -= buyCnt;}//购票
	public synchronized void buyTicket(int buyCnt)
	{
		if(getBalance() >= buyCnt) //余票足够的话就购票
		{
			setBalance(buyCnt);
			System.out.println(Thread.currentThread().getName() + "购票成功,余票为:" + getBalance());
		}
		else
		{
			try{
				wait();
			}
			catch(Exception e)
			{
				
			}
			
			System.out.println(Thread.currentThread().getName() + "购票失败:无票");
		}
	}
}

class BuyTicketThread implements Runnable
{
	private BuyTicket account;
	private int buyCnt = 1; //购票数
	BuyTicketThread(BuyTicket account)
	{ 
		this.account = account;
	}
	public void run()
	{
		account.buyTicket(buyCnt);
	}
}

public class Test
{
	public static void main(String[] args)throws Exception
	{
		BuyTicketThread ticket = new BuyTicketThread(new BuyTicket());
		//开启两个购票的线程
		Thread t1 = new Thread(ticket, "线程1");
		t1.start();
		Thread t2 = new Thread(ticket, "线程2");
		t2.start();
		t1.join();
		t2.join();
	}
}

  ③、使用同步锁:Lock同步锁、ReadWriteLock读写锁是两个接口,ReentrantLock和ReentrantReadWriteLock是它们的实现类,并且提供可重入(可以对已加锁的Lock再次加锁)。

  Lock中还定义了tryLock()方法,获得锁失败会直接返回false,而不是阻塞等待。为了避免程序发生异常而无法解锁Lock,一定要在finally中调用Lock对象的unlock()方法解锁。

import java.util.concurrent.locks.*;

class BuyTicket
{
	private int balance = 1; //余票
	private final ReentrantLock lock = new ReentrantLock();
	private int getBalance(){return balance;} //查询余票
	private void setBalance(int buyCnt) {balance -= buyCnt;}//购票
	public void buyTicket(int buyCnt)
	{
		lock.lock();
		try
		{
			if(getBalance() >= buyCnt) //余票足够的话就购票
			{
				setBalance(buyCnt);
				System.out.println(Thread.currentThread().getName() + "购票成功,余票为:" + getBalance());
			}
			else
			{
				System.out.println(Thread.currentThread().getName() + "购票失败:无票");
			}
		}
		finally
		{
			lock.unlock();
		}
	}
}

class BuyTicketThread implements Runnable
{
	private BuyTicket account;
	private int buyCnt = 1; //购票数
	BuyTicketThread(BuyTicket account)
	{ 
		this.account = account;
	}
	public void run()
	{
		account.buyTicket(buyCnt);
	}
}

public class Test
{
	public static void main(String[] args)throws Exception
	{
		BuyTicketThread ticket = new BuyTicketThread(new BuyTicket());
		//开启两个购票的线程
		Thread t1 = new Thread(ticket, "线程1");
		t1.start();
		Thread t2 = new Thread(ticket, "线程2");
		t2.start();
		t1.join();
		t2.join();
	}
}

   使用读写锁的好处是当只有读操作的时候,所有线程都可以获得锁,当写的时候会加锁。读写锁适合有两个或两个以上线程会进行读操作的情况。调用ReentrantReadWriteLock.ReadLock().lock()时,若没有任何ReentrantReadWriteLock.().Writelock().lock(),也就是没有任何写入获得锁定的时候,就可以直接获得锁。调用ReentrantReadWriteLock.().Writelock().lock()时,若没有任何ReentrantReadWriteLock.ReadLock().lock()或ReentrantReadWriteLock.().Writelock().lock(),也就是没有任何读取或写入获得锁定的时候,就可以直接获得锁。读写锁使用示例如下:

import java.util.concurrent.locks.*;
class CFoo
{
	void read()
	{
		rwLock.readLock().lock();
		try {
			//ToDo...
		}finally {
			rwLock.readLock().unlock();
		}
	}
	
	void write()
	{
		rwLock.writeLock().lock();
		try {
			//ToDo...
		}finally {
			rwLock.writeLock().unlock();
		}
	}
	
	ReadWriteLock rwLock = new ReentrantReadWriteLock();
}

    ReadWriteLock在没有任何读取或写入锁定的时候才会获取写入的锁定(悲观锁),当读取线程很多,写入线程很少的情况,使用ReadWriteLock进行写入锁定的话可能会使写入线程遭受饥饿问题,也就是写入线程迟迟无法竞争到锁(因为很多读线程在占用锁),而长时间处于等待状态。StampedLock提供了乐观的读取操作(乐观锁),也就是在读取线程很多写入线程很少的情况下,可以乐观的认为读取和写入同时发生的机会很少,读取的时候可以先不用锁定,在读入数据后再判断数据是否已经遭到写入线程的变更,再进行后续的措施(重新读取变更的数据或抛出异常)。Java 8增加的StampedLock可以在大多数场景中替换掉原来的ReentrantReadWriteLock。以下为使用StampedLock的示例:

import java.util.concurrent.locks.*;
class CFoo
{
	Object get(int index)
	{
		long stamp = lock.tryOptimisticRead(); //试着乐观的获得读锁定,该方法不会真正的获得读锁定(不会阻塞),调用它来获得一个锁定的标记
		Object elem = elems[index]; //直接读取数据
		//validat()判断锁定的标记是不是已经被其它排他锁获得了,是的话返回false,表示数据可能已经被改变,需要加锁获得数据
		//validat()返回true表示上面获取数据过程中没有其它的写入操作,可以直接使用读取的数据
		if(!lock.validate(stamp)) 
		{
			stamp = lock.readLock(); //真正的获得读锁定
			try {
				elem = elems[index];
			}finally {
				lock.unlockRead(stamp); //释放读锁定
			}
		}
		
		return elem;
	}
	
	void add(Object elem)
	{
		long stamp = lock.writeLock(); //获得写入锁定
		try {
			elems.add(elem);
		}finally {
			lock.unlock(stamp); //释放写入锁定
		}
	}
	
	StampedLock lock = new StampedLock();
	Object[] elems;
}

    死锁:当两个线程互相等待对方释放同步监视器时就会发生死锁。如下程序中,子线程调用了b对象的同步方法bar(),所以锁定了同步监视器b,主线程调用了a对象的同步方法foo(),所以锁定了同步监视器a,而当子线程的bar()方法中要调用a对象的last()方法时就需要先对a对象加锁,而此时a对象正被主线程锁定,主线程的foo()方法中也想调用b对象的last()方法,这就造成了互相等待的死锁。

class A
{
	public synchronized void foo( B b )
	{
		try
		{
			Thread.sleep(200);
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
		
		b.last(); //企图调用B实例的last()方法
	}
	public synchronized void last()
	{
		System.out.println("进入了A类的last()方法内部");
	}
}

class B
{
	public synchronized void bar( A a )
	{
		try
		{
			Thread.sleep(200);
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
		a.last(); //企图调用A实例的last()方法
	}
	public synchronized void last()
	{
		System.out.println("进入了B类的last()方法内部");
	}
}

public class DeadLock implements Runnable
{
	A a = new A();
	B b = new B();
	public void init()
	{
		a.foo(b);
	}
	public void run()
	{
		// 调用b对象的bar方法
		b.bar(a);
	}
	public static void main(String[] args)
	{
		DeadLock dl = new DeadLock();
		new Thread(dl).start();
		
		// 调用a对象的foo方法
		dl.init();
	}
}

  ④、原子类

  AtomicXXX是原子类,比如AtomicInteger、AtomicLong、AtomicReference(对象的引用类型)等,它们支持计算的原子操作而不用再加锁,如++计算可以直接调用原子类的aincrementAndGet()方法而不用加锁。

    AtomicXXX原子类利用了CAS(compareAndSwap)技术来实现旋转锁。比如当一个线程对一个数n调用aincrementAndGet()的时候会先获得n的值,然后比较n现在在内存中的值是否还等于刚才获得的n的值,如果等于的话就+1,不等于的话循环上面的操作直到等于的情况。比如线程A调用aincrementAndGet(),在CAS方法中获得了n的值为100,然后线程B执行aincrementAndGet(),在CAS中也获得了n的值为100,然后A的CAS继续比较n内存中的值为100,与刚才获得的值相同,所以将n加1,A的CAS执行成功, 而线程B的CAS继续执行的时候比较n内存中的值为101,与刚才获得的n的值不同,所以不执行加1操作CAS返回失败,而B会再次进行CAS操作直到成功。注意CAS利用了现代CPU的技术来使比较操作和加1操作是一个不可分割的原子操作。

  CAS实现的旋转锁的优点:无锁编程,如果使用lock等锁的话就是在访问共享变量前不管该变量有没有被其它线程使用都先加锁后访问(加锁是一个耗费CPU的动作),CAS不使用锁,但它类似与一个乐观锁,如果变量被其它线程改变会直接返回失败,而且失败的话会一直尝试CompareAndSwap直到成功,否则是是直接对变量进行操作,效率高。

  CAS实现的旋转锁的缺点:CAS在意识到变量被其它线程修改后会返回失败,然后再次进行比较和赋值的操作直到成功,这就是自旋,如果长时间不成功的话也会给CPU带来高额开销。

4、线程通信

  ①、使用Object的成员方法wait()、notify()、notifyAll()

  这三个方法必须由synchronized中的同步监视器对象来调用,当线程中调用了同步监视器对象的wait()后会释放同步监视器并等待,当调用了同步监视器对象的notify()后,会从对象等待集合中随机通知一个线程。对于使用synchronized修饰的同步方法,因为该类的默认实例this就是同步监视器,所以可以在同步方法中直接调用。 对于synchronized(obj)修饰的同步代码块,因为同步监视器是一个obj对象,所以必须使用该对象来调用这三个方法。

class Operation
{
    boolean bFlag = false;
	public synchronized void waitFun()
	{
		try
		{
			System.out.println(Thread.currentThread().getName() + " wait...");
            while(!bFlag) //这里使用while循环而不是if是因为wait()的返回可能是因为线程的私自苏醒
			    wait();
			System.out.println(Thread.currentThread().getName() + " run!");
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
	}
	public synchronized void notifyFun()
	{
		System.out.println(Thread.currentThread().getName() + " notify.");
        bFlag = true;
		notify();
	}
}
 
class WaitThreadRun implements Runnable
{
	private Operation op;
	public WaitThreadRun(Operation op){this.op = op;}
	public void run()
	{
		op.waitFun();
	}
}

class NotifyThreadRun implements Runnable
{
	private Operation op;
	public NotifyThreadRun(Operation op){this.op = op;}
	public void run()
	{
		op.notifyFun();
	}
}
 
public class Test
{
	public static void main(String[] args)throws Exception
	{
		Operation op = new Operation();
		Thread t1 = new Thread(new WaitThreadRun(op), "等待线程");
		t1.start();
		Thread.sleep(1000);
		Thread t2 = new Thread(new NotifyThreadRun(op), "通知线程");
		t2.start();
		t1.join();
		t2.join();
	}
}

  ②、使用Condition类的await()、signal()、signalAll()

  对于使用Lock锁来同步的程序不存在同步监视器,可以使用Condition来控制线程通信,在这种情况下,Lock替代了同步代码块或方法,Condition替代了同步监视器的功能。Condition的三个方法必须在获得同步锁Lock后使用,线程调用Contidion的await()后会放弃对Lock的锁定。

import java.util.concurrent.locks.*;

class Operation
{
    bool bStart = false;
	private final ReentrantLock lock = new ReentrantLock();
	private final Condition cond = lock.newCondition();
	public void waitFun()
	{
		lock.lock();
		try
		{
			System.out.println(Thread.currentThread().getName() + " wait...");
            while(!bStart)
			    cond.await();
			System.out.println(Thread.currentThread().getName() + " run!");
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
		finally
		{
			lock.unlock();
		}
	}
	public void notifyFun()
	{
		lock.lock();
		System.out.println(Thread.currentThread().getName() + " notify.");
        bStart = true;
		cond.signal();
		lock.unlock();
	}
}
 
class WaitThreadRun implements Runnable
{
	private Operation op;
	public WaitThreadRun(Operation op){this.op = op;}
	public void run()
	{
		op.waitFun();
	}
}

class NotifyThreadRun implements Runnable
{
	private Operation op;
	public NotifyThreadRun(Operation op){this.op = op;}
	public void run()
	{
		op.notifyFun();
	}
}
 
public class Test
{
	public static void main(String[] args)throws Exception
	{
		Operation op = new Operation();
		Thread t1 = new Thread(new WaitThreadRun(op), "等待线程");
		t1.start();
		Thread.sleep(1000);
		Thread t2 = new Thread(new NotifyThreadRun(op), "通知线程");
		t2.start();
		t1.join();
		t2.join();
	}
}

 下面是使用条件变量实现生产者-消费者模型的例子:

import java.util.concurrent.locks.*;

class Clerk
{
	private int product = -1;
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void setProduct(int product) throws InterruptedException //生产者
	{
		lock.lock();
		try {
			waitIfFull();
			this.product = product;
			System.out.printf("生产者设定(%d)%n", this.product);
			condition.signal();
		}finally { lock.unlock(); }
	}
	private void waitIfFull() throws InterruptedException
	{
		while(this.product != -1)
			condition.await();
	}
	public int getProduct() throws InterruptedException //消费者
	{
		lock.lock();
		try {
			waitIfEmpty();
			int p = this.product;
			this.product = -1;
			System.out.printf("消费者取走(%d)%n", p);
			condition.signal();
			return p;
		}finally{ lock.unlock(); }
	}
	private void waitIfEmpty() throws InterruptedException
	{
		while(this.product == -1)
			condition.await();
	}
}

class Producer implements Runnable
{
	private Clerk clerk;
	public Producer(Clerk clerk) { this.clerk = clerk; }
	@Override
	public void run()
	{
		System.out.println("生产者开始生产整数...");
		for(int i = 0; i < 10; ++i)
		{
			try {
				clerk.setProduct(i);
			}catch(InterruptedException ex) { throw new RuntimeException(ex); }
		}
	}
}

class Consumer implements Runnable
{
	private Clerk clerk;
	public Consumer(Clerk clerk) { this.clerk = clerk; }
	@Override
	public void run()
	{
		System.out.println("消费者开始消费整数...");
		for(int i = 0; i < 10; ++i)
		{
			try {
				clerk.getProduct();
			}catch(InterruptedException ex) { throw new RuntimeException(ex); }
		}
	}
}

public class Test
{
	public static void main(String[] args)
	{	
		Clerk clerk = new Clerk();
		new Thread(new Producer(clerk)).start();
		new Thread(new Consumer(clerk)).start();
	}
}

  上面的示例可能会存在一个问题,那就是在有多个生产者、消费者线程的时候,调用notify()时,可能通知的是生产者,也可能通知的是消费者,可以使用多个Condition实例,一个给生产者线程用,一个给消费者线程用:

  

class Clerk
{
	private int product = -1;
	private Lock lock = new ReentrantLock();
	private Condition producerCond = lock.newCondition();
	private Condition consumerCond = lock.newCondition();
	
	public void setProduct(int product) throws InterruptedException //生产者
	{
		lock.lock();
		try {
			waitIfFull();
			this.product = product;
			System.out.printf("生产者设定(%d)%n", this.product);
			consumerCond.signal();
		}finally { lock.unlock(); }
	}
	private void waitIfFull() throws InterruptedException
	{
		while(this.product != -1)
			producerCond.await();
	}
	public int getProduct() throws InterruptedException //消费者
	{
		lock.lock();
		try {
			waitIfEmpty();
			int p = this.product;
			this.product = -1;
			System.out.printf("消费者取走(%d)%n", p);
			producerCond.signal();
			return p;
		}finally{ lock.unlock(); }
	}
	private void waitIfEmpty() throws InterruptedException
	{
		while(this.product == -1)
			consumerCond.await();
	}
}

 ③、使用阻塞队列BlockingQueue

   BlockingQueue是Queue容器的子接口,当生产者线程调用put()方法向其队尾插入元素的时候,如果队列已满则当前线程会阻塞,当消费者线程调用take()方法从队列头取出元素的时候,如果队列为空的话当前线程会阻塞。

  Queue中在队列尾插入元素的方法有add(队列满会抛出异常)、offer(队列满会返回false),在队列头部删除并返回删除的元素有remove(队列空会抛出异常)、poll(队列空会返回false),在队列头部取出但不删除元素有element(队列空会抛出异常)、peek(队列空会返回false)。

  BlockingQueue下有以下5个实现类:

   ArrayBlockingQueue:基于数组实现的BlockingQueue。
   LinkedBlockingQueue:基于链表实现的BlockingQueue。
   PriorityBlockingQueue:该队列以元素大小排列,最小元素排在队头,元素大小判断可以通过元素(实现Comparable接口)本身大小来自然排序,也可以使用一个Comparator(比较器)来定制排序。
   SynchronousQueue:同步队列,对该队列的存、取操作必须交替进行。
   DelayQueue:底层基于PriorityBlockingQueue,它要求元素类型实现Delay接口,根据接口方法的返回值进行排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值