java基础--8.多线程

来源:互联网 发布:网络大电影计划书 编辑:程序博客网 时间:2024/06/06 01:40

一、基本概念:程序 - 进程 - 线程

1. 程序 - 进程 - 线程

 

2.进程与多线程


 

3. 何时需要多线程

程序需要同时执行两个或多个任务。

程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

需要一些后台运行的程序时。

 

 

二、线程的创建和启动

1. 创建线程的两种方式

(1)区别与联系

         publicclass Thread extends Objectimplements Runnable

1>区别

继承Thread:线程代码存放Thread子类run方法中。

实现Runnable:线程代码存在接口的子类的run方法。

 

2>比较哪个好?实现的方式较好。

①  解决了单继承的局限性。

②  如果多个线程有共享数据的话,建议使用实现方式,同时,共享数据所在的类可以作为Runnable接口的实现类。

 

(2) Thread类的有关方法

构造方法:

Thread():创建新的Thread对象

Thread(String threadname):创建线程并指定线程实例名

Thread(Runnable target):指定创建线程的目标对象,实现Runnable的run方法

Thread(Runnable target, Stringname):创建新的Thread对象


(3)线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程

它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

         守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。Java垃圾回收就是一个典型的守护线程。

若JVM中都是守护线程,当前JVM将退出。

 

2. 继承Thread类

 

 1)  定义子类继承Thread类。

 2)  子类中重写Thread类中的run方法。

 3)  创建Thread子类对象,即创建了线程对象。

 4)  调用线程对象start方法:启动线程,调用run方法

 

/* * 创建一个子线程,完成1-20之间自然数的输出。同样地,主线程执行同样的操作 * 创建多线程的第一种方式:继承java.lang.Thread类 */ //1.创建一个继承Thread的子类,表示为一个线程class SubThread extends Thread{    //2.重写Thread的run方法.方法内实现此子线程要完成的功能    public void run(){             for (int i = 1; i <= 20; i++) {           System.out.println(Thread.currentThread().getName()+":"+i);       }    }} public class TestThread {     public static void main(String[] args) {       //3.创建一个子类对象       SubThread st = new SubThread();       //4.调动现成的Start(),启动此线程;调用相应的run方法       //一个线程只能够执行一次start()       //不能通过Thread实现类对象的run()去启动一个线程       st.start();             for (int i = 1; i <= 20; i++) {           System.out.println(Thread.currentThread().getName()+":"+i);       }    }  }

 

3.实现Runnable接口

1)定义子类,实现Runnable接口。

2)子类中重写Runnable接口中的run方法。

3)通过Thread类含参构造器创建线程对象。

4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中。

5)调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

 

/* * 创建多线程的方式二:通过实现的方式 * * 对比一下继承的方式 vs 实现的方式 * 1.联系:public class Thread implements Runnable * 2.哪个方式好?实现的方式优于继承的方式 *   why?  ① 避免了java单继承的局限性 *         ② 如果多个线程要操作同一份资源(或数据),更适合使用实现的方式 *///1.创建一个实现了Runnable接口的类class PrintNum1 implements Runnable {    //2.实现接口的抽象方法    public void run() {       // 子线程执行的代码       for (int i = 1; i <= 100; i++) {           if (i % 2 == 0) {              System.out.println(Thread.currentThread().getName() + ":" + i);           }       }    }} public class TestThread2 {    public static void main(String[] args) {       //3.创建一个Runnable接口实现类的对象       PrintNum1 p = new PrintNum1();        //要想启动一个多线程,必须调用start()       //4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程       Thread t1 = new Thread(p);       //5.调用start()方法:启动线程并执行run()       t1.start();//启动线程;执行Thread对象生成时构造器形参的对象的run()方法。             //再创建一个线程       Thread t2 = new Thread(p);       t2.start();    }}

 

4.线程的优先级及调度

线程的优先级控制:MAX_PRIORITY(10);   

涉及的方法:

         getPriority():返回线程优先值

         setPriority(intnewPriority) :改变线程的优先级

线程创建时继承父线程的优先级

 

5.使用多线程的优点

提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

提高计算机系统CPU的利用率

改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

 

 

 源代码文件:http://download.csdn.net/detail/qq_26553781/9751501

 

 

三、线程的生命周期

         JDK中用Thread.State枚举表示了线程的几种状态

         要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

 

四、线程的同步机制(重点、难点)            

         解决线程安全问题

1.问题

前提:

         如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题?

解决方式:

         要求一个线程操作共享数据时,只有当其完成操作完成共享数据,其它线程才有机会执行共享数据。

 

2.例题

         模拟火车站售票程序,开启三个窗口售票。TestWindow1.java

 


3.Synchronized的使用方法

 

(1)方式一:同步代码块:

