上篇说道可是 在主线程或者其他线程中创建自己的线程来执行任务。

 

Runnable task = new TaskClass();
new Thread(task).start();

       该方法对单一任务是非常方便的但是由于必须对每个人物创建一个线程,因此对大量的任务而言就是不够高效的。为每个任务创建一个线程可能会限制流量并且影响性能。比如说, 如果一个计算机配置不错,它假设可以流畅的运行 100个线程,但是你弱同时创建了1000个线程,那么cup时间就会不够用,这样就造成了所有线程执行时间都很少,外在表现就是 程序运行缓慢,甚至很多操作无法得到响应,也就是死机。线程池是管理并发执行多个任务的理想方法。Java提供Executor接口来执行线程池中的任务,提供ExecutorService接口来管理和控制任务。ExecutorService接口是 Executor的子接口。

       

 

       为了创建一个Executor对象,可以使用Executors类中的静态方法。 newFixedThreadPool(int) 方法 在池中创建固定数目的线程。如果线程完成了任务的执行,它可以被重新使用以执行另外一个任务。如果线程池中所有的线程都不是出于空闲状态,而且有任务在等待执行,那么在关机之前,如果由于一个错误中止了一个线程,就会创建一个新线程来替代它。而newCachedthreadPool() 会创建一个缓冲池,如果缓冲池中没有空闲线程,而且有新的任务等待执行,那么会创建一个新的线程来执行这个等待的任务,如果空闲线程60秒内斗没有被使用就会终止它。缓冲池比较适合执行多个小任务。

 

package LianXi;
import java.util.concurrent.*;
public class TaskThreadDemo{
	public static void main(String[] args){
		PrintChar printC = new PrintChar('C',1000);
		PrintChar printX = new PrintChar('X',1000);
		PrintNum printNum = new PrintNum(1000);
        ExecutorService executor = Executors.newFixedThreadPool(3);
		executor.execute(printC);
		executor.execute(printX);
		executor.execute(printNum);
		executor.shutdown();
	
	}
}

class PrintChar implements Runnable{
	private char _charToPrint;
	private int _times;

	public PrintChar(char c,int t){
		_charToPrint = c;
		_times = t;
	}

	public void run(){
		for(int i = 0 ;i <_times; i++){
			System.out.print(_charToPrint);
		}
		System.out.println("\n "+_charToPrint+"的输出结束");
	}
}

class PrintNum implements Runnable{
	private int _lastNum;

	public PrintNum(int lastNum){
		_lastNum = lastNum;
	}

	public void run(){
		for(int i = 0; i<=_lastNum; i++){
			System.out.print(i);
		}
		System.out.println("\n 数字输出结束");
	}
}

   以上程序创建了一个包含三个固定数目线程的线程池,因此,三个任务会并行执行。如果 将ExecutorService executor = Executors.newFixedThreadPool(3); 换成

ExecutorService executor = Executors.newFixedThreadPool(1); 那么将会只有一个线程执行三个任务,三个任务会顺序执行。如果换成ExecutorService executor = Executors.newCachedThreadPool(); 那么就会为每一个任务创建一个线程。

  如果需要为一个任务创建一个线程,就是用Thread类,如果需要为多个任务创建爱你线程,最好使用线程池 。

线程同步


     如果一个共享资源同时被多个线程访问的话就可能为被破坏。请看下面的例子。

    

package LianXi;
import java.util.concurrent.*;
public class AccountWithoutSync {
	private static Account _account = new Account();
	public static void main(String[] args) 
	{
		ExecutorService executor = Executors.newCachedThreadPool();
		for(int i =0 ; i<10000 ;i++){
			executor.execute(new AddMoneyToAccountTask());
		}
		executor.shutdown();
		 while(!executor.isTerminated()){
	    }
		System.out.println("当前账户的余额:¥"+_account.getMoney());
	}
   
   //执行任务的静态内部类
    private static class  AddMoneyToAccountTask  implements Runnable {
          @Override
          public void   run(){
			   _account.deposit(1);
		  }
    }
   
	//表示账户的内部类
    private static	class Account	{
		private  int _rmb = 0;

		public   int getMoney(){
			return _rmb;
		}

