Java-多线程2

多线程的概念

就像操作系统可以在不同程序(进程)上切换执行,让我们感到多个程序同时运行那样,在一个Java程序内部可以开辟线程来同时执行多个任务。

cpu运行多个线程

在很多情况下多线程可以发挥作用,比如多线程可以让程序在进行一项非常繁重的工作时,比如读写文件,还可以同时进行其它的任务,比如响应界面事件。

main()方法本身就运行在一个线程中。Swing界面代码需要运行在事件派发线程中。

创建线程

一般来说,可以用两种方式来创建线程

  • 让某一个类实现Runnable接口并让Thread对象来运行它
  • 继承Thread类并重写run方法

方法1.实现Runnable接口

一种实现线程的方式是让一个类实现Runnable接口,java.lang.Runnable是Java中定义的接口

public interface Runnable{    
	public void run();    
}  
 public class TaskClass implements Runnable {
     ...
     public TaskClass(...){
       ...
     }
	 // 实现Runnable接口的run方法
     public void run() {
         // 定义线程运行时运行的代码
         . . .
     }
}

创建该类的对象后,将它作为参数传给Thread线程对象的构造函数,然后调用线程对象的start()方法启动线程,代码如下

public class Client{
  ...
  public void someMethod(){
    ...
    // 创建一个TaskClass类的实例
    TaskClass task = new TaskClass();
    // 创建一个线程
    Thread thread = new Thread(task);
    // 开始线程
    thread.start();
  }
}

注意,开始线程的代码是thread.start()方法,因为把task对象传给了线程thread,线程开始以后,会运行task对象的run()方法,执行用户需要线程执行的代码。

  • 演示代码:下面的代码开辟了3个新线程,在第一个线程里面打印字符a一百次,第2个线程里面打印字符b一百次,第3个线程里面连续打印从1到100中的各个数。
public class TaskThreadDemoWithRunnable {
  public static void main(String[] args) {
    // Create tasks
    Runnable printA = new PrintChar('a', 100);
    Runnable printB = new PrintChar('b', 100);
    Runnable print100 = new PrintNum(100);

    // Create threads
    Thread thread1 = new Thread(printA);
    Thread thread2 = new Thread(printB);
    Thread thread3 = new Thread(print100);

    // Start threads
    thread1.start();
    thread2.start();
    thread3.start();
  }
}

// The task for printing a specified character in specified times
class PrintChar implements Runnable {
  private char charToPrint; // The character to print
  private int times; // The times to repeat

  /** Construct a task with specified character and number of
   *  times to print the character
   */
  public PrintChar(char c, int t) {
    charToPrint = c;
    times = t;
  }

  @Override /** Override the run() method to tell the system
   *  what the task to perform
   */
  public void run() {
    for (int i = 0; i < times; i++) {
      System.out.print(charToPrint);
    }
  }
}

// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
  private int lastNum;

  /** Construct a task for printing 1, 2, ... i */
  public PrintNum(int n) {
    lastNum = n;
  }

  @Override /** Tell the thread how to run */
  public void run() {
    for (int i = 1; i <= lastNum; i++) {
      System.out.print(" " + i);
    }
  }
}

这种写法很好的将线程(Thread类)和线程执行的任务(实现Runnable接口的类)区分开来。除了这种方法,还有一种写法可以运行线程执行任务,这种写法直接使用Thread类。

线程类Thread

java.lang.Thread类是Java中定义的线程类,该类含有启动线程并有一些管理和查看线程状态的方法。
Thread类

方法2. 继承Thread类

由于Thread类自己也实现了Runnable接口,也可以这样来启动一个新的线程:

定义一个继承于Thread类的类,并重写Thread类的run方法,创建该类的对象后调用start方法启动线程,线程会执行run()方法。比如,定义线程来计算大于某个数的所有素数的线程可以写成

// 自定义线程类
public class CustomThread extends Thread { 
    ...
    pulic CustomThread(...) {
      ...
	}
	// 重写Runnable接口的run方法
	public void run() {
	     // 定义线程运行时运行的代码
	     . . .
	}
}

创建线程对象并启动线程的代码如下

