首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

java基础->线程

2012-08-30 
java基础----线程一、进程与线程1、Java的线程实现在Java中如果要想进行多线程代码的实现有两种方式:·继承T

java基础---->线程
一、进程与线程1、Java的线程实现

        在Java中如果要想进行多线程代码的实现有两种方式:

                  ·继承Thread类

                  ·实现Runnable接口

1.1、继承Thread类

        当一个类需要按照多线程的方式处理时,可以让这个类直接继承自Thread类即可,而且继承的时候要覆写好Thread类中提供的run()方法:

启动一个线程并不是依靠run()方法而是start()方法。

class MyThread extends Thread {              // 继承Thread类         private String name ;         public MyThread(String name){                   this.name = name ;         }         public void run(){                           // 做为线程的主体                   for(int x=0;x<100;x++){                            System.out.println(this.name+ "运行,x = " + x) ;                   }         }};public classThreadDemo01 {         public static void main(String args[]){                   MyThread mt1 = newMyThread("线程A") ;                   MyThread mt2 = newMyThread("线程B") ;                   MyThread mt3 = newMyThread("线程C") ;                   mt1.start() ;                   mt2.start() ;                   mt3.start() ;         }};

此时通过start()方法执行线程的操作,操作中可以发现,每一个线程间都属于交替的运行状态,即:所有的线程都是交替运行的,且:那个线程抢到了CPU资源,那个线程就执行。

1.2实现Runnable接口

