java编程 - 并发

  • 多线程是实现并发的一种有效手段。
  • 一个进程可以通过运行多个线程来并发地执行多项任务。
  • 多个线程如何调度执行由系统来实现。
  • 线程是程序中的单个执行流,多线程是一个程序 中包含的多个同时运行的执行流。
    table

线程与进程

  • 进程:内核级的实体。包含虚存映象、文件指示符, 用户ID等。这些结构都在内核空间中,用户程序 只有通过系统调用才能访问与改变。
  • 线程:用户级的实体。线程结构驻留在用户空间中, 能够被普通的用户级函数组成的线程库直接访问。 寄存器(栈指针,程序计数器)是线程专有的成分。
  • 一个进程中的所有线程共享该进程的状态。

Java中的线程

  • Java 中线程被认为是一个CPU、程序代码、和数据的封装体。
  • 一个虚拟的CPU,
  • 该CPU执行的代码: 代码与数据是相互独立的,代 码可以与其它线程共享。
  • 代码所操作的数据:数据也可以 被多个线程共享。
    table

线程的构造

  • Java.lang.Thread类使用户可以创建和控制自己的线程。
  • 在Java中,虚拟CPU是自动封装进Thread类的实例中, 而Code和Data要通过一个对象传给Thread类的构造函数。

线程的创建

  • 线程的Code和Data构成线程体。线程体决定了线程的行为。
  • Java中线程体由Thread类的 run( ) 方法定义,该方法 定义了线程的具体行为并指定了线程要操作的数据。
  • 有两种方式进行run( )方法的定义:
    • 实现 Runnable 接口
    • 继承 Thread 类

通过实现Runnable 接口创建线程

  • Runnable 接口只提供了一个public void run( )方法。
  • 定义一个类实现Runnable接口。
  • 将该类的实例作为参数传给Thread类的一个构造函数,从而创建一个线程。

线程创建示例

  • Public class ThreadTest{
        public static void main(String args[ ]){
    	Xyz r= new Xyz( );
    	Thread t = new Thread( r );
    	t.start( );
    	}
    }
    Class Xyz implements Runnable{
        int I ;
        public void run( ){
    	while( true){
    		System.out.println(“Hello”+I++);
    		if (I==5)  break ;
    		}
        	}
      }
    
    

线程创建示例

  • 一个线程就是Thread类的一个实例。线程是从一个传递给线程的Runnable 实例的run( )方法开始执行。线程所操作的数据是来自于该 Runnable 类的实例。
  • 线程 t
    table

通过继承Thread类创建线程

  • Thread 类本身实现了Runnable接口。
  • 通过继承Thread类,重写其中的run( )方法定义线程体。
  • 创建该子类的对象创建线程。
    Public class Counter extends Thread{
        public void run( ){
    	…
        }
    }
    
  • 创建与运行线程:
    Counter  ThreadCounter = new Counter( );
    ThreadCounter.Start( );
    
    

