黑马程序员 笔记(十一)——面向对象(多线程)

来源:互联网 发布:ui bootstrap.js 编辑:程序博客网 时间:2024/06/03 13:59

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

多线程

       线程是CPU调度和分派的基本单位,一个进程可以由多个线程组成,而这多个线程共享一个存贮空间。多线程指单个程序可以通过运行多个不同线程来提高程序的处理效率。即多个线程处理同一个任务,就如多个人做同一件事。

  1. 概述:
    1. 线程和进程的认识
      • 进程:是一个正在执行的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
      • 线程:就是进程中一个独立的控制单元。线程在控制着进程的执行。一个程序至少有一个线程。
      • 线程和进程本质上都是系统创建的。
    2. 线程的状态:
      • 被创建:创建Thread类极其子类对象。
      • 运行:正在运行的状态。
      • 冻结:正常(运行)状态通过sleep()和wait()方法。即一个线程放弃执行资格。被唤醒后,此线程要先转换到阻塞状态。
      • 临时阻塞:具备运行资格,但没抢到CPU执行权。
      • 消亡:(1)运行状态通过stop()方法。(2)线程执行结束
  2. 线程的创建:(两种方式)
    1. 通过继承Thread类(一个run()里面的内容只能被一个线程执行。)
      1. 步骤:
        • 定义类继承Thread
        • 复写Thread类中的run()方法。
          • 目的是将自定义代码存贮在run()方法中,让线程运行。因为只有放在run()方法中代码,才能被用作多线程运行。也就是说Thread类中的run()方法中存放的是代码的存放位置。如果调用的是run()方法,则只是方法的调用是单线程。
        • 调用线程的start()方法。该方法两个作用:启动线程,调用run方法。
      2. 示例:
        • class Demo extends Thread//1、继承Thread类{public void run()//复写run()方法,即多线程执行的内容{for(int x=0; x<60; x++)System.out.println("demo run----"+x);}}class ThreadDemo {public static void main(String[] args) {Demo d = new Demo();//创建好一个线程。d.start();//开启线程并执行该线程的run方法。自定义线程执行for(int x=0; x<60; x++)//主函数线程执行的内容System.out.println("Hello World!--"+x);}}
        • 代码说明:首先继承Thread类创建了一个线程类。然后在主函数内创建线程对象,通过调用start()方法开启线程,使之执行。执行过程是首先主函数执行创建线程对象,执行d.start();后主线程继续执行主函数里的内容,但是在d.start()出开启了另外一个线程开始执行。
        • 多线程在运行的时候实际上在任意一个时刻,都只有一个线程在执行,只是CPU在进行快速的切换,我们感觉就像是同时在运行。
        • 通过多次的运行我们可以看出每次的运行结果都不一样,因为多个线程都在获取CPU的执行权,谁抢到谁执行,至于执行多久,不确定。
    2. 通过实现Runnable接口(建议使用此方式建立多线程)
      1. 步骤:
        • 定义一个类实现Runnable接口。
        • 复写Runnable的run()方法。
        • 建立实现Runnable接口类的对象。
        • 将建立的对象通过new Thread(Runnable target)建立线程。
          • 因为自定义的run()方法的所属对象是Runnable接口的子类对象。所以要让线程去执行指定对象的run()方法,就必须明确该run()方法所属对象。
        • 用线程对象调用start()方法,运行该线程。
      2. 示例:
        • /*将买票的小实例,在建立对象的时候通过单例设计模式保证对象的唯一性*/class Ticket implements Runnable//步骤一、实现Runnable接口{private int num = 1000;//设定卖票的数量private static Ticket s = null;//通过单例设计模式保证对象的唯一性private Ticket(){}public static Ticket getTicket(){if (s==null){synchronized(Ticket.class){System.out.println(Thread.currentThread().getName()+"-----");if(s==null)s =new Ticket();}}return s;}public void run()//步骤二、复写run()方法{while(num>0)synchronized(Ticket.class){if(num>0){System.out.println(Thread.currentThread().getName()+"sale..."+num);--num;}}}}class TicketDemo{public static void main(String[] args){Ticket a = Ticket.getTicket();//步骤三、获取实现Runnable接口的对象Thread t1 = new Thread(a);//步骤四、建立线程对象Thread t2 = new Thread(a);Thread t3 = new Thread(a);t1.start();//步骤五、 通过start()方法是线程运行t2.start();t3.start();}}

      3. 实现方式和继承方式的区别
      • 继承方式
        • 线程代码存放Thread子类run方法中。
        • 如果一个类继承了另一个类便不能再继承Thread类,也就不能再运用多线程。
        • 适合多个相同代码的线程去处理同一个资源的情况。
        • 可以避免由于java的单继承特性带来的局限。
    3. 线程中的安全问题
      1. 原因:
        • 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。另一个线程参与进来执行。导致共享数据的错误。
      2. 解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行
    4. 处理语句:
      1. 同步代码块(同步)运用关键字synchronized
        • 同步的前提:
          • 必须要有两个或者两个以上的线程。
          • 必须是多个线程使用同一个锁。
        • 好处:解决了多线程中的安全问题、
        • 缺点:多个线程都要判断锁,消耗了资源。
        • 格式:
          • synchronized(对象){需要同步的代码;}
        • 出现安全问题的代码:
          • class Ticket implements Runnable//extends Thread{private  int tick = 5;public void run(){while(tick>0){try//加上延时,模拟程序在此切换到另外一个线程的状况{Thread.sleep(10);}catch (Exception e){}System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}}class  TicketDemo{public static void main(String[] args) {Ticket t = new Ticket();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();}}/*运行结果================================Thread-0....sale : 5Thread-1....sale : 4Thread-2....sale : 3Thread-3....sale : 2Thread-1....sale : 0Thread-0....sale : 1Thread-2....sale : -1Thread-3....sale : -2================================*/
            代码分析:执行过程和线程创建的第一种方式中一样。只是在本程序中,CPU会在加延时的地方切换到另外的线程中去,使之造成安全问题。为了避免这种结果,我们必须保证,CPU是在把操作共享数据的线程执行完再切换到另一个线程。即把操作共享数据的语句加上同步
          • 优化的部分
            • synchronized (ob){while(tick>0){try{Thread.sleep(10);}catch (Exception e){}System.out.println(Thread.currentThread().getName()+".sale:"+ tick);--tick;}}
            • 代码说明:synchronized中的(ob),是一个任意对象,该对象的作用就如果看门狗,如果在被同步的代码中有线程在执行,则其他的线程即使获取到了执行权,他也不能进入到同步代码块中,必须在同步代码块中的线程出了该区域,其他的线程才能进去执行。即synchronized的作用是保证每次只有一个线程在执行同步代码块中的内容,并且必须执行完。
      2. 同步函数:同步函数就是将修饰符synchronized放在返回类型的前面
        • 使用同步函数的前提:函数的内部必须全部是操作共享数据的语句,才能在函数上加同步。
          • 同步函数示例
          • class Ticket implements Runnable{private  int tick = 1000;public void run(){while(tick>0)show();}public synchronized void show()//同步函数{if(tick>0)System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}class  TicketDemo{public static void main(String[] args) {Ticket t = new Ticket();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();


        • 同步函数的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this
          •  
        • 静态同步函数的锁:静态函数是随类的加载而加载,先于对象建立前。所以静态函数的锁是该类的字节码文件对象   类名.class
      3. 死锁:发生在同步中嵌套同步。
        • 示例:
        • class DeadLock implements Runnable{ Object obj1 = new Object();//两个锁的对象 Object obj2 = new Object();boolean flag=false;//标志位public void run(){if (flag)//当标志位flag 为真时执行下面的代码{while (true){synchronized (obj1){System.out.println(Thread.currentThread().getName());synchronized (obj2){System.out.println(Thread.currentThread().getName()+"The num is ");}}}}else//当标志位flag 为假时执行下面的代码{while (true){synchronized (obj2){System.out.println(Thread.currentThread().getName());synchronized (obj1){System.out.println(Thread.currentThread().getName()+"The num is ");}}}}}}class DeadLockDemo//主函数{public static void main(String[] args) {DeadLock dl = new DeadLock();Thread t1 = new Thread(dl);Thread t2 = new Thread(dl);t1.start();try{Thread.sleep(30);}catch(Exception e){}dl.flag = true;t2.start();}}
          当线程Thread-0在当标志位flag为真时的代码块里的两个锁之间时,线程Thread-1进入了flag为假的代码块里两个锁之间的时候。线程Thread-0必须要获取obj2的锁,但是Thread-1此时在已经获取obj2锁还未释放需要获取obj1锁。两个线程都获取不了所需要的锁,从而形成了死锁。

    5. 线程的操作:
      1. 获取线程名称:
        • 查阅java的API文档可以看到,JAVA在Thread类里面就定义有getName()方法来获取线程名称。该方法通过线程对象调用。返回值是String类型。
        • 通过Thread类中静态方法Thread.currentThread()方法调用getName()方法。
      2. 设置线程名称:
        • 查阅java的API文档可以看到,在Thread类的构造函数中就有Thread(String name),在建立线程的时候给线程命名,我们只需在定义自己的线程类时,在构造函数里通过super调用父类的方法,并且在建立线程对象时传一个字符串就可以完成对线程名的设置。
        • 和获取线程名一样,JAVA在Thread类中定义了setName(String name)的方法,只需用线程对象调用此方法便可完成对线程名称的设置。
    6. 线程之间的通信其实就是多个线程在操作同一个资源,在操作的同时进行线程之间的信息传递。
      1. 等待唤醒机制:多线程的锁机制虽然保证了线程的安全性,但是由于线程的执行是随机的不是固定的。所以不能控制线程的先后执行顺序。比如几个线程操作一个共享数据,不能保证,设置和输出是交替执行的一样。所谓等待唤醒机制,就是当满足什么条件的时候,该线程通过wait()方法,进入线程池。一直要到该线程监视器调用notify()方法将其唤醒。
        1. wait(),notify(),notify(),notifyAll()的使用说明。
          1. 因为要对持有监视器的线程进行操作,只有同步中才具有锁,所以要使用在同步中。
            • 在线程执行wait()命令后,该线程就进入线程池。并且wait()方法会抛出异常。
            • 在执行notify()方法后,唤醒最先进入线程池中的线程。
            • notifyAll()唤醒的是线程池里的所有线程。
            • wait()和notify()方法都需要监视器。
          1. 这些方法定义在Object类中的原因:
            • 因为这些方法在操作同步线程时,都必须要标识他们所操作线程所持有的锁,只有同一个锁上的被等待的线程,可以被同一个锁上的notify()唤醒。不可以对不同锁中的线程进行唤醒,也就是说等待和唤醒必须是同一把锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
          2. wait()和notify()的应用:
            • class Res//定义资源的类{private String name;//定义资源的属性private String sex;boolean flag = false;//定义标志位,用于标志资源里面是否有值public synchronized void setRes(String name,String sex)//定义设置资源属性的属性{if(flag)try{this.wait();}catch(Exception e){}//如果资源里面有数据则等待this.name = name;this.sex = sex;flag = true;//标志位为true,表示资源里面有数据this.notify();//唤醒另一线程}public synchronized void getRes()//定义获取资源里面属性的方式{if(!flag)try{this.wait();}catch(Exception e){}//如果资源里面没有数据则等待System.out.println("name is "+name+"\tsex is "+sex);flag = false;//标志位为false表示,资源里面的数据已被取走this.notify();//唤醒对方线程}}class Input implements Runnable//定义往资源里面输送值的函数{Res res;int x = 0;Input(Res res){this.res = res;}public void run(){while(true){if (x==1)res.setRes("武则天","女");elseres.setRes("Li,Shiming","Man");x = (x+1)%2;}}}class Output implements Runnable//定义从资源里面取值的函数{Res res;Output(Res res){this.res = res;}public void run(){while (true){res.getRes();}}}class  InOutDemo//主函数{public static void main(String[] args) {Res res = new Res();new Thread(new Input(res)).start();//通过匿名的形式开启线程new Thread(new Output(res)).start();}}
              运行结果:name is Li,Shiming       sex is Man
                  name is 武则天   sex is 女
                                  name is Li,Shiming       sex is Man
                                  name is 武则天   sex is 女
          3. wait()和notify()的缺点,因为notify()唤醒的是最早进入线程池的线程,当几个线程同时操作一件事情的时候不能很好的控制唤醒的线程是哪一个。当几个线程同时操作一件事情的时候,只能通过notifyAll()唤醒所有的线程,在判断执行的时候用while循环代替if,使每个线程在被唤醒之后会再次判断标志位。在JDK5.0用显示的锁机制和唤醒机制很好的代替原有的锁机制和唤醒机制,是程序的执行效率更高。
      2. 停止线程:
        1. 控制原理:因为线程运行代码一般都是循环的,只要控制了循环即可。
        2. 控制方法:使用Interrupt(中断)方法。     stop()方法已经过时,不能再用。
        3. 作用: 用来强制清除线程的冻结状态。但是在强制清除后,会抛出Interrupt中断异常。
        4. 控制流程:如果线程在标志位(控制循环的标志位)前进入了冻结状态,那么就不能再次判断标志位也就不能结束线程,当此时调用Interrupt方法即可强制清楚他的冻结状态,使之返回重新判断标志位。最终是线程停止。
        5. 代码示例:
          1. class StopThread implements Runnable{private boolean flag =true;public synchronized void run()//为什么这里不加锁为什么线程就不能进入冻结状态呢?{while(flag){try{wait();}catch (InterruptedException e){System.out.println("中断异常");}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){st.changeFlag();t1.interrupt();t2.interrupt();break;}System.out.println(Thread.currentThread().getName()+"......."+num);}System.out.println("over");}}


      3. 守护线程:
        1. 守护线程和前台线程在运行上没有区别,只是只要前台线程结束了,后台线程(守护线程)自动结束
        2. 设置守护线程的方式:
          1. 在线程启动前设置。
          2. 设置格式:线程名.setDaemon(true);
        3. 示例:
          class StopThread implements Runnable{private boolean flag =true;public synchronized void run()//为什么这里不加锁为什么线程就不能进入冻结状态呢?{while(flag){try{wait();}catch (InterruptedException e){System.out.println("中断异常");}System.out.println(Thread.currentThread().getName()+"....run");}}}class  DaemonDemo{public static void main(String[] args) {StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);t1.setDaemon(true);//将线程t1和t2设置成守护线程,    先线程前设置t2.setDaemon(true);t1.start();t2.start();int num = 0;while(true){if(num++ == 3){break;}System.out.println(Thread.currentThread().getName()+"......."+num);}System.out.println("over");}}/*运行结果=============================main.......1main.......2main.......3over=============================*/
          从线程的运行结果可以看出,线程t1和t2在主线程结束后,也就结束了。
      4. jion方法:
        1. 特点:当A线程执行到B线程的join()方法时,A就会等待,等B线程结束后,A又才会执行。
        2. 使用格式:线程名.join();
        3. joinyon用来临时加入线程执行。
      5. 优先级:
        • 设置优先级的格式:线程名.setPriority(*);     说明:*的取值可以是MAX_PRIORITY、MAX_PRIORITY、MAX_PRIORITY。以及1~10.最好写前面三个。
      6. yield方法:
        • 暂时停止该线程的执行。
        • 格式:Thread.yield();
    7. JDK1.5中对等待唤醒机制的优化:使用前:
      1. 应先导入java.util.concurrent.locks
      2. 具体使用方法:
        1. 首先创建锁的对象——Lock的子类对象(ReentrantLock)。
        2. 利用锁对象的newCondition方法创建“等带和唤醒的监视器”。
        3. 在需要同步的代码的前面通过Lock的子类对象调用lock()方法将其同步。
        4. 在需要的地方通过newCondition()方法创建的监视器调用(await()、signal()、signalAll())方法进行具体的等待唤醒操作。
        5. 在同步语句的后面用同一个对象调用unlock方法解锁。
        6. 注意:在使用await()方法的时候会抛出中断异常(InterruputException)所以在使用的时候要使用try将其处理,并且为了释放锁,则在try后面通过finally语句将释放锁的语句放在其中。
      3. 使用示例
    • import java.util.concurrent.locks.*;//导入该工作所在的类class Goods        //定义商品类{        private String name;//商品的属性        private int num = 0;//商品的编号        boolean flag = false;//定义标志位,如果为真代表已生产商品,等待销售Lock lock = new ReentrantLock();//创建锁的对象Condition con_sell = lock.newCondition();//创建线程等待与唤醒的监视器Condition con_product = lock.newCondition();         Goods(String name)        //构造函数        {                this.name = name;        }                public  void Product()        //商品的生产        {lock.lock();//将需要同步的地方上锁try//因为await()方法会抛出异常,需用try语句检测代码{if(flag)con_product.await();//如果商品生产了则等待System.out.println(Thread.currentThread().getName()+"Product \t"+this.name+"\t编号是"+(++num));flag = true;con_sell.signal();//唤醒con_sell监视器的线程}catch(Exception e){}finally{lock.unlock();//释放锁}        }        public  void Sell()        //商品的销售        {            lock.lock();try{if(!flag)con_sell.await();//如果商品卖出了,则等待System.out.println(Thread.currentThread().getName()+"Sell \t"+this.name+"\t编号是"+num);flag = false;con_product.signal();}catch(Exception e){}finally{lock.unlock();}        }}class Product implements Runnable        //商品生产的类{        Goods g;        Product(Goods g)        {                this.g = g;        }        public void run()        {                while(true)                {                        g.Product();                }        }}class Sell implements Runnable        //商品销售的类{        Goods g;        Sell(Goods g)        {                this.g = g;        }        public void run()        {                while(true)                g.Sell();        }}class ProductSellDemo        //主函数{        public static void main(String[] args)         {                Goods g = new Goods("IPhone");                                new Thread(new Product(g)).start();//生产线程                new Thread(new Product(g)).start();                new Thread(new Sell(g)).start();//销售线程                new Thread(new Sell(g)).start();        }}


原创粉丝点击