      		public  void deposit(int amount){
                        int newRmb= _rmb +1;
			//这里 让线程睡眠一会儿是为了模拟复杂操作需要更长的时间执行放大出现问题的概率
			try{
				Thread.sleep(5);
			} catch(InterruptedException ex){
			}
                        _rmb = newRmb;		
              }
	}
}

      上面的程序使用一个执行一千次想账户中添加money的任务,任务中每次想账户中添加1 ,按照正常的逻辑,任务执行完毕之后,账户余额应该是一千,但是结果却差强人意,可能只是43,或者52 ,输出的结果是不可预测的,但毫无疑问答案是错误的。

    在上面的代码中,Account的 deposit 方法内部的代码 可以替换为  

              

   int newRmb= _rmb+1;
  
   _rmb = newRmb;


 

   然而,尽管得到的值非常接近1000,但是似是而非(如果没有出现错误可以多试几次,或者把循环的次数增加到一万或者更多,错误总会出现的)。 deposit中多余的代码好似为了使问题显现的明显些。

    那么究竟是什么导致的程序的错误?下面给出一个可能的场景。

  

        在步骤1中,任务从账户中获取余额数目。在步骤2中,任务2从账户中获取的同样数目的余额。在步骤3中,任务1想账户写入一个新余额,在步骤4中,任务2也像该账户写入一个新余额。

      这个情景的效果是任务1做了无用功。因为在步骤4中,任务2覆盖了任务1的结果。很明显,问题就是任务1 和任务2 以一种冲突的方式访问账户这个公共资源。这是多线程程序中的常见问题,成为竞争状态。如果一个类的对象咋iduoxiancheng程序中没有导致竞争状态,则成这样的类为线程安全的。

 

   使用synchronized关键字同步线程

       为避免  竞争状态,应防止多个形成同时进入程序的某个部分,这个特定的部分成为临界区。上面的程序的临界区是整个deposit方法。可以使用synchronized关键字来同步方法,以便一次只有一个线程方位这个方法。有几种方法可以解决上面程序的问题。一种办法是 在seposit方法的生命中添加synchronized关键字..

public synchronized  void deposit(int amount)

      一个同步方法在执行之前需要加锁,对于实例方法,要给调用该方法的对象加锁。对于静态方法,要给这个类的class加锁。如果一个线程调用一个对象上的同步实例方法(静态方法),首先给该对象(类)加锁,然后执行该方法,最后解锁。在解锁之前另一个调用那个对象(类)中方法的线程被阻塞,直到解锁。

    随着deposit方法的被同步化,前面的场景不会再出现,如果任务一开始进入deposit方法,任务2将被阻塞,知道任务完成该方法的运行。

   同步语句
         

          调用一个类的实例方法要求为对象(this)加锁,调用一个类的静态同步方法,需要为该类(class)加锁。当执行方法中一个代码块需要同步时,同步语句,不经可以用于this对象加锁,而且可以对人物对象君子安所,这个代码块成为同步块。 

synchronized(expr){
       //TODO
       //statements
}

          表达式expr必须得到一个对象的引用。如果对象已经被另一个线程锁定,则在解锁之前,该线程将被阻塞。当获准对一个对象加锁时,该线程执行同步代码块中的语句,然后解除给对象所加的锁。任何的实例同步方法都可以转换为同步语句。例如,

       

public  synchronized void xMothod(){
         //mothod  body
}
//等同于
public void xMothed(){
         synchronized(this){
                 //mothod body
         }
}




而同步的静态方法可以转化为一下同步语句

public static synchronized void xMothod(){
         //mothod  body
}
//等同于
public static void xMothed(){
         synchronized(getClass()){
                 //mothod body
         }
}


利用加锁同步

       同步的实例方法在执行之前隐式的需要一个锁。

      java可以显式的加锁,这给协调线程带来的更多的控制功能。一个锁是一个Lock接口的实例,他定义了枷锁和释放锁的方法。锁也可以使用newCodiction方法来创建任意

个数的Condition对象,用来进行线程间通信。

ReentrantLock 是为促昂见互相排斥的锁的具体实现。可以创建具有特定的公平策略的锁。真正的公平策略确保等待时间最长的线程首先获得锁。假的公平策略将锁给任意

一个在等待的线程。呗多个线程访问的使用公正锁的程序,其整体性能比那些使用默认设置的程序差,但是在获取锁且避免资源缺乏是变化很小。

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();
		for(int i =0; i<100;i++){
			executor.execute(new AddMoneyTask());
			//System.out.println(_account.getAccountInfor());
		}

	}
    
	//执行任务想账户中加钱
	public static class AddMoneyTask implements Runnable{
		@Override
		public void run(){
			_account.deposit(1);
		}
	}
    
	//账户
	public static class  Account{
	    private static Lock _lock = new ReentrantLock(); //创建一个锁
		private int _moneyCount=0;

		public int getMoneyCount(){
			return _moneyCount;
		}

		public void deposit(int amount){
			_lock.lock(); //获得锁
            int newMoneyCount = getMoneyCount()+amount;
			try{
			    Thread.sleep(3);
                _moneyCount=newMoneyCount;
				System.out.println(_account.getAccountInfor());
			} catch(InterruptedException ex) {
			
			} finally {
				_lock.unlock();
			}
			
		}
		public String getAccountInfor(){
			return "当前账户的余额为:¥"+getMoneyCount();
		}
	}
}


需要注意的是 在finally 块中释放锁,可以确保索贝释放掉。通常,使用Synchronized犯法或这语句使用相互排斥的锁比较简单一些,然而,使用显示锁对同步具有状态的

线程更加直观和灵活。

  待续。。。。

 

 







作者:duanmengchao 发表于2012-4-22 11:46:17 原文链接
阅读:22 评论:0 查看评论
 posted on 2012-04-22 11:46  小段段  阅读(637)  评论(0编辑  收藏  举报