// 
public class Client{
    ...
    public void someMethod(){
        ...
        // 创建一个线程对象
        CustomThread thread1 = new CustomThread(...);
        
        // 启动线程
        thread1.start();
        ...

        // 创建另一个线程对象
        CustomThread thread2 = new CustomThread(...);
        
        // 启动线程
        thread2.start();        
        ...
    }
    ...
}
  • 用继承Thread的方式来实现上述例子
public class TaskThreadDemoWithThread {
	  public static void main(String[] args) {	    
	    // Create threads
	    PrintCharThread thread1 = new PrintCharThread('a', 100);
	    PrintCharThread thread2 = new PrintCharThread('b', 100);
	    PrintNumThread thread3 = new PrintNumThread(100);
	    // Start threads
	    thread1.start();
	    thread2.start();
	    thread3.start();
	  }
	}

	// The thread for printing a specified character in specified times
	class PrintCharThread extends Thread {
	  private char charToPrint; // The character to print
	  private int times; // The times to repeat

	  /** Construct a thread with specified character and number of
	   *  times to print the character
	   */
	  public PrintCharThread(char c, int t) {
	    charToPrint = c;
	    times = t;
	  }

	  @Override /** Override the run() method of thread
	   */
	  public void run() {
	    for (int i = 0; i < times; i++) {
	      System.out.print(charToPrint);
	    }
	  }
	}

	// The thread class for printing number from 1 to n for a given n
	class PrintNumThread extends Thread {
	  private int lastNum;

	  /** Construct a thread for printing 1, 2, ... i */
	  public PrintNumThread(int n) {
	    lastNum = n;
	  }

	  @Override/** Override the run() method of thread
	   */
	  public void run() {
	    for (int i = 1; i <= lastNum; i++) {
	      System.out.print(" " + i);
	    }
	  }
	}

实现Runnable接口和继承Thread类都可以实现线程,推荐使用第一种方案,即实现Runnable接口,因为它把线程框架和线程任务很好的区分开来了。

除了start()方法可以启动线程,介绍一下线程类Thread其它常用方法。

sleep

static void sleep(long milliseconds) throws InterruptedException

sleep(long milliseconds)方法让当前线程休眠一会,其参数是让线程休眠的毫秒数。如果希望线程长期执行,又避免过度占用cpu,可以调用此方法。创建线程的例子中让PrintNum线程执行过程中有规律的休眠一会。

public class TaskThreadDemoWithSleep {
	public static void main(String[] args) {

		Runnable print100 = new PrintNum(100);

		Thread thread3 = new Thread(print100);

		thread3.start();
	}
}

// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
	private int lastNum;

	/** Construct a task for printing 1, 2, ... i */
	public PrintNum(int n) {
		lastNum = n;
	}

	@Override /** Tell the thread how to run */
	public void run() {
		try {
			for (int i = 1; i < lastNum; i++) {
				System.out.print(" " + i);
				Thread.sleep(100);  // 线程休眠100毫秒,这样别的线程可以有更多的时间执行
			}
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}
}

因为sleep()方法会抛出InterruptedException异常,这个异常是检查异常,因此需要try-catch捕捉异常,或者声明函数会抛出这个异常。休眠中的线程如果被调用了interrupt()方法就会被唤醒,并抛出该异常。如果此时我们不想让线程继续休眠,就不能把try-catch放在循环中,否则线程即使被打断,也不会终止。例如像下面的代码做的那样,

public class TaskThreadDemoWithSleepAndNoRespondToInterrupt {
	public static void main(String[] args) {

		Runnable print100 = new PrintNum(100);

		Thread thread3 = new Thread(print100);

		thread3.start();
	}
}

// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
	private int lastNum;

	/** Construct a task for printing 1, 2, ... i */
	public PrintNum(int n) {
		lastNum = n;
	}

	@Override /** Tell the thread how to run */
	public void run() {
		for (int i = 1; i < lastNum; i++) {
			System.out.print(" " + i);
			try {  // 把try-catch放在了for循环中,这样造成线程被中断了以后还会被执行,是错误的
				Thread.sleep(100);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}
	}
}

yield方法

类似于sleep方法,yield方法也是类方法,用来让当前线程暂时释放cpu时间,给别的线程执行。比如,PrintNum类中的代码可以实现为:

public void run() {
	    for (int i = 1; i <= lastNum; i++) {
	      System.out.print(" " + i);
	      Thread.yield();
	    }
	  }

每次输出一个数后,输出数的线程会放弃下cpu时间,让其它输出线程执行。

