黑马程序员_JAVA中的多线程

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------


一:线程简介

(1)、线程概述

    线程是程序运行的基本执行单元。当操作系统在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)来作为这个程序运行的入口点。 

      一个进程中可以有一个或多个线程。进程和进程之间不共享内存,也就是说系统中的进程是在各自独立的内存空间中运行的。而一个进程中的线程可以共享系统分派给这个进程的内存空间。线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈, 

(2)、线程给我们带来的好处

   1. 充分利用CPU资源

   2. 简化编程模型

   3. 简化异步事件的处理

   4. 使GUI更有效率

   5. 节约成本 使用多线程来提高效率

二、Java的线程模型

     建立线程有两种方法:一种是继承Thread类,另一种是实现Runnable接口,并通过Thread和实现Runnable的类来建立线程,其实这两种方法从本质上说是一种方法,即都是通过Thread类来建立线程,并运行run方法的。但它们的最大区别是通过继承Thread类来建立线程,虽然在实现起来更容易,但由于Java不支持多继承,因此,这个线程类如果继承了Thread,就不能再继承其他的类了,因此,Java线程模型提供了通过实现Runnable接口的方法来建立线程,这样线程类可以在必要的时候继承和业务有关的类,而不是Thread类。

(1)用Thread类创建线程

Java中创建线程有两种方法:使用Thread类和使用Runnable接口。在使用Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。Thread类的构造方法被重载了八次,构造方法如下:

public Thread( );
public Thread(Runnable target);        //实现了Runnable接口的类的实例
public Thread(String name); //线程的名子
public Thread(Runnable target, String name); //
public Thread(ThreadGroup group, Runnable target); //ThreadGroup  group当前建立的线程所属的线程组
public Thread(ThreadGroup group, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize); // long stackSize  线程栈的大小

     一个普通的Java类只要从Thread类继承,就可以成为一个线程类。并可通过Thread类的start方法来执行线程代码。虽然Thread类的子类可以直接实例化,但在子类中必须要覆盖Thread类的run方法才能真正运行线程的代码。下面的代码给出了一个使用Thread类建立线程的例子:

package thread;

//用Thread类创建线程

public class ThreadTest1 extends Thread  {

  public void run(){

//得到线程的名字

System.out.println("线成名: "+this.getName());

}

public static void main(String[] args) {

//输出主线程的线程名。

System.out.println("线成: "+Thread.currentThread().getName());

         //建立两个线程

ThreadTest1 tt1= new ThreadTest1();

ThreadTest1 tt2= new ThreadTest1();

//两个线程会在调用start方法时,系统会自动调用run方法

tt1.start();

tt2.start();

}

 

}//结果:

主线成名: main

线成名: Thread-0

线成名: Thread-1

      从上面的输出结果可以看出,第一行输出的main是主线程的名子。后面的Thread-1Thread-2分别是thread1thread2的输出结果。

注意:任何一个Java程序都必须有一个主线程。一般这个主线程的名子为main.只有在程序中建立另外的线程,才能算是真正的多线程程序。也就是说,多线程程序必须拥有一个以上的线程。

(2)使用Runnable接口创建线程

实现Runnable接口的类必须使用Thread类的实例才能创建线程。通过Runnable接口创建线程分为两步:

    1. 将实现Runnable接口的类实例化。

    2. 建立一个Thread对象,并将第一步实例化后的对象作为参数传入Thread类的构造方法。最后通过Thread类的start方法建立线程。

下面的代码演示了如何使用Runnable接口来创建线程:

package thread;

/*Runnable创建线程的步骤

 * 1. 将实现Runnable接口的类实例化。

   2. 建立一个Thread对象,并将第一步实例化后的对象作为参数传入Thread类的构造方法。*/

public class RunThread implements Runnable{

 

//覆盖父类的方法

@Override

public void run() {

// TODO Auto-generated method stub

//输出创建的线程名

System.out.println(Thread.currentThread().getName());

}

public static void main(String[] args) {

//第一步

RunThread run1=new RunThread() ;

RunThread ru12=new RunThread() ;

//第二步

Thread th1=new Thread(run1, "第一个线程 ");

//调用一个参数   然后在通过,setname()方法来来修改线程名

Thread th2=new Thread(run1);

th2.setName("第二个线程");

      //两个线程会在调用start方法时,系统会自动调用run方法

        th1.start();

th2.start();

}

 

}

结果:

第一个线程 

第二个线程

三:线程的生命周期