线程两种创建方法比较

  • 实现Runnable接口的优势:
    • 符合OO设计的思想。
    • 便于用extends继承其它类。
  • 采用继承Thread类方法的优点:程序代码更简单。
  • 提倡采用第一种方式。

    线程的运行

    • 新创建的线程不会自动运行。必须调用线程的start( )方法, 如: t.start( )
    • 该方法的调用把嵌入在线程中的虚拟CPU置为可运行(Runnable)状态。
    • Runnable状态意味着该线程可以参加调度,被JVM运行,并不意味着线程会立即执行。

    线程调度与线程控制

    • 线程调度策略
    • 线程的基本控制

    线程调度策略

    • Java中线程调度采用抢先式调度方法。
    • 抢先式调度模式: 许多线程可能是可运行的,但只能有一个线程在运行。该线程将持续运行,直到它自行中止或出现高优先级线程成为可运行的,则该低优先级线程被高优先级线程强占运行。
    • 线程中止的原因可能有多种,如执行Thread.sleep( )调用,或等待访问共享的资源。

    线程的优先级

    • 每个线程都有优先级,有缺省值,可用 SetPriority( )方法改变。
    • 每个优先级有一个等待池:
      table

    线程调度策略

    • JVM先运行高优先级池中的线程,待该池空后才 考虑低优先级线程。
    • 如果有高优先级线程成为可运行的,则运行它。
    • 抢先式可能是分时的,即每个池中的线程轮流运行; 也可能不是,即线程逐个运行,由JVM而定。
    • 线程一般可用sleep( )保证给其他线程运行时间。

    线程的基本控制

    • 结束线程
    • 获取当前线程
    • 测试线程
    • sleep( )
    • join( )
    • yield( )

    结束线程(1)

    • 线程完成运行并结束后,将不能再运行。
    • 除正常运行结束外,还可用其他方法控制使其停止。
      • 用stop( )方法。 强行终止线程,容易造成线程的不一致。
      • 使用标志flag。 通过设置flag 指明run( )方法应该结束。

    结束线程(2)

    class Xyz implements Runnable{
        private boolean  timeToQuit = false;
        public void run( ){
    	while(!timeToQuit){   …  }
     	// clean up before run() ends
    	...
    	}
        public void stopRunning( ){
    	timeToQuit = true ;
        }
    }
    public class ControlThread{
         public void main(String [ ]  args){
    	Runnable r = new Xyz( ) ;
    	Thread t = new Thread( r) ;
    	t.start();
    	r.stopRunning( );
    	} }
    
    

      获取当前线程/测试线程

      • Thread 类的静态方法currentThread( )返回当前线程。
      • 当线程的状态未知时,用isAlive( )确定线程是否活着。返回true 意味着线程已经启动,但还没有运行结束。

      Sleep( )方法

      • 该方法用来使一个线程暂停运行一段固定的时间。在线程睡眠时间内,将运行别的线程。
      • Sleep( ) 结束后,线程将进入Runnable状态。

      join( ) 方法

      • t.join( )方法使当前的线程等待,直到 t 结束为止,线程恢复到runnable状态。
        Public void doTask(){
            TimerThread  tt= new TimerThread(100) ;
            tt.start( ) ;
            …
            // Do stuff in parallel with the other thread for a while 
            …
            // Wait here for the timer thread to finish
            try{
          	tt.join( );
                 } catch( InterruptedException e){  // tt came back early }
           …
           // continue in this thread
           …
        }
        
        

      yield( )方法

      • 调用该方法将CPU让给具有与当前线程相同优先级的线程。
      • 如果没有同等优先级的线程是Runnable状态, yield( )方法将什么也不做。

      线程同步

      • 线程间同步机制

      • 线程间的交互wait( ) 和notify( )

      • 不建议使用的一些方法

      多线程并发执行中的问题

      • 多个线程相对执行的顺序是不确定的。

      • 线程执行顺序的不确定性会产生执行结果的不确定性。

      • 在多线程对共享数据 操作时常常会产生这种不确定性。

      多线程并发执行中的问题

      • 一个堆栈类
        public class MyStack{
            private int idx = 0 ;
            private char[ ] data = new char[6] ;
            public void push( char c ){
          	data[idx] = c ;
        	idx ++ ;   // 栈顶指针指向下一个空单元
        	}
            public char pop( ){
        	idx-- ;
         	return data[idx] ;
        	}
        }
        
        

      多线程并发执行中的问题

      • table

      对象锁的概念

      • Java中每个对象都带有一个monitor标志,相当于一个锁。

      • Sychronized关键字用来给对象加上独占的排它锁。

      对象锁的操作

      • Public void push(char c){
             synchronized(this){
        	data[idx] = c ;
        	idx++ ;
        	}
        }
        
        
        table

      对象锁的操作

      • table

      示例

      • 将MyStack 对栈的push,pop操作都采用这种机制:
        public class MyStack{
            … 
            public void push( char c ){
                 synchronized(this){
          	 data[idx] = c ;
          	 idx ++ ; 	}
                 }
            public char   pop( ){
                 synchronized(this){
        	idx-- ;
         	return data[idx] ;}
        	}
        }
        
        

      多线程对共享数据进行操作

      • table

      几点说明(1)

      • 如何返还对象的monitor
        • 当synchronized( ){ }语句块执行完毕后。
        • 当在synchronized( ){ }语句块中出现exception
        • 当调用该对象的wait( )方法。将该线程放入 对象的wait pool中,等待某事件的发生。
      • 对共享数据的所有访问都必须使用synchronized.
      • 用synchronized保护的共享数据必须是私有的,使线程不能直接访问这些数据,必须通过对象的方法。
      • 如果一个方法的整体都在synchronized块中,则可以把 synchronized关键字放于方法定义的头部:
        public synchronized void push( char c){
        	…
        }
        
        

        几点说明(2)

        public class Reentrant { 
        	public synchronized void a() { 
        		b(); 
        		System.out.println("here I am, in a()"); 
        	} 
        	public synchronized void b() { 
        		System.out.println("here I am, in b()"); 
        	} 
        } 
        
        
        • Java运行系统允许已经拥有某个对象所的线程再次获得该对象的锁——Java locks are reentrant.

        避免死锁

        • 死锁是指两个线程同时等待对方持有的锁。
        • 死锁的避免完全由程序控制。
        • 可以采用的方法:
          • 如果要访问多个共享数据对象,则要从全局考虑定义一个获得封锁的顺序,并在整个程序中都遵守这个顺序。释放锁时,要按加锁的反序释放。

          线程间的交互

          • Wait( ) 和notify( )
            • 线程在synchronized块中调用x.wait( )等待共享数据的某种状态。该线程将放入对象x的wait pool, 并且将释放x的monitor。
            • 线程在改变共享数据的状态后,调用x.notify(),则对象的wait pool中的一个线程将移入lock pool,等待x的monitor, 一旦获得便可以运行。
          • Notifyall( )把对象wait pool中的所有线程都移入lock pool。

          示例--Producer/Consumer(1)

          • Producer线程:每隔300ms产生一个字母压栈theStack , 共200个。
            Public void run( ){
                char c ;
                for(int I=0; I<200; I++){
            	c=(char)(math.random()*26+’A’);
             	theStack.push( c);
            	try{
            		Thread.sleep(300) ;
            	}catch(InterruptedException e){ }
            	}
            }
            
            

          示例--Producer/Consumer(2)

          • Consumer线程:从栈theStack中取200个字符,间隔300ms。
            Public void run( ){
                char c ;
                for(int I=0; I<200; I++){
            	c = theStack.pop( );
            	try{
            	    Thread.sleep(300) ;
            	}catch(InterruptedException e){ }
            	}
            }
            
            
            

          示例--Producer/Consumer(3)

          • 堆栈类SyncStack:
            • 为了保证共享数据一致性,push( ) 与pop( ) 定义成 synchronized;
            • 为了实现Producer 与 Consumer之间的同步,加入 wait( ) 与notify( )
            public class SyncStack{
            	private Vector buffer = new Vector(400,200) ;
            	public synchronized  char  pop( ){ };
            	public synchronized  void  push(char c){ }
            }
            
            
            
            

          示例--Producer/Consumer(4)

          • Pop( )方法:
            public synchronized  char  pop( ){
                char c ;
                while(buffer.size( ) ==0){
                	try{
            		this.wait( );
            	}catch(InterruptedException e){  }
                 }
                c=( (Character)buffer.remover(buffer.size( )-1)).charValue();
                return c ;
            }
            
            
            
            

          示例--Producer/Consumer(5)

          • Push( )方法:
            public synchronized  void  push(char c){
                this.notify( );
                Character charObj = new Character( c );
                buffer.addElement( char Obj);
            } 
            
            
            
            

          不建议使用的方法

          • Stop( )
            • 线程强行终止,容易造成数据的不一致。
          • suspend( ) 和 resume( )
            • 使一个线程A可以通过调用B.suspend( )直接控制B的运行。 Suspend( )方法将不使B释放锁。容易发生死锁。建议使用 wait( )和notify( ).

          线程状态与生命周期

          • table

          线程状态

          • 几种基本状态new, Runnable, Running, Dead, Blocked等。
          • 线程状态由线程控制方法,如sleep( ), join()或线程同步控制方法引起变化。

          线程分组(Grouping Threads)

          • 线程组:
            • 每个Java线程都是归属于一个线程组。线程组机制把多个线程集合为一个对象,从而可以实现对这些线程整体操作。
            • 线程组由java.lang中的ThreadGroup 类实现。
            • 线程属于某个线程组具有永久性。
          • 缺省线程组
            • 在Java Application启动时,运行系统创建缺省的线程组,称为main 。所有未指定组的线程,均属于该组。
            • 未指定组的线程,将属于其“父线程”所在线程组。
          • 创建属于线程组的线程
            • public Thread(ThreadGroup group, Runnable runnable) ;
            • public Thread(ThreadGroup group, String name);
            • public Thread(ThreadGroup group, Runnable runnable, String name) ;

          ThreadGroup 类

          • table
          • ThreadGroup 类的方法:
            • Collection Management Methods:activeCount ,listCurrentThreads
            • Methods that Operate on the Group: getMaxPriority ,getName
            • Methods that Operate on All Threads within a Group:resume ,stop,suspend
            • Access Restriction Methods:checkAccess

          http://www.xinx.sdnu.edu.cn/sfzx/jpsykc/zhx/zhanghx13.html

          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

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

          抵扣说明:

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

          余额充值