isAlive

final boolean isAlive​()

isAlive()方法判断线程是否在运行,如果线程还没有开始,或者线程运行已经结束,isAlive()方法返回false,否则返回true。

如果main线程要等待子线程结束才结束,可以使用isAlive方法来判断子线程是否执行完成

public class WaitThreadWithIsAlive {
	public static void main(String[] args) {

		Runnable print100 = new PrintNum(100);

		Thread thread3 = new Thread(print100);

		System.out.println("before thread start, alive is :" + thread3.isAlive() + ", thread is to start");

		thread3.start();

		try {
			do {
				Thread.sleep(100); // main线程会休眠100毫秒
			} while (thread3.isAlive());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
	private int lastNum;

	/** Construct a task for printing 1, 2, ... i */
	public PrintNum(int n) {
		lastNum = n;
	}

	@Override /** Tell the thread how to run */
	public void run() {
		try {
			for (int i = 1; i < lastNum; i++) {
				System.out.print(" " + i);
				Thread.sleep(100);
			}
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}
}

join

void join​() throws InterruptedException

假设在线程a中调用线程对象b的join()方法,那么线程a会阻塞(暂停)在调用语句那里,直到线程对象b表示的线程运行结束。如果要让main线程等待子线程的结束再结束,可以调用子线程的join方法,阻塞main线程的执行。

public class WaitThreadWithIsJoin {
	public static void main(String[] args) {

		Runnable print100 = new PrintNum(100);

		Thread thread3 = new Thread(print100);

		System.out.println("before thread start, alive is :" + thread3.isAlive() + ", thread is to start");
		
		thread3.start();
		
		try {		    
			thread3.join();  //  main函数会阻塞在这里,直到thread3线程运行结束
			System.out.println("thread3 end");			
		}catch(InterruptedException ex) {
			ex.printStackTrace();
		}
	}
}

// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
	private int lastNum;

	/** Construct a task for printing 1, 2, ... i */
	public PrintNum(int n) {
		lastNum = n;
	}

	@Override /** Tell the thread how to run */
	public void run() {
		try {
			for (int i = 1; i < lastNum; i++) {
				System.out.print(" " + i);
				Thread.sleep(100);
			}
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}
}

setPriority

void setPriority​(int newPriority)

setPriority(int)方法设置线程的优先级,getPriority()返回线程的优先级。优先级在1到10之间。用整数常量Thread.MIN_PRIORITY,Thread.NORM_PRIORITY,Thread.MAX_PRIORITY分别表示优先级1,5,10。main方法的优先级是Thread.NORM_PRIORITY。

优先级越高的线程,被cpu选择运行的概率越大。如果所有的线程都有相同的优先级,那么每个线程会被分配相同的cpu时间依次执行,这称为轮询调度算法。

线程池

要创建多个线程,最简单的做法是创建Thread数组,比如下面的程序,创建了含有3个元素的Thread数组,运行3个Runnable线程任务:

public class ThreadArrayDemo {
	public static void main(String[] args) {
		Thread[] threadArray = new Thread[3];
		threadArray[0] = new Thread(new PrintChar('a', 100));
		threadArray[1] = new Thread(new PrintChar('b', 100));
		threadArray[2] = new Thread(new PrintNum(100));

		for (Thread th : threadArray) {
			th.start();
		}

		try {
			for (Thread th : threadArray) {
				th.join();
			}
		} catch (InterruptedException e) {
			System.out.println("Main thread interrupted.");
		}
		
		System.out.println("threads finished");
	}
}

要创建多个线程,除了像上面创建多个Thread对象,或者Thread数组,还可以使用线程池类,线程池可以理解为多个线程(Thread)的专用容器。Executors类的静态方法newFixedThreadPool(int)或者newCachedThreadPool()可以产生不同特性的线程池。

import java.util.concurrent.*;

public class ExecutorDemo {
  public static void main(String[] args) {
    // Create a fixed thread pool with maximum three threads
    ExecutorService executor = Executors.newFixedThreadPool(3);

    // Submit runnable tasks to the executor
    executor.execute(new PrintChar('a', 100));
    executor.execute(new PrintChar('b', 100));
    executor.execute(new PrintNum(100));

    // Shut down the executor
    executor.shutdown();
  }
}

ExecutorService是线程池的使用接口,而ExecutorService接口又继承于Executor接口,Executors可以产生不同特性的线程池。

  • Executors.newFixedThreadPool(int nThreads)创建一个线程池,这个线程池同时最多有nThreads个线程在运行,运行结束的线程可以复用。如果要运行的线程数量超出了线程池支持的线程个数,那这些线程会排队等待池中的线程运行结束再执行。
  • Executors.newCachedThreadPool() 创建一个线程池,这个线程池没有最多同时运行线程数量限制,运行结束的线程可以复用。如果一个线程在60秒内没有被使用,这个线程将被销毁。

如果上述线程池数量由3修改为1
ExecutorService executor = Executors.newFixedThreadPool(1);

即只包含一个线程,对程序有什么影响?

3个任务会依次顺序执行,因为线程池中只有1个线程。

shutdown()方法关闭线程池,使线程池不接收新的线程,但是不会妨碍线程池中已有线程的运行。当不需要再往线程池中添加新线程时,建议调用shutdown()方法。

线程池

线程同步

如果多个线程同时访问同一个数据,可能会出现数据的状态与预期结果不一致的问题,比如下面的程序创建100个线程来向t同一个账户中增加1元钱,结果预计是100元,但是实际结果却不是

import java.util.concurrent.*;

public class AccountWithoutSync {
  private static Account account  = new Account();  // 创建一个Account对象,其balance成员为0

  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();

    // Create and launch 100 threads
    for (int i = 0; i < 100; i++) {
      executor.execute(new AddAPennyTask());  // 创建一个AddAPennyTask,添加到executor多线程任务中,准备执行它
    }

    executor.shutdown();

    // 线程池shutdown以后,判断executor任务中的线程是否都执行完毕
    while (!executor.isTerminated()) {
    }

    // 在所有线程执行完毕后显示账户中balance的值
    System.out.println("What is balance? " + account.getBalance());
  }

  // A thread for adding a penny to the account,向帐号中增加1元的线程
  private static class AddAPennyTask implements Runnable {
    public void run() {
      account.deposit(1);
    }
  }

  // An inner class for account,Account内部类
  private static class Account {
    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void deposit(int amount) {
      int newBalance = balance + amount;  // 语句1

      // 这里故意使用sleep增加延迟,来扩大数据因多线程访问造成的混乱
      try {
        Thread.sleep(5);
      }
      catch (InterruptedException ex) {
      }

      balance = newBalance;  // 语句2
    }
  }
}

上面的程序中,AddAPennyTaskAccount是静态嵌套类(static nested classes)。(静态嵌套类只能访问外部类的静态成员,不能访问非静态成员。内部类(inner classes),也可称为非静态内部类,还能访问外部类的非静态成员。静态嵌套类仅表达了类层次上的包含关系,内部类还进一步表达了对象层次上的包含关系。)

程序多次运行的结果

What is balance? 4
What is balance? 6
What is balance? 3

结果与我们预期的100相差很大,为什么出现这种结果?

我们以两个线程的情况来说明,如果只有两个线程,那么balance预期应该是2,但是由于每个线程中把1元加到account的balance成员所依赖的两条语句,即语句1和语句2,可能没有连续的执行。中间可能穿插了另外一条线程的语句1、语句2,这样造成balance值的混乱。比如,下面的两种情况都会导致balance的值不对。

可能1:
未同步的一种错误情况

可能2:
未同步的一种错误情况

如果每个线程在执行deposit()方法时,语句1和语句2可以连续执行完那么最后的结果一定是我们想要的2。就像下面的图示

同步的情况

可见,因为两个线程要处理同一个变量,即account的balance成员,而由于线程的特性,在不做任何处理的情况下,每个线程的语句1和语句2不一定能连续的执行完成。因此为了得到我们想要的结果,我们需要让同一个线程的语句1和语句2连续的执行,不被其它的线程打断。这样需要连续的执行、即不被其它线程打断执行的代码段称为关键区域( critical region)。如果同一个时间只有一个线程可以进入关键区域,就可以避免结果的混乱。有多种办法可以做到这一点。

  • 同步方法
  • 同步代码块
  • 用Lock来同步代码(可选)

同步方法

因为语句1和语句2在deposit()方法里面,可以把整个deposit()方法当作我们的关键区域,用synchronized来修饰这个方法,就可以保证线程对这个方法的使用是同步的,即同一个时间只有一个线程可以运行这个方法中的代码,其余线程将被排除在这个方法之外,当已经有一个线程运行到同步方法里,当其它线程运行到调用这个方法时,将被阻塞(暂停)住,直到在方法中的线程运行完这个方法中的代码,白话解释就是同步方法使得各个线程在这个方法中能不被打断的(排它的)运行。

cpu运行多个线程

一旦方法被声明为同步的,方法里面的代码只会在某1个灰色的时间片中,不会出现在连续多个时间片中,即方法中的代码能够独享一段时间片,连续的、不被打断的运行。

我们可以这样声明deposit方法是同步的

public synchronized void deposit(double amount)

当我们把程序中的deposit()方法修饰为同步的,程序的结果和我们的预期就是一致的。当某个线程正在deposit方法中执行代码,别的线程就被阻塞在deposit()方法之外,直到在deposit()中的线程退出了deposit()方法。其它阻塞的线程会有一个线程被允许进入deposit()方法中执行代码。

What is balance? 100

synchronized修饰的方法在运行之前会要求一个锁(lock)。锁是排它的使用资源的一种机制。如果synchronized修饰的方法是实例(非静态)方法,锁来自于调用该成员方法的对象上。如果方法是类(静态)方法,锁来自于类。如果一个线程调用了synchronized实例方法(类方法),对象上(类上)的锁被改线程要求获得,获得后线程可以执行方法体,执行完成后锁被释放。另外调用该对象的这个方法的线程会被阻塞住,指定该线程可以获取锁。

线程同步机制-锁

同步代码块

把整个方法都同步起来,即在同一个时刻只有一个线程才能进入这个方法中运行代码,如果方法代码比较多,那么进入方法中执行的线程可能要花较长时间才能运行完整个方法,这意味着被阻塞在方法外面的线程可能会等待比较长的时间才能进入方法运行,很多线程都可能会等待较长时间,不经济。除了可以声明整个方法是同步的,还可以声明方法中的若干条语句是同步的,这样单个线程排它运行的代码就不是整个方法,而是方法中的若干条语句,其它线程等待的时间可能就变少。

   synchronized (对象变量名){
     // 同步的代码块
     语句;
     ...
   }

比如,我们可以用account对象上的锁来同步的account.deposit(1)方法的运行,

// A thread for adding a penny to the account,向帐号中增加1元的线程
  private static class AddAPennyTask implements Runnable {
    public void run() {
      synchronized (account){
	      account.deposit(1);
	  }
    }
  }

用这种方式修饰的代码块称为同步的代码块。多线程运行同步的代码块和运行同步的方法的模式是一致的。一个线程要进入同步的代码块,需要获得对象的锁(监视器),上面代码中用的是account对象的锁。锁是Object类中定义的一种成员变量,因此Java中的对象都有锁。进入到同步代码块的线程必须获得了对象的锁,而任意时刻只能有一个线程可以获得对象的锁,因此其它要进入同步代码块的线程会因为无法获取对象上的锁而被阻塞住。线程执行完同步代码块就释放掉对象上的锁,这样被阻塞的线程才能获得这个对象上的锁,进而进入被这个锁保护的同步代码块中运行。

线程通信

多个线程除了可能不能同时进入关键区域外,它们之间可能还需要更加复杂的协作关系,比如上面存储的例子,不但除了存钱的方法,还有取钱的方法,假设取钱的方法是

public void withDraw(int amount) {
       int newBalance = balance - amount;
       try {
           Thread.sleep(5);
         }
         catch (InterruptedException ex) {
       }
       balance = newBalance;
}

基于同步的知识,我们知道不论取钱线程还是取钱线程,这些线程不能同时进入这两个函数,否则会像之前只有单独的存储线程那样造成balance中值的异常。因此这两个函数都需要声明为同步的,任何一个时刻只能有一个线程可以进入它们中的一个,其余线程被阻塞在这两个函数之外。

public synchronized void withDraw(int amount) {
       int newBalance = balance - amount;
       try {
           Thread.sleep(5);
         }
         catch (InterruptedException ex) {
       }
       balance = newBalance;
}

但是目前,我们的线程对帐号的操作是有区别的,取钱线程有作用的一个前提是账号上的钱足大于等于需要取的数目,否则无法进行取钱操作。那么这里就引出一个问题,线程不但要逐个进入关键代码段,而且如果各个线程对关键代码段的操作含义不一样,那么在某些条件下,有些线程可以进入等待状态,只有在那些条件消失的情况下,等待状态下的线程才有必要苏醒了,并准备继续运行。比如,当取钱线程发现当前账号上钱不够时,它就可以进入等待状态,并允许其它线程进入同步代码块中运行。在某个存钱线程把钱存入账号后,存钱线程就可以发出通知,让等待的取钱线程准备继续运行。

Java中,可以使用Object类的wait()方法让线程进入等待状态,Object类的notify()或者notifyAll()方法通知等待线程退出等待状态。它们都是在同步的代码块中或者方法中被调用才有意义。

Object参考文档

import java.util.concurrent.*;

public class AccountWithoutSync {
  private static Account account  = new Account();  // 创建一个Account对象,其balance成员为0

  public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(2);

    executor.execute(new DepositTask());
    executor.execute(new WithdrawTask());

    executor.shutdown();

  }

  private static class DepositTask implements Runnable {
    public void run() {
    	try {
	    	while(true) {
	    		account.deposit((int)(Math.random() * 10) + 1);
				Thread.sleep(1000);				
	    	}
    	} catch (InterruptedException e) {			
			e.printStackTrace();
		}
    }
  }
  
  private static class WithdrawTask implements Runnable {
	    public void run() {
	    	while(true) {
	    		account.withDraw((int)(Math.random() * 10) + 1);
	    	}
	    }
	  }

  // An inner class for account,Account内部类
  private static class Account {
    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public synchronized void deposit(int amount) {
      balance += amount;
      System.out.println("Deposit " + amount + "\t\t\t\t\t" + getBalance());
      notifyAll();  // 通知等待的线程可以苏醒了
    }
    
    public synchronized void withDraw(int amount) {
    	try {
    		while(amount > balance) {   
    			System.out.println("\t\t\tFail Withdraw " + amount + "\t\t" + getBalance());
    			wait(); // 无法取钱,可以进入等待状态了
    		}
    	
    		int newBalance = balance - amount;
        
           balance = newBalance;
           System.out.println("\t\t\tWithdraw " + amount + "\t\t" + getBalance());
    	 }
        catch (InterruptedException ex) {
        	ex.printStackTrace();
        }
    }
  }
}

线程通信的一个案例

生产者消费者模式是一个典型的使用线程通信的场景,生产者线程往队列中存入数据,消费者线程从队列中取出数据,首先多个线程访问同一个数据-队列,那么存入和取出数据的代码需要同步,其次,当队列满了生产者线程需要等待消费者取出数据后才能继续生产,当队列空了消费者线程需要等待生产者线程存入数据后才能运行消费,这两个线程之间需要等待和通知继续运行。

class ConsumerProducer{
	private static Buffer buffer = new Buffer();
	