谁是共享数据?

                   synchronized(同步监视器){

            //操作共享数据的代码

         }

         注:

         1.同步监视器:俗称锁,任何一个类的对象都可以才充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁!

         2.使用实现Runnable接口的方式创建多线程的话,同步代码块中的锁,可以考虑是this。如果使用继承Thread类的方式,慎用this!

对于静态方法而言,使用当前类本身充当锁

         3.共享数据:多个线程需要共同操作的变量。   明确哪部分是操作共享数据的代码。


class Window2 implements Runnable {   int ticket = 100;// 共享数据    public void run() {       while (true) {         synchronized (this) {//this表示当前对象,本题中即为w            if (ticket > 0) {                try {                   Thread.currentThread().sleep(10);                }catch(InterruptedException e) {                   // TODO Auto-generated catch block                   e.printStackTrace();                }                System.out.println(Thread.currentThread().getName()                      +"售票,票号为:" + ticket--);            }         }      }   }} public classTestWindow2 {   public static void main(String[] args) {      Window2w = newWindow2();      Threadt1 = newThread(w);      Threadt2 = newThread(w);      Threadt3 = newThread(w);       t1.setName("窗口1");      t2.setName("窗口2");      t3.setName("窗口3");       t1.start();      t2.start();      t3.start();   }}

(2)方式二:同步方法:

         将操作共享数据的方法声明为synchronized

publicsynchronizedvoidshow (String name){

        ….

}

 //操作共享数据的代码

         注:

         1.对于非静态的方法而言,使用同步的话,默认锁为:this。如果使用在继承的方式实现多线程的话,慎用!

         2..对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。 Class clazz = Singleton.class

class Window4 implements Runnable {   int ticket = 100;// 共享数据    public void run() {      while (true) {         show();      }   }      public synchronized void show() {           if (ticket > 0) {         try {            Thread.currentThread().sleep(10);         }catch(InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();         }         System.out.println(Thread.currentThread().getName()+ "售票,票号为:"                +ticket--);      }    }} public classTestWindow4 {   public static void main(String[] args) {      Window4w = newWindow4();      Threadt1 = newThread(w);      Threadt2 = newThread(w);      Threadt3 = newThread(w);       t1.setName("窗口1");      t2.setName("窗口2");      t3.setName("窗口3");       t1.start();      t2.start();      t3.start();   }}


 

4.互斥锁

(1)概述

         在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

         每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

         关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。

         同步的局限性:导致程序的执行效率要降低