线程经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中和这四种状态相关的方法。

 

 // 开始线程 

    public void start( );

    public void run( );

    // 挂起和唤醒线程

    public void resume( );     // 不建议使用

    public void suspend( );    // 不建议使用

    public static void sleep(long millis);

    public static void sleep(long millis, int nanos);

 

    // 终止线程

    public void stop( );       // 不建议使用

    public void interrupt( );

 

    // 得到线程状态

    public boolean isAlive( );

    public boolean isInterrupted( );

    public static boolean interrupted( );

 

// join方法

 public void join( ) throws InterruptedException

 

    (1)创建并运行线程

线程在建立后并不马上执行run方法中的代码,而是处于等待状态。线程处于等待状态时,可以通过Thread类的方法来设置线程不各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。

当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。

下面的代码演示了线程的创建、运行和停止三个状态之间的切换,并输出了相应的isAlive返回值。

 

package thread;

//判断线程是否处于运行状态

public class LifeCycleThead extends Thread {

 

public void run(){

int i=100;

for(;i>0;i--){

}

}

public static void main(String[] args) throws InterruptedException {

// TODO Auto-generated method stub

     LifeCycleThead lct=new LifeCycleThead();

     //调用isAlive来判断线程是否处于运行状态

     System.out.println("isAlive:  "+lct.isAlive());

     lct.start();

     System.out.println("isAlive:  "+lct.isAlive());

     lct.join();  // 等线程thread1结束后再继续执行 

     System.out.println("thread1已经结束!");

     System.out.println("isAlive: " + lct.isAlive());

}

 

}

结果:

    isAlive:  false

    isAlive:  true

    thread1已经结束!

    isAlive: false

  (2)挂起和唤醒线程

一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspendsleep.在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态 

 虽然suspendresume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,这两个方法被标识为deprecated过时)标记, 下面的代码演示了sleepsuspendresume三个方法的使用。

package thread;

 

import java.util.Date;

 

public class MyThread extends Thread {

 

//内部类  线程

class SleepThread extends Thread{

public void run(){

try {

sleep(2*1000);

System.out.println("不是");

catch (Exception e) {

// TODO: handle exception

}

}

}

public void run(){

while(true){

System.out.println("nihao "+new Date().getTime());

}

}

/**

 * @param args

 * @throws Exception 

 */

public static void main(String[] args) throws Exception {

// TODO Auto-generated method stub

 

    MyThread thread = new MyThread();

        SleepThread sleepThread = thread.new SleepThread();

        sleepThread.start(); // 开始运行线程sleepThread

        sleepThread.join();  // 使线程sleepThread延迟2秒

        thread.start();

        boolean flag = false;

        while (true)

        {

            sleep(5000);  // 使主线程延迟5秒

            flag = !flag;

            if (flag)

                thread.suspend(); //使主线程挂起

            else

                thread.resume();//使主线程挂起    resume()只能唤醒suspend() 挂起的

        }

}}

    从表面上看,使用sleepsuspend所产生的效果类似,但sleep方法并不等同于suspend.它们之间最大的一个区别是可以在一个线程中通过suspend方法来挂起另外一个线程。使用sleep时要注意,不能在一个线程中来休眠另一个线程。 

(3)终止线程的三种方法

    有三种方法可以使终止线程。

    1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

    2.  使用stop方法强行终止线程(已过时)。

    3.  使用interrupt方法中断线程。

    1. 使用退出标志终止线程

run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,      

   2. 使用stop方法终止线程

    使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用thread.stop()终止线程,但是使用这来终止线程很危险

   3. 使用interrupt方法终止线程

    使用interrupt方法来终端线程可分为两种情况:

    (1)线程处于阻塞状态,如使用了sleep方法。

    (2)使用while(!isInterrupted()){……}来判断线程是否被中断。

四:Synchronized关键字

Synchronized关键字主要使用分三种:同步类方法、同步代码块  

(1)同步类方法

最简单的方法就是使用synchronized关键字来使run方法来解决“脏数据”(在数据库事物中出现)的问题,代码如下:

//同步类方法

public synchronized void run(){

    }    

从上面的代码可以看出,只要在voidpublic之间加上synchronized关键字,就可以使run方法同步,也就是说,对于同一个Java类的对象实例,run方法同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。先看看下面的代码:

sychronized关键字只和一个对象实例绑定

package thread;

//sychronized关键字只和一个对象实例绑定

public class Sync implements Runnable

{

