黑马程序员——Java语言基础:多线程

来源:互联网 发布:茶叶销售量数据 编辑:程序博客网 时间:2024/06/11 17:14
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

  

多线程


一、概述

1. 进程:一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径或称为一个控制单元。


2. 线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。

             一个进程中至少有一个线程。

3. 线程的状态:


(1)被创建:等待调用start启动。


(2)运行状态:具有执行资格和执行权。


(3)临时状态(阻塞):有执行资格,但是没有执行权。


(4)冻结状态:遇到  sleep()方法和wait()方法时,失去执行资格和执行权


sleep()方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。


(5)消忙状态:stop()方法,或者run方法结束


4. 多线程:在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中,该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。这种在一个进程中有多个线程执行的方式,就叫做多线程。


※ 当某些代码需要同时被执行时,就用单独的线程进行封装。


5. 多线程存在的意义:多线程的出现能让程序产生同时运行效果,可以提高程序执行效率


例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。


二、创建线程的方式

1. 继承方式:通过继承Thread类,然后复写其run方法的方式来创建线程。


通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。


※创建步骤:


a.定义类继承Thread。


b.复写Thread中的run方法。(目的:将自定义代码存储在run方法中,让线程运行)


c.创建定义类的实例对象。相当于创建一个线程。


d.用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。


 如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。


例:  

/*  小练习  创建两线程,和主线程交替运行。  */        //创建线程Test    class Test extends Thread    {        Test(String name)        {            super(name);        }        //复写run方法        public void run()        {            for(int x=0;x<1000;x++)            System.out.println(Thread.currentThread().getName()+"..run..."+x);        }    }    class  ThreadTest    {        public static void main(String[] args)         {            new Test("one+++").start();//开启一个线程并执行该线程的run方法,如果.run(),线程创建了,但并未运行,仅调用了run方法          new Test("tow———").start();//开启第二线程            //主线程执行的代码            for(int x=0;x<2000;x++)            System.out.println("main run!");        }    }    

  ※ 运行结果每一次都不同,因为多个线程都活去了cpu的执行权,cpu执行到谁,誰就运行

  ※ 明确一点,某一个时刻,只能有一个程序在执行(多核除外)

  ※ cpu在做着快速的切换,以达到看上去是同时运行的效果(可把多线程的运行行为看做抢夺cpu的执行权)

  ※ 多线程的一个特性:随机性

2. 实现方式:实现Runnable接口,并复习其中run方法的方式。

   使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,无法通过Thread类来创建线程,于是出现实现方式

   ※ 创建步骤:

   a. 定义类实现Runnable的接口。

   b. 覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。

   c. 通过Thread类创建线程对象。

   d. 将Runnable接口的子类对象作为实参传递给Thread类的构造方法。

   ※ 为什么要将Runnable接口的子类对象传递给Thread的构造函数?

      自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。

   e. 调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。

   ※ 实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。 

   例:

class TicketDemo implements Runnable//extends Thread{private static int tick  = 100;public void run() {while (true){<span style="white-space:pre"></span>if(tick > 0)//剩余票数大于0,输出线程名及余票数System.out.println(Thread.currentThread().getName()+"...sale:" + tick--);}}}class Ticket{public static void main(String[] args){TicketDemo t = new TicketDemo();//Runnable接口子类的实例对象Thread t1 = new Thread(t);//多个窗口同时买票Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();//启动线程t2.start();t3.start();t4.start();}}

三、多线程的安全问题

1. 导致安全问题的出现的原因:当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完

另一个线程参与进来执行,导致共享数据的错误。

简单的说就两点:

(1)多个线程访问出现延迟

(2)线程的随机性

※ 线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

2. 解决办法——同步(java中对于多线程的安全问题提供了专业的解决方式——synchronized

(1)同步的前提

a. 必须要有两个或者两个以上的线程。

b. 必须是多个线程使用同一个锁。

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

(2)两种解决方式:同步代码块、同步函数(都是利用关键字synchronized来实现)

a.同步代码块

synchronized(对象)

{

需要被同步的代码;

}

※ 同步可以解决安全问题的根本原因就在那个对象上,其中对象如同锁,持有锁的线程可以在同步中执行。

    没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

b.同步函数

格式:在函数上加上synchronized修饰符即可。

※ 同步函数用的是哪一个锁:函数需要被对象调用,那么函数都有一个所属对象引用。

   就是this。所以同步函数使用的锁是this。

例:

/*使用两个线程来卖票,一个在同步代码块中,一个在同步函数中,都在执行卖票动作*/class TicketDemo1 implements Runnable{private static int tick  = 100;Object obj = new Object();boolean flag  = true;public void run() {if(flag){while (true){synchronized(this){if(tick > 0){try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName()+"..code:" + tick--);}}}}elsewhile(true)<span style="white-space:pre"></span>show();}public synchronized void show(){if(tick > 0){try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName()+"...show...:" + tick--);}}}class TicketTest{public static void main(String[] args){TicketDemo1 t = new TicketDemo1();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try{Thread.sleep(10);}catch(Exception e){}t.flag = false;t2.start();}}

(3)同步的利弊:

好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源。

(4)如何寻找多线程中的安全问题

a. 明确哪些代码是多线程运行代码。

b. 明确共享数据。

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

(5)静态函数的同步方式

如果同步函数被静态修饰后,使用的锁不是this,因为静态方法中也不可以定义this。

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。例:类名.class(该对象的类型是Class)

这就是静态函数所使用的锁,而静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class。

例:

/* 加同步的单例设计模式————懒汉式 */  class Single  {      private static Single s = null;      private Single(){}      public static void getInstance()      {          if(s==null)          {              synchronized(Single.class)              {                  if(s==null)                      s = new Single();              }          }          return s;      }  }  

(6)死锁: 当同步中嵌套同步时,就有可能出现死锁现象。

四、多线程间的通信

1. 概念:其实就是多个线程在操作同一个资源,但是操作的动作不同

2. wait()、notify()、notifyAll()语句都是用在同步中,因为要对持有监视器(锁)的线程操作

※ 为什么这些操作线程的方法要定义在Object类中?

因为这些方法在操作同步中线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,即等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object中。

3. 解决安全问题——通过锁

例:

/* 生产者生产商品,供消费者使用 有两个或者多个生产者,生产一次就等待消费一次 有两个或者多个消费者,等待生产者生产一次就消费掉 */  class Resource{private String name;private int count = 1;private boolean flag = false;public synchronized void set(String name){while(flag)try{wait();} catch(Exception e) { }this.name = name+"--"+ count++;System.out.println(Thread.currentThread().getName()+"....生产者"+this.name);flag = true;this.notifyAll();}public synchronized void out(){while(!flag)//多生产者多消费者时用while,但可能发生全部wait(),必须用notifyAll()try{wait();} catch(Exception e){ }System.out.println(Thread.currentThread().getName()+"....消费者............"+this.name);flag = false;this.notifyAll();}}//生产者线程class Producer implements Runnable{private Resource res;Producer(Resource res){this.res = res;}public void run(){while(true){res.set("+商品+");}}}//消费者线程class Consumer implements Runnable{private Resource res;Consumer(Resource res){this.res = res;}public void run(){while(true){res.out();}}}public class ProducerConsumerDemo {public static void main(String[] args) {Resource r = new Resource();Producer pro = new Producer(r);Consumer con = new Consumer(r);Thread t1 = new Thread(pro);Thread t2 = new Thread(con);Thread t3 = new Thread(pro);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}}
※ JDK1.5提供了多线程升级解决方案:Lock操作,ObjectwaitnotifynotifyAll,替换成了Condition对象。

※ 该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

例:(同上例,生产者消费)

import java.util.concurrent.locks.*;    class Resource   {         private String name;      private int count=1;      private boolean flag = false;        //多态      private Lock lock=new ReentrantLock();      //创建两Condition对象,分别来控制等待或唤醒本方和对方线程      Condition condition_pro=lock.newCondition();      Condition condition_con=lock.newCondition();        //p1、p2共享此方法      public void setProducer(String name)throws InterruptedException      {          lock.lock();//锁          try          {              while(flag)//重复判断标识,确认是否生产                  condition_pro.await();//本方等待              this.name=name+"......"+count++;//生产              System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产              flag=true;//控制生产\消费标识              condition_con.signal();//唤醒对方          }          finally          {              lock.unlock();//解锁,这个动作一定执行          }                }        //c1、c2共享此方法      public void getConsumer()throws InterruptedException      {          lock.lock();          try          {              while(!flag)//重复判断标识,确认是否可以消费                  condition_con.await();                System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费              flag=false;//控制生产\消费标识              condition_pro.signal();          }          finally          {              lock.unlock();          }        }  }    //生产者线程  class Producer implements Runnable   {      private Resource res;      Producer(Resource res)      {          this.res=res;      }      //复写run方法      public void run()      {          while(true)          {              try              {                  res.setProducer("商品");              }              catch (InterruptedException e)              {              }          }      }  }    //消费者线程  class Consumer implements Runnable  {      private Resource res;      Consumer(Resource res)      {          this.res=res;      }      //复写run      public void run()      {          while(true)          {              try              {                  res.getConsumer();              }              catch (InterruptedException e)              {              }          }      }    }    class  ProducerConsumer  {      public static void main(String[] args)       {          Resource res=new Resource();            new Thread(new Producer(res)).start();//第一个生产线程 p1          new Thread(new Consumer(res)).start();//第一个消费线程 c1            new Thread(new Producer(res)).start();//第二个生产线程 p2          new Thread(new Consumer(res)).start();//第二个消费线程 c2      }  }  

五、停止线程

1. 线程停止方法:在JDK 1.5版本之前,有stop停止线程的方法,但升级后此方法已过时;

    现在只有一种办法,那就是让run方法结束。

※ 开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

  (设置flag标记)

2. 特殊情况:当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。

   当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。

   这样就可以操作标记让线程结束。Thread类提供该方法interrupt();

例:

class StopThread implements Runnable  {      private boolean flag =true;      public  void run()      {          while(flag)          {              System.out.println(Thread.currentThread().getName()+"....run");          }      }      public void changeFlag()      {          flag = false;      }  }    class  StopThreadDemo  {      public static void main(String[] args)       {          StopThread st = new StopThread();          Thread t1 = new Thread(st);          Thread t2 = new Thread(st);           t1.start();          t2.start();             int num = 0;          while(true)          {              if(num++ == 60)              {                  t1.interrupt();//清除冻结状态                  t2.interrupt();                  st.changeFlag();//改变循环标记                  break;              }              System.out.println(Thread.currentThread().getName()+"......."+num);          }          System.out.println("over");      }  }  
※ join方法

   当A线程执行到了B线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。

   (此时B和其他线程交替运行)join可以用来临时加入线程执行。

※ setPriority()方法用来设置优先级:

    MAX_PRIORITY 最高优先级10

    MIN_PRIORITY   最低优先级1

    NORM_PRIORITY 分配给线程的默认优先级

※ yield()方法可以暂停当前线程,让其他线程执行。









0 0
原创粉丝点击