黑马程序员 多线程

来源:互联网 发布:js函数必须有返回值 编辑:程序博客网 时间:2024/05/21 06:21

------- android培训、java培训、期待与您交流! ----------

1、概述:

多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术,即在同一时刻运行多个程序的能力。通常我们将可以同时执行多个任务的程序称为多线程程序,而把每一个任务都称为线程。多线程可以给其他任务提供机会,由于多线程的本质是CP在多个线程中快速切换,所以在执行效果中就类似于多个程序同时进行。在按顺序执行多个程序程序的时候,若其中有一个比较耗时间的程序时剩余顺序执行的程序将会大大增加等待时间,此时使用多线程技术可以让多个程序同时进行,减小了程序的等待时间。

 

 2、如何创建一个多线程程序呢?

在Java中提供了两种方法。一是构建一个Thread类的子类来定义一个线程,该子类中需要复写Thread类中的run方法。

Class MyThread extends Thread{         Publicvoid run()         {                   //执行语句         }}

然后,构造一个子类的对象,这时候就创建了一个新的线程,再调用该类的start方法,就可以使该线程运行并执行run方法中的内容。

NewMyThread().start();

但是这种方法有很大的弊端,因为在java中是只能单继承的,当一个类已经有一个父类的时候就将不能再继承Thread类了。也就不能使用这种方法来达到开启新的线程。这时,通常推荐使用第二种方法来达到开启新线程的目的。来看一段代码:

Class MyThread implements Runnable{         Publicvoid run()         {                //执行语句         }}

再建立该类的对象,然后后将该类的对象作为参数传进一个新的Thread对象中。最后再令该Thread对象使用其start方法,同样也能达到建立并开启一个新线程的效果。

MyThread mt = newMyThread();new Thread(mt) .start();

在这个方法中,仅仅是将原来的类实现了一个Runnable接口并复写其中run的方法,然后将改变后的类对象传值给Thread类来建立能实现指定run方法的新进程。可以看到在这个方法中相比继承Thread类有一个好处,由于java中是可以多实现的,在实现Runnable接口的同时也不妨碍这个类其实现其他接口,同时这个类还能再继承一个父类。

  • 注意:在这两个方法中,建立了新的线程后必须使用start方法来开启新线程,start方法会开启一个新线程并运行其中的run方法,而直接调用run方法不会开启新线程,只是在主线程中调用了一次run方法而已。

 

3、线程的5种状态

线程可以有以下5种状态:New(新创建),Runnable(运行),Blocked(阻塞),Waiting(等待),Terminated(终止)。其中等待状态又分为等待和计时等待。要确定线程的当前状态,可以使用getState方法。

  • 当一个线程的对象被建立的时候,他就是处于新创建状态,此时该线程中的run方法还没被使用。当这个线程对象使用了start方法的时候,这个线程就进入了运行状态。在线程运行的过程中,其随时可能在特定条件下转换到其他几个状态。
  • 比如当线程未获得执行权的时候他就进入了阻塞状态,而当一个线程试图获得一个内部的对象锁,而该锁在被其他线程持有的时候,则该线程也进入了阻塞状态,而当有其他的线程释放了该锁,并且这个线程再度执行并获取了该锁之后,它才将转变成非阻塞状态。
  • 当线程收到一个等待通知的时候,它就进入了等待状态,在等待状态的线程不会运行任何代码。而在这个状态的线程只能在等待超时或获取了其他让他放弃等待的通知的时候才会重新转换到运行状态或阻塞状态来。一般让线程进入等待的常用方法有sleep(time)和wait()方法前者只能等等待时间结束后返回运行状态,而wait()方法则可以通过notify(),notifyAll()等方法唤醒。

线程被终止的原因:因为run方法执行结束而自然中止,因为一个没有捕获的异常而终止了run方法而意外死亡。在Thread类中有一个interrupt方法也可以用来正常的中断线程。但在这个方法使用中,若线程处于等待状态,该中断状态将被清除,并抛出一个InterruptedException。我们可以捕获该异常并处理后让其正常退出线程。下面是使用interrupt使线程终止的例子:


public class StopThread implements Runnable{    Boolean flag = true ;    public static void main(String args []){        StopThread s = new StopThread();        Thread t = new Thread(s) ;        t.start();        //主线程sleep 1秒后 打断线程t。子线程停止        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        t.interrupt();    }    public synchronized void run(){        while(flag){            try {                wait();            } catch (InterruptedException e) {                //处理异常,将flag设置成false                flag = false ;                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName()+"over");    }}

 

4、线程优先级和守护线程

在Java中的每个线程都有一个优先级。默认情况下,这个线程继承其父线程的优先级。可以使用setPriority方法提高或降低一个线程的优先级。一个线程的优先级一般被定义在MIN_PRIORITY和MAX_PRIORITY之间。当CPU切换线程时,它会优先选择有较高优先级的线程。

在Thread类中有一个静态方法,即Thread.yield(),调用了这个方法的进程将放弃自己的执行权,转而让CPU执行其他线程。

守护线程,可以通过调用Thread对象的setDaemon方法把一个线程转换成守护线程,守护线程的作用就是给其他线程提供服务,当一个程序中只剩下守护线程之后,程序就将退出。

  • 注意:守护线程的设定必须在线程启动之前设定。

 

5、线程同步

当多个线程都修改一个共享数据时,线程之间的切换特性有可能让一个共享数据被同时修改了多次。来看一个例子:

public voidsaleticket()   {         if(ticketnum>0)         {            try            {                Thread.sleep(10);            }            catch (InterruptedException e)            {                e.printStackTrace();            }                          System.out.println(Thread.currentThread().getName()+":"+ticketnum);            ticketnum--;         }        }

在这个例子中线程执行的本意是当ticketnum减小到0的时候就不再运行输出语句,但是在多线程执行的过程中一个线程可能还没有执行到ticketnu--语句的时候就被切换至另一个线程,而当另一个线程运行完ticketnum--切换回该线程继续运行的时候,我们就发现ticketnum在减小到0之前被运行了两次。也就是说ticketnum并没有在减小到0的时候第一时间停止执行。为了解决这个办法,我们只要想办法让一个线程在进入该函数的时候只能等其将整个函数中的语句全部运行结束才能切换另一个线程运行。对于这种需要让一个线程一次性全部运行结束的代码段称为同步代码块。可以在方法前加synchronized修饰。或者把需要同步的语句放入synchronized(?){}代码段中。?中代表现场同步使用的监视器对象,同步非静态方法的监视器是this,而同步静态方法的监视器是当前所在的类的Class对象。只有使用同一个监视器的对象才能实现线程的同步。

上面那段代码可以改为:

public synchronized void saleticket()   {         .... .   }

在JDK5.0中引入了Lock类。其功能类似于synchronized,在使用Lock类之前需要建立Lock对象。然后在同步代码块开始的时候调用该Lock对象的lock方法,在同步代码块结束的时候调用Lock对象的unlock方法。使用方式如下:

Lock mylock = new Lock();public void saleticket()   {      mylock.lock();            .. .. .//同步代码块      mylock.unlock();}

 

6、死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,程序既不会发生任何异常,也不会出现任何提示。

public class DeathThread2 {    public static void main(String[] args) {        MyRun run = new MyRun() ;         Thread t1 = new Thread(run) ;        Thread t2 = new Thread(run) ;        //t1拿到obj的锁,往下执行需要this的锁        t1.start();        try {            Thread.sleep(100);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        run.setFlag();        //t2先拿到this的锁,往下执行需要obj的锁        t2.start();    }}class MyRun implements Runnable{    Object obj = new Object();    Boolean flag = true ;    //run()方法需要两把锁 obj和this。    @Override    public void run() {        if(flag){            synchronized(obj){                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                show() ;            }        }else{            show() ;        }    }    //show方法需要两把锁,this和obj。    synchronized void show(){        System.out.println(Thread.currentThread().getName()) ;        synchronized(obj){            System.out.println(Thread.currentThread().getName()+"show+in") ;        }    }    void setFlag(){        flag = false ;    }}

简单的说来就是当一个同步嵌套另一个同步的时候,一个线程可能挂起在两层同步语句之间,而另一个线程也挂起在同一地方的时候他们就都互相拥有了对方的锁,而且又同时需要对方释放锁。这时就会导致程序造成死锁



0 0
原创粉丝点击