	public static void main(String[] args) {
		ExecutorService executor = Executors.newFixedThreadPool(2);
		executor.execute(new ProducerTask());
		executor.execute(new ConsumerTask());
		executor.shutdown();
	}
	
	// A task for adding an int to the buffer
	  private static class ProducerTask implements Runnable {
	    public void run() {
	      try {
	        int i = 1;
	        while (true) {	          
	          buffer.write(i++); // Add a value to the buffer
	          // Put the thread into sleep
	          Thread.sleep((int)(Math.random() * 10000));
	        }
	      } 
	      catch (InterruptedException ex) {
	        ex.printStackTrace();
	      }
	    }
	  }

	  // A task for reading and deleting an int from the buffer
	  private static class ConsumerTask implements Runnable {
	    public void run() {
	      try {
	        while (true) {
	        	buffer.read();
	          // Put the thread into sleep
	          Thread.sleep((int)(Math.random() * 10000));
	        }
	      } 
	      catch (InterruptedException ex) {
	        ex.printStackTrace();
	      }
	    }
	  }
	
	 // An inner class for buffer
	  private static class Buffer {
	    private static final int CAPACITY = 1; // buffer size
	    private java.util.LinkedList<Integer> queue =
	      new java.util.LinkedList<>();

	    public synchronized void write(int value) {
	    	System.out.println("Producer ready to writes " + value);	      
	      try {
	        while (queue.size() == CAPACITY) {
	          System.out.println("Wait for notFull condition");	          
	          wait();
	        }

	        queue.offer(value);
	        System.out.println("Producer writes " + value + " , "  + "buffer: " + queue.toString());
	        notify();	        
	      } 
	      catch (InterruptedException ex) {
	        ex.printStackTrace();
	      } 
	    }