     private Test test;

     public void run()

     {

          test.method();

     }

     public Sync(Test test)

     {

         this.test = test;

     }

     public static void main(String[] args) throws Exception

     {

         Test test1 =  new Test();

         Test test2 =  new Test();

         Sync sync1 = new Sync(test1);

         Sync sync2 = new Sync(test2);

         new Thread(sync1).start();

         new Thread(sync2).start(); 

     }

 }

 

class Test

{     //同步方法

      public synchronized void method()

     {    

     }

}   

     Test类中的method方法是同步的。但上面的代码建立了两个Test类的实例,因此,test1test2method方法是分别执行的。要想让method同步,必须在建立Sync类的实例时向它的构造方法中传入同一个Test类的实例,

使用synchronized关键字时有以下四点需要注意:

    1.  synchronized关键字不能继承。

    2.  在定义接口方法时不能使用synchronized关键字。

    3.  构造方法不能使用synchronized关键字,但可以使用下节要讨论的synchronized         块来进行同步。

    4.  synchronized可以自由放置。

2Synchronized块同步方法

synchronized关键字就是synchronized块。我们不仅可以通过synchronized块来同步一个对象变量。也可以使用synchronized块来同步类中的静态方法和非静态方法。

synchronized块的语法如下:

//同步synchronized块

public void method()

{---

 synchronized(表达式)

    {

 

    }

}

一、非静态类方法的同步

    从《使用Synchronized关键字同步类方法》一文中我们知道使用synchronized关键字来定义方法就会锁定类中所有使用synchronzied关键字定义的静态方法或非静态方法,但这并不好理解。而如果使用synchronized块来达到同样的效果,就不难理解为什么会产生这种效果了。如果想使用synchronized块来锁定类中所有的同步非静态方法,需要使用this做为synchronized块的参数传入synchronized块,代码如下:

通过synchronized块同步非静态方法

package thread;

 

public class SyncBlock {

public void method1{

 // 相当于对method1方法使用synchronized关键字

synchronized (this) {

}

}

 

public void method2{

 // 相当于对method2方法使用synchronized关键字

synchronized (this) {

}

}

 public synchronized void method3()  

 {     }

 

}

    在上面的代码中的method1method2方法中使用了synchronized块。而第017行的method3方法仍然使用synchronized关键字来定义方法。在使用同一个SyncBlock类实例时,这三个方法只要有一个正在执行,其他两个方法就会因未获得同步锁而被阻塞。在使用synchronized块时要想达到和synchronized关键字同样的效果,必须将所有的代码都写在synchronized块中,否则,将无法使当前方法中的所有代码和其他的方法同步。

    

二、静态类方法的同步

    由于在调用静态方法时,对象实例不一定被创建。因此,就不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。代码如下:

通过synchronized块同步静态方法

package thread;

//通过synchronized块同步静态方法

public class StaticSyncBlock{

    public static void method1()

    {   //synchronized块 

        synchronized(StaticSyncBlock.class)  

        {

           System.out.println("synchronized块 ");

        }

    }

   // 通过synchronized块同步静态方法

    public static synchronized void method2()  

    {

     System.out.println("同步静态方法 ");

    }

}  

   

 五、Java多线程技术中所使用的方法

 1run()start()

    这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由Java的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.

 (2)关键字Synchronized

    这个关键字用于保护共享数据,当然前提是要分清哪些数据是共享数据。每个对象都有一个锁标志,当一个线程访问该对象时,被Synchronized修饰的数据将被“上锁”,阻止其他线程访问。当前线程访问完这部分数据后释放锁标志,其他线程就可以访问了(上面已讲过这里就不多余了)

  (3sleep()

    使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不同访问共享数据。注意该方法要捕获异常

    比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。

    总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

  (4)join()

    join()方法使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行。注意该方法也要捕获异常。

   (5)yield()

    它与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。

 (6)wait()notify()notifyAll()

    这三个方法用于协调多个线程对共享数据的存取,所以必须在Synchronized语句块内使用这三个方法。前面说过Synchronized 这个关键字用于保护共享数据,阻止其他线程对共享数据的存取。但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出Synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。

    wait()方法使当前线程暂停执行并释放对象锁标志,让其他线程可以进入Synchronized数据块,当前线程被放入对象等待池中。当调用 notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中的线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。



----------- android培训java培训、java学习型技术博客、期待与您交流! ------------



你可能感兴趣的:(java基础,java,线程,并行处理,多线程,同步)