         同步方法(非静态的)的锁为this。同步方法(静态的)的锁为当前类本身

 

(2)单例设计模式之懒汉式

//关于懒汉式的线程安全问题:使用同步机制//对于一般的方法内,使用同步代码块,可以考虑使用this。//对于静态方法而言,使用当前类本身充当锁。class Singleton{   private Singleton() {      // TODO Auto-generated constructor stub   }     private static Singleton instance = null;   public static SingletongetInstance(){      if (instance == null) {         synchronized (Singleton.class) {            if (instance != null) {                instance = new Singleton();            }         }          }           return instance;   }} public classTestSingleton {   public static void main(String[] args) {      Singletons1 = Singleton.getInstance();      Singletons2 = Singleton.getInstance();           System.out.println(s1 == s2);   }} 

(3) 释放锁的操作

 

当前线程的同步方法、同步代码块执行结束

当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。

当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。

应尽量避免使用suspend()和resume()来控制线程

 

(4)例题

银行有一个账户。

有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

问题:该程序是否有安全问题,如果有,如何解决?

【提示】

1,明确哪些代码是多线程运行代码,须写入run()方法

2,明确什么是共享数据。

3,明确多线程运行代码中哪些语句是操作共享数据的

拓展问题:可否实现两个储户交替存钱的操作。需要使用线程通信!

 

/** * 银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。 * 1.是否涉及多线程?是。有2个储户(2种方式) * 2.是否共享数据?有,同一个账户 * 3.考虑线程同步(2种方式处理线程安全) */ class Account {   double balance;    //余额    public Account() {      // TODO Auto-generated constructor stub   }     //存钱   public synchronized void deposit(double amt){      balance += amt;      try {         Thread.currentThread().sleep(10);      }catch(InterruptedException e) {         // TODO Auto-generated catch block         e.printStackTrace();      }          System.out.println(Thread.currentThread().getName()+":"+balance);   }} class Customer extends Thread{     Accountaccount;     public Customer(Accountaccount) {      this.account = account;   }   public void run(){      for (int i = 0; i < 3; i++) {         account.deposit(1000);      }   }} public classTestAccount {   public static void main(String[] args) {      Accountaccount = newAccount();      Customercustomer1 = newCustomer(account);      Customercustomer2 = newCustomer(account);           customer1.setName("甲");      customer2.setName("乙");           customer1.start();      customer2.start();   }}

 

(5) 线程的死锁问题

死锁

         不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

解决方法

         专门的算法、原则

         尽量减少同步资源的定义

package thread.dead; //死锁的问题:处理线程同步时容易出现。//不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁//写代码时,要避免死锁!public classTestDeadLock {   static StringBuffer sb1 = new StringBuffer();   static StringBuffer sb2 = new StringBuffer();    public static void main(String[] args) {      new Thread() {         public void run() {            synchronized (sb1) {                try {                   Thread.currentThread().sleep(10);                }catch(InterruptedException e) {                   // TODO Auto-generated catch block                   e.printStackTrace();                }                sb1.append("A");                synchronized (sb2) {                   sb2.append("B");                   System.out.println(sb1);                   System.out.println(sb2);                }            }         }      }.start();       new Thread() {         public void run() {            synchronized (sb2) {                try {                   Thread.currentThread().sleep(10);                }catch(InterruptedException e) {                   // TODO Auto-generated catch block                   e.printStackTrace();                }                sb1.append("C");                synchronized (sb1) {                   sb2.append("D");                   System.out.println(sb1);                   System.out.println(sb2);                }            }         }      }.start();   } }

 

五、线程的通信

1.线程通信

         Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常

(1) wait() 方法

         在当前线程中调用方法:  对象名.wait()

         使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。

         调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

         调用此方法后,当前线程将释放对象监控权  ,然后进入等待

         在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

 

(2) notify()/notifyAll()

         在当前线程中调用方法:  对象名.notify()

         功能:唤醒等待该对象监控权的一个线程。

         调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

 

2. 例 题

使用两个线程打印1-100. 线程1, 线程2 交替打印

 

//线程通信。如下的三个关键字使用的话,都得在同步代码块或同步方法中。//wait():一旦一个线程执行到wait(),就释放当前的锁。//notify()/notifyAll():唤醒wait的一个或所有的线程//使用两个线程打印 1-100. 线程1, 线程2 交替打印 class PrintNum implements Runnable {   int num = 1;   Objectobj= newObject();   public void run() {      while (true) {         synchronized (obj) {            obj.notify();            if (num <= 100) {                try {                   Thread.currentThread().sleep(10);                }catch(InterruptedException e) {                   // TODO Auto-generated catch block                   e.printStackTrace();                }                System.out.println(Thread.currentThread().getName()+ ":"                      +num);                num++;            }else{                break;            }                       try {                obj.wait();            }catch(InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }         }      }   } } public classTestCommunication {   public static void main(String[] args) {      PrintNump = newPrintNum();      Threadt1 = newThread(p);      Threadt2 = newThread(p);           t1.setName("甲");      t2.setName("乙");           t1.start();      t2.start();   }}

 

3. 经典例题:生产者/消费者问题

l  生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

l  这里可能出现两个问题:

Ø  生产者比消费者快时,消费者会漏掉一些数据没有取到。

Ø  消费者比生产者快时,消费者会取相同的数据。

 

/* * 生产者/消费者问题 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下, * 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下, * 如果店中有产品了再通知消费者来取走产品。    分析:   1.是否涉及到多线程的问题?是!生产者、消费者   2.是否涉及到共享数据?有!考虑线程的安全   3.此共享数据是谁?即为产品的数量   4.是否涉及到线程的通信呢?存在这生产者与消费者的通信  */class Clerk{//店员   int product;     public synchronized void addProduct(){//生产产品      if(product >= 20){         try {            wait();         }catch(InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();         }      }else{         product++;         System.out.println(Thread.currentThread().getName()+ ":生产了第" + product + "个产品");         notifyAll();      }   }   public synchronized void consumeProduct(){//消费产品      if(product <= 0){         try {            wait();         }catch(InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();         }      }else{         System.out.println(Thread.currentThread().getName()+ ":消费了第" + product + "个产品");         product--;         notifyAll();      }   }} class Producer implements Runnable{//生产者   Clerkclerk;     public Producer(Clerk clerk){      this.clerk = clerk;   }   public void run(){      System.out.println("生产者开始生产产品");      while(true){         try {            Thread.currentThread().sleep(100);         }catch(InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();         }         clerk.addProduct();              }   }}class Consumer implements Runnable{//消费者   Clerkclerk;   public Consumer(Clerk clerk){      this.clerk = clerk;   }   public void run(){      System.out.println("消费者消费产品");      while(true){         try {            Thread.currentThread().sleep(10);         }catch(InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();         }         clerk.consumeProduct();      }   }}  public classTestProduceConsume {   public static void main(String[] args) {      Clerkclerk = newClerk();      Producerp1 = newProducer(clerk);      Consumerc1 = newConsumer(clerk);      Threadt1 = newThread(p1);//一个生产者的线程      Threadt3 = newThread(p1);      Threadt2 = newThread(c1);//一个消费者的线程           t1.setName("生产者1");      t2.setName("消费者1");      t3.setName("生产者2");           t1.start();      t2.start();      t3.start();   }}

 

 源代码文件:http://download.csdn.net/detail/qq_26553781/9758503

 

 

 

 

0 0
原创粉丝点击