	    public synchronized int read() {
	      System.out.println("\t\t\tConsumer ready to read");
	      int value = 0;
	      try {
	        while (queue.isEmpty()) {
	          System.out.println("\t\t\tWait for notEmpty condition");	         
	          wait();
	        }

	        value = queue.remove();	        
	        notify();
	      } 
	      catch (InterruptedException ex) {
	        ex.printStackTrace();
	      } 
	      finally {	        
	    	System.out.println("\t\t\tConsumer  reads " + value + " , "  + "buffer: " + queue.toString());
	        return value;
	      }
	    }
	  }
}

使用Lock来同步代码(可选)

可以显式对代码块加锁,一个lock是实现了Lock接口的对象,接口中的方法用来获取和释放锁。一个lock可以使用newCondition()来产生多个条件锁,条件锁在线程通讯中使用到。ReentrantLock是一个具体的类,它实现了Lock接口。

lock

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

public class AccountWithSyncUsingLock {
  private static Account account = new Account();

  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();

    // Create and launch 100 threads
    for (int i = 0; i < 100; i++) {
      executor.execute(new AddAPennyTask());
    }

    executor.shutdown();

    // Wait until all tasks are finished
    while (!executor.isTerminated()) {
    }

    System.out.println("What is balance ? " + account.getBalance());
  }