        线程实现的第二种手段,实际上就是可以实现Runnable接口来实现线程的操作类,Runnable接口定义如下:

public interfaceRunnable{         public void run() ;}         class MyThread implements Runnable {           // 实现Runnable接口         private String name ;         public MyThread(String name){                   this.name = name ;         }         public void run(){                           // 做为线程的主体                   for(int x=0;x<10;x++){                            System.out.println(this.name+ "运行,x = " + x) ;                   }         }};

线程确实已经实现了,但是需要注意的是,如果要想启动一个线程肯定是Thread类中的start()方法完成,观察Thread类中提供的构造方法:public Thread(Runnable target)通过构造发现,Thread类可以接收Runnable子类的对象,所以一切的线程都可以通过Thread类进行启动。

public class ThreadDemo02 {         public static void main(String args[]){                   MyThread mt1 = newMyThread("线程A") ;                   MyThread mt2 = newMyThread("线程B") ;                   MyThread mt3 = newMyThread("线程C") ;                   new Thread(mt1).start() ;                   new Thread(mt1).start() ;                   new Thread(mt2).start() ;                   new Thread(mt3).start() ;         }};

 此时,通过Thread类进行了线程的启动。

1.3、两种实现方式的区别

        对于Thread类和Runnable接口本身都是可以进行多线程的实现,那么两者到底该使用谁更好呢?

                   1、  继承局限:使用Runnable接口可以避免单继承的局限,而Thread类则有此局限;

                   2、  资源共享:使用Runnable接口实现多线程,可以实现资源(对象属性)的共享,而Thread类却无法实现。

                            |-此点只是相对而言,因为两者的此种区别是有其应用范围的。

范例:观察资源共享

class MyThreadextends Thread {         private int count = 5 ;         public void run(){                   for(int x=0;x<50;x++){                            if(this.count>0){                                     System.out.println("count= " + this.count--) ;                            }                   }         }};public classThreadDemo03 {         public static void main(String args[]){                   new MyThread().start() ;                   new MyThread().start() ;                   new MyThread().start() ;         }};

现在的程序中每一个线程都各自占有各自的count属性,所以并没有达到资源共享的目的,那么如果现在换成了Runnable呢?

class MyThreadimplements Runnable {         private int count = 5 ;         public void run(){                   for(int x=0;x<50;x++){                            if(this.count>0){                                     System.out.println("count= " + this.count--) ;                            }                   }         }};public classThreadDemo04 {         public static void main(String args[]){                   MyThreadmt = new MyThread() ;                   new Thread(mt).start() ;                   new Thread(mt).start() ;                   new Thread(mt).start() ;         }};

现在的代码中可以发现,count属性已经被所有的线程对象所共同拥有了。

class MyThreadimplements Runnable {         private int count = 5 ;         public void run(){                   for(int x=0;x<50;x++){                            if(this.count>0){                                     System.out.println("count= " + this.count--) ;                            }                   }         }};public classThreadDemo04 {         public static void main(String args[]){                   MyThreadmt1= new MyThread() ;             MyThread mt2 = new MyThread() ;             MyThread mt3 = new MyThread() ;                   new Thread(mt1).start() ;                   new Thread(mt2).start() ;                   new Thread(mt3).start() ;         }};

现在的程序中每一个线程都各自占有各自的count属性,

1.4、两种实现方式的联系

        从Thread类和Runnable接口中都可以发现,都必须同时覆写run()方法,那么两者的关系如何呢?观察Thread类的定义:

public class Threadextends Object implements Runnable

        发现Thread类实际上是Runnable的子类。而且Thread类也要去接收Runnable其他子类的对象,而且所有的线程中,通过Runnable接口实现的线程类里面都是编写的具体功能,而并没有所谓的CPU调度,而真正意义上的CPU调度由操作系统完成(通过Thread类的start()方法调用的)。

         Thread类要去协调操作系统,并且最终还要执行具体的线程主体的方法,而线程的主体呢,现在只专著于具体的功能实现,至于如何调度根本不管。

         Thread代理自定义的线程类的对象,如图所示:java基础->线程

从图的关系上可以清楚的发现,现在在线程中应用的设计思路就是代理设计模式(USB)。

1.5、线程的状态

        每一个线程对象都要经历五个步骤:

                   1、  初始化:当创建了一个新的线程对象时

                   2、  等待:调用了start()方法

                   3、  执行:调用run()执行的操作的过程

                   4、  停止:因为所有的线程都需要进行CPU资源的抢占,那么当一个线程执行完部分代码要交出资源,留给其他线程继续执行。

                   5、  卸载:所有的线程的操作代码都执行完毕之后,就将线程对象卸载下来。

二、线程的操作方法2.1、命名和取得

        每一个线程实际上都可以为其设置名字,而且也可以取得每一个线程的名字:

                  ·设置线程名称:public final void setName(String name)

                  ·取得线程名称:public final String getName()

        但是有一点也非常的麻烦,由于线程的操作不属于固定的状态,所以对于取得线程名称的操作,应该是取得的当前正在运行的线程名称才合适,那么可以通过如下的方法取得一个当前正在运行的线程对象:

                  ·取得当前线程:public static Thread currentThread()

        除了以上的设置名称的方法外,在Thread类中也提供了两个构造方法:

                  ·public Thread(String name)

                  ·public Thread(Runnable target,String name)

        注意:一般都在线程启动前设置好名字,当然也可以为已经启动的线程修改名字或设置重名线程,不过这样不好。

class MyThreadimplements Runnable {         public void run(){                   for(int x=0;x<5;x++){                            System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ;                   }         }};public class NameDemo{         public static void main(String args[]){                   MyThread mt = new MyThread();                   new Thread(mt).start() ;                   new Thread(mt,"线程A").start() ;                   new Thread(mt).start() ;                   new Thread(mt).start() ;                   new Thread(mt).start() ;         }};

本程序明白之后,下面再观察以下的程序输出:

class MyThreadimplements Runnable {         public void run(){                   for(int x=0;x<5;x++){                            System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ;                   }         }};public classNameDemo {         public static void main(String args[]){                   MyThread mt = new MyThread() ;                   new Thread(mt,"自定义线程").start() ;                   mt.run() ;    // main线程         }};

 结果:     main运行,x = 0

自定义线程运行,x = 0

main运行,x = 1

自定义线程运行,x = 1

main运行,x = 2

自定义线程运行,x = 2

main运行,x = 3

自定义线程运行,x = 3

main运行,x = 4

自定义线程运行,x = 4

在本程序中发现出现了一个main线程,那么这个线程肯定是主方法产生的,之前一直强调,java本身是属于多线程的处理机制,所以每次java运行的时候,实际上都会启动一个JVM的进程。

        那么既然是多线程的处理机制,实际上主方法是在一个JVM上产生的一个线程而已,那么一个JVM启动的时候至少启动几个线程呢?两个:main、GC。

2.2、线程的休眠

        所谓的休眠就是指减缓程序的运行速度,如果要休眠使用如下的方法:

                  ·休眠:public static void sleep(long millis) throwsInterruptedException,指定休眠时间

class MyThreadimplements Runnable {         public void run(){                   for(int x=0;x<5;x++){                            System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ;                            try{                                     Thread.sleep(300);                            }catch (Exception e){}                   }         }};public classSleepDemo {         public static void main(String args[]){                   MyThread mt = new MyThread();                   new Thread(mt,"自定义线程").start() ;                   mt.run() ;    // main线程         }};

2.3、线程的优先级

        实际上所有的线程启动之后并不是立刻运行的,都需要等待CPU进行调度,但是调度的时候本身也是存在“优先”级的,如果优先级高则有可能最先被执行。

        如果要想设置优先级可以使用:publicfinal void setPriority(int newPriority)

        这个优先级需要接收一个整型的数字,这个数字只能设置三个内容:

                  ·最高优先级:public static final int MAX_PRIORITY

                  ·中等优先级:public static final int NORM_PRIORITY

                  ·最低优先级:public static final int MIN_PRIORITY

范例:观察优先级设置的影响

class MyThreadimplements Runnable {         public void run(){                   for(int x=0;x<5;x++){                            System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ;                   }         }};public classProDemo {         public static void main(String args[]){                   MyThread mt = new MyThread();                   Thread t1 = new Thread(mt) ;                   Thread t2 = new Thread(mt) ;                   Thread t3 = new Thread(mt) ;                   t1.setPriority(Thread.MIN_PRIORITY);                   t1.start() ;                   t2.setPriority(Thread.MAX_PRIORITY);                   t2.start() ;                   t3.setPriority(Thread.NORM_PRIORITY);                   t3.start() ;         }};

问题:主方法的优先级是什么?

public classMainDemo {         public static void main(String args[]){                   System.out.println(Thread.currentThread().getPriority());   //5                   System.out.println("MAX_PRIORITY" + Thread.MAX_PRIORITY) ;//10                   System.out.println("MIN_PRIORITY" + Thread.MIN_PRIORITY) ;//1                   System.out.println("NORM_PRIORITY" + Thread.NORM_PRIORITY) ;//5         }};

主方法属于中等优先级。

三、线程的同步与死锁

        由于多个线程可以对同一个资源进行操作,那么就必须进行同步的处理,但是如果过多的引入了同步的处理,也可能造成死锁。

3.1、同步

        当多个线程同时进行一种资源操作,为了保证操作的完整性,引入了同步处理。

class MyThreadimplements Runnable {         private int ticket = 10 ;         public void run(){                   for(int x=0;x<50;x++){                            if(this.ticket >0){                                     System.out.println(Thread.currentThread().getName()                                               +"卖票,剩余:" + this.ticket--) ;                            }                   }         }};public classSynDemo01 {         public static void main(String args[]){                   MyThread mt = new MyThread();                   new Thread(mt,"票贩子A").start() ;                   new Thread(mt,"票贩子B").start() ;                   new Thread(mt,"票贩子C").start() ;         }};

但是,从实际的操作来看,都是使用网络卖票,既然是网络卖票的话,则有可能出现延迟。

class MyThreadimplements Runnable {         private int ticket = 10 ;         public void run(){                   for(int x=0;x<50;x++){                            if(this.ticket >0){                                     try{                                              Thread.sleep(300) ;                                     } catch(Exception e){}                                     System.out.println(Thread.currentThread().getName()                                               +"卖票,剩余:" + this.ticket--) ;                            }                   }         }};public classSynDemo01 {         public static void main(String args[]){                   MyThread mt = new MyThread();                   new Thread(mt,"票贩子A").start() ;                   new Thread(mt,"票贩子B").start() ;                   new Thread(mt,"票贩子C").start() ;         }};

本程序中可以发现一旦加入了延迟(不加延迟也可能有问题)之后发现有的人卖出的票数是负数。

        由于现在是有多个操作,那么最好的解决方法是加入一个锁的标记,锁的标记中将判断和修改同时进行,要想完成这种锁的程序可以通过两种语句实现:同步代码块、同步方法。

范例:使用同步代码块完成

class MyThreadimplements Runnable {         private int ticket = 10 ;         public void run(){                   for(int x=0;x<50;x++){                            synchronized(this){      // 将当前对象锁定                                     if(this.ticket> 0){                                               try{                                                        Thread.sleep(300);                                               }catch (Exception e){}                                               System.out.println(Thread.currentThread().getName()                                                        +"卖票,剩余:" + this.ticket--) ;                                     }                            }                   }         }};public classSynDemo02 {         public static void main(String args[]){                   MyThread mt = new MyThread();          //以下的三个线程都是用同一个实现了Rrunable接口类的对象mt创建的,thread 会调用mt的run方法,所以把它锁住                   new Thread(mt,"票贩子A").start() ;                   new Thread(mt,"票贩子B").start() ;                   new Thread(mt,"票贩子C").start() ;         }};

程序中加入了同步操作之后可以发现执行的速度变慢了。

        除了可以使用同步代码块之外还可以使用同步方法完成以上的操作。

class MyThreadimplements Runnable {         private int ticket = 10 ;         public void run(){                   for(int x=0;x<50;x++){                            this.sale() ;                   }         }         public synchronized void sale() {                   if(this.ticket > 0){                            try{                                     Thread.sleep(300);                            } catch (Exceptione){}                            System.out.println(Thread.currentThread().getName()                                     + "卖票,剩余:" + this.ticket--) ;                   }         }};public classSynDemo03 {         public static void main(String args[]){                   MyThread mt = new MyThread();                   new Thread(mt,"票贩子A").start() ;                   new Thread(mt,"票贩子B").start() ;                   new Thread(mt,"票贩子C").start() ;         }};

现在就可以给出一个方法的完整定义格式

         [public | protected | private ][static] [final] [synchronized]

        返回值类型方法名称(参数列表) [throws异常1,异常2,…]{

                   [return返回值 ;]

}

四、线程操作的经典案例——生产者和消费者

        以上就是多线程的基本操作,但是在整个多线程存在一个经典的交互案例,生产者和消费者。先通过代码观察问题,现在假设说要生产的是一组信息,此组信息有两种选项:

                  · oracle       数据库

                  · java      www.sun.com.cn

        既然要生产信息,则肯定要建立一个信息的保存对象。

class Info {         private String name ="oracle" ;         private String desc = "数据库" ;         public void setName(String name){                   this.name = name ;         }         public void setDesc(String desc){                   this.desc = desc ;         }         public String getName(){                   return this.name ;         }         public String getDesc(){                   return this.desc ;         }};

下面建立生产者和消费者,同时为了保证两者之间存在共同的生产线,所以两个类分别都占有同一个Info的引用。

class Proimplements Runnable {         private Info info ;         public Pro(Info info){                   this.info = info ;         }         public void run(){                   for(int x=0;x<50;x++){                            if(x%2==0){                                     this.info.setName("Java");                                     try{                                               Thread.sleep(300);                                     } catch(Exception e){}                                     this.info.setDesc("www.sun.com");                            } else {                                     this.info.setName("Oracle");                                     try{                                               Thread.sleep(300);                                     } catch(Exception e){}                                     this.info.setDesc("数据库") ;                                      }                   }         }};class Cusimplements Runnable {         private Info info ;         public Cus(Info info){                   this.info = info ;         }         public void run(){                   for(int x=0;x<50;x++){                            try{                                     Thread.sleep(300);                            } catch (Exceptione){}                            System.out.println(this.info.getName()+ " --> " + this.info.getDesc()) ;                   }         }};public classCommDemo {         public static void main(String args[]){                   Info info = new Info() ;                   Pro p = new Pro(info) ;                   Cus c = new Cus(info) ;                   new Thread(p).start() ;                   new Thread(c).start() ;         }}

通过以上的操作代码可以发现,现在的程序有两点问题:

                   1、  数据的设置错误

                   2、  出现了重复取或重复设置的问题。

4.1、解决错误数据

        需要使用同步的方法来解决数据设置错误问题,可以通过以下的修改完成。

class Info {         private String name ="oracle" ;         private String desc = "数据库" ;         public synchronized void set(Stringname,String desc){                   this.setName(name) ;                   try{                            Thread.sleep(300) ;                   } catch (Exception e){}                   this.setDesc(desc) ;         }         public synchronized void get(){                   try{                            Thread.sleep(300) ;                   } catch (Exception e){}                   System.out.println(this.name+ " --> " + this.desc) ;         }         public void setName(String name){                   this.name = name ;         }         public void setDesc(String desc){                   this.desc = desc ;         }         public String getName(){                   return this.name ;         }         public String getDesc(){                   return this.desc ;         }};class Proimplements Runnable {         private Info info ;         public Pro(Info info){                   this.info = info ;         }         public void run(){                   for(int x=0;x<50;x++){                            if(x%2==0){                                     this.info.set("Java","www.sun.com");                            } else {                                     this.info.set("Oracle","数据库") ;                            }                   }         }};class Cus implementsRunnable {         private Info info ;         public Cus(Info info){                   this.info = info ;         }         public void run(){                   for(int x=0;x<50;x++){                            this.info.get() ;                   }         }};public classCommDemo {         public static void main(String args[]){                   Info info = new Info() ;                   Pro p = new Pro(info) ;                   Cus c = new Cus(info) ;                   new Thread(p).start() ;                   new Thread(c).start() ;         }};

 现在的程序代码之中,已经增加了同步操作。

        现在虽然避免了错误数据的问题,但是重复取和重复设置的问题依然存在。依靠信号量+Object中唤醒等待方法解决

4.2、Object类对线程的支持

        在Object类中提供了以下的方法可以实现对线程的等待及唤醒的处理:

                  ·等待:public final void wait() throws InterruptedException

                  ·唤醒:public final void notify(),唤醒第一个等待的线程

                  ·唤醒:public final void notifyAll(),唤醒全部等待的线程

下面就可以利用这种机制观察对线程的处理操作,解决重复读取和设置的问题,修改Info类即可。

class Info {         private String name ="oracle" ;         private String desc = "数据库" ;         private boolean flag = false ;  // 定义一个标记         /*                   flag = true,表示可以生产,但是不能取得                   flag = false,表示可以取得,但是不能生产         */         public synchronized void set(Stringname,String desc){                   if(!this.flag){                            try{                                     super.wait();                            }catch(Exceptione){}                   }                   this.setName(name) ;                   try{                            Thread.sleep(300);                   } catch (Exception e){}                   this.setDesc(desc) ;                   this.flag = false ;                   super.notify() ;     // 唤醒         }         public synchronized void get(){                   if(this.flag){                            try{                                     super.wait();                            }catch(Exceptione){}                   }                   try{                            Thread.sleep(300) ;                   } catch (Exception e){}                   System.out.println(this.name+ " --> " + this.desc) ;                   this.flag = true ;                   super.notify() ;         }         public void setName(String name){                   this.name = name ;         }         public void setDesc(String desc){                   this.desc = desc ;         }         public String getName(){                   return this.name ;         }         public String getDesc(){                   return this.desc ;         }};


 

热点排行