  // A thread for adding a penny to the account
  public static class AddAPennyTask implements Runnable {
    public void run() {
      account.deposit(1);
    }
  }

  // An inner class for account
  public static class Account {
    private static Lock lock = new ReentrantLock(); // Create a lock
    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void deposit(int amount) {
      lock.lock(); // Acquire the lock

      try {
        int newBalance = balance + amount;

        // This delay is deliberately added to magnify the
        // data-corruption problem and make it easy to see.
        Thread.sleep(5);

        balance = newBalance;
      }
      catch (InterruptedException ex) {
      }
      finally {
        lock.unlock(); // Release the lock
      }
    }
  }
}

用Condition实现线程通信(可选)

调用lock对象的newCondition()方法,可以创建一个Condition对象,通过调用Condition对象的await()、signal()和signalAll()方法可以进行线程通信。

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

public class ThreadCooperation {
  private static Account account = new Account();

  public static void main(String[] args) {
    // Create a thread pool with two threads
    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.execute(new DepositTask());
    executor.execute(new WithdrawTask());
    executor.shutdown();

    System.out.println("Thread 1\t\tThread 2\t\tBalance");
  }

  public static class DepositTask implements Runnable {
    @Override // Keep adding an amount to the account
    public void run() {
      try { // Purposely delay it to let the withdraw method proceed
        while (true) {
          account.deposit((int)(Math.random() * 10) + 1);
          Thread.sleep(1000);
        }
      }
      catch (InterruptedException ex) {
        ex.printStackTrace();
      }
    }
  }

  public static class WithdrawTask implements Runnable {
    @Override // Keep subtracting an amount from the account
    public void run() {
      while (true) {
        account.withdraw((int)(Math.random() * 10) + 1);
      }
    }
  }

  // An inner class for account
  private static class Account {
    // Create a new lock
    private static Lock lock = new ReentrantLock();

    // Create a condition
    private static Condition newDeposit = lock.newCondition();

    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void withdraw(int amount) {
      lock.lock(); // Acquire the lock
      try {
        while (balance < amount) {
          System.out.println("\t\t\tWait for a deposit");
          newDeposit.await();
        }
        
        balance -= amount;
        System.out.println("\t\t\tWithdraw " + amount +
          "\t\t" + getBalance());
      }
      catch (InterruptedException ex) {
        ex.printStackTrace();
      }
      finally {
        lock.unlock(); // Release the lock
      }
    }

    public void deposit(int amount) {
      lock.lock(); // Acquire the lock
      try {
        balance += amount;
        System.out.println("Deposit " + amount +
          "\t\t\t\t\t" + getBalance());

        // Signal thread waiting on the condition
        newDeposit.signalAll();
      }
      finally {
        lock.unlock(); // Release the lock
      }
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值