zoukankan      html  css  js  c++  java
  • 黑马程序员——Java基础---多线程

    一、多线程概述:

         要了解多线程,就必须知道什么是线程。而要知道什么是线程就必须知道什么是进程。

    1、进程:

        进程是一个正在执行中的程序(几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序)。

        每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

    2、线程(Thread):

        线程是进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程至少有一个线程。

    3、多线程:

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

    提示:归纳起来可以这样说:操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。

    4、多线程的优势:

         在实际应用中,多线程是非常有用的,一个浏览器必须能同时下载多个图片;一个Web服务器必须能同时响应多个用户请求;Java虚拟机本身就在后台提供了一个超级线程来进行垃圾回收; 总之,多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。

    二、创建线程的方式:

        Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例(继承Thread类创建线程类)或是实现Runnable接口创建线程类。

    1、继承Thread类创建线程类:

        通过继承Thread类来创建并启动多线程的步骤如下:

    (1)、定义Thread的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。

    (2)、创建Thread子类的实例,即创建了线程对象。

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

    下面程序示范了通过继承Thread类来创建并启动多线程。

    // 通过继承Thread类来创建线程类

    复制代码
     1 // 通过继承Thread类来创建线程类
     2 public class FirstThread extends Thread
     3 {
     4     private int i ;
     5     // 重写run方法,run方法的方法体就是线程执行体
     6     public void run()
     7     {
     8         for ( ; i < 100 ; i++ )
     9         {
    10             // 当线程类继承Thread类时,直接使用this即可获取当前线程
    11             // Thread对象的getName()返回当前该线程的名字
    12             // 因此可以直接调用getName()方法返回当前线程的名
    13             System.out.println(getName() +  " " + i);
    14         }
    15     }
    16     public static void main(String[] args) 
    17     {
    18         for (int i = 0; i < 100;  i++)
    19         {
    20             // 调用Thread的currentThread方法获取当前线程
    21             System.out.println(Thread.currentThread().getName()
    22                 +  " " + i);
    23             if (i == 20)
    24             {
    25                 // 创建、并启动第一条线程
    26                 new FirstThread().start();
    27                 // 创建、并启动第二条线程
    28                 new FirstThread().start();
    29             }
    30         }
    31     }
    32 }
    复制代码

    2、实现Runnable接口创建线程类:

    实现Runnable接口来创建并启动多线程的步骤如下:

    (1)、定义类实现Runnable的接口。

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

    (3)、通过Thread类创建线程对象。

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

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

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

    (5)、调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。

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

    程序示例:

     

    复制代码
     1 package I10;
     2 /* 
     3 需求:简单的卖票程序。 
     4 多个窗口卖票。 
     5 */  
     6 
     7 public class TicketTest1 implements Runnable
     8 {
     9     private int count=100;
    10     public void run()
    11     {        
    12        while(true)
    13        {           
    14           if(count>0)            
    15               {
    16                    //显示线程名及余票数  
    17                 System.out.println(Thread.currentThread().getName()+"----"+count--);
    18               }
    19         }
    20     }
    21 }
    22 class Ticked1
    23 {
    24     public static void main(String[] args) 
    25     {
    26 
    27          //创建Runnable接口子类的实例对象  
    28         TicketTest1 t1=new TicketTest1();
    29         //有多个窗口在同时卖票,这里用三个线程表示 
    30         Thread thread1=new Thread(t1);
    31         Thread thread2=new Thread(t1);
    32         Thread thread3=new Thread(t1);
    33         thread1.start();//启动线程
    34         thread2.start();
    35         thread3.start();
    36      }
    37 }
    复制代码

     

    三、两种方式的区别和线程的几种状态

    1、两种创建方式的区别

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

            实现Runnable:线程代码存放在接口子类run方法中,实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。。     

    2、几种状态

           被创建:等待启动,调用start启动---该线程处于就绪状态即等待执行。

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

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

            冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

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

    注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。

     

    四、线程安全问题

    1、导致安全问题的出现的原因:

            当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。

    简单的说就两点:

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

            (2)、线程随机性

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

    2、解决办法——同步

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

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

            这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。

             (1)、同步代码块

            用法:

                     synchronized(对象)

                      {需要被同步的代码}

            同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

    实例:

     

    复制代码
     1 package I10;
     2 /* 
     3 需求:简单的卖票程序。 
     4 多个窗口卖票。 
     5 */  
     6 
     7 public class TicketTest1 implements Runnable
     8 {
     9     private int count=100;
    10     Object obj=new Object();
    11     public void run()
    12     {        
    13        while(true)
    14        {        
    15          //给程序加同步,即锁  
    16            synchronized (obj)
    17            {
    18              if(count>0)
    19                {
    20                  try {                   
    21                      //使用线程中的sleep方法,模拟线程出现的安全问题  因为sleep方法有异常声明,所以这里要对其进行处理  
    22                        Thread.sleep(1000);
    23                       } 
    24                  catch (InterruptedException e) 
    25                     {                 
    26                        e.printStackTrace();
    27                     }
    28                }
    29            }
    30        }
    31     }
    32 }
    33 class Ticked1
    34 {
    35     public static void main(String[] args) 
    36     {
    37 
    38          //创建Runnable接口子类的实例对象  
    39         TicketTest1 t1=new TicketTest1();
    40         //有多个窗口在同时卖票,这里用三个线程表示 
    41         Thread thread1=new Thread(t1);
    42         Thread thread2=new Thread(t1);
    43         Thread thread3=new Thread(t1);
    44         thread1.start();//启动线程
    45         thread2.start();
    46         thread3.start();
    47      }
    48 }
    复制代码

     

      (2),同步函数

           格式:

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

    那么同步函数用的是哪一个锁呢?

            函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。

    拿同步代码块的示例:

     

    复制代码
     1 package I10;
     2 
     3 
     4 
     5 public class TicketTest implements Runnable {
     6     private int count=100;
     7     public void run()
     8     {
     9         while(true)
    10         {
    11             show();
    12         }
    13     }
    14             public synchronized void show()
    15             {
    16                 if(count>0)
    17                   {
    18                         try {
    19                             Thread.sleep(100);
    20                         } catch (InterruptedException e) {
    21                             // TODO Auto-generated catch block
    22                             e.printStackTrace();
    23                         }
    24                         System.out.println(Thread.currentThread().getName()+"----"+count--);
    25                   }
    26             }
    27         }
    28 class Ticked{
    29 public static void main(String[] args) {
    30     TicketTest t1=new TicketTest();
    31     Thread thread1=new Thread(t1);
    32     Thread thread2=new Thread(t1);
    33     Thread thread3=new Thread(t1);
    34     thread1.start();
    35     thread2.start();
    36     thread3.start();
    37 }
    38 }
    复制代码

     

    3、同步的前提

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

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

    4、同步的利弊

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

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

    5、如何寻找多线程中的安全问题

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

            b,明确共享数据。

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

     

    五、静态函数的同步方式

           如果同步函数被静态修饰后,使用的锁是什么呢?

         通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:

         类名.class 该对象的类型是Class

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

    单例模式:懒汉式

     

    复制代码
     1 package I10;
     2 /**
     3  * 加同步的单例设计模式----懒汉式
     4  * 
     5  */
     6 public class Single 
     7 {
     8     private static Single s=null;
     9     private Single(){}
    10     public static Single getInstance()
    11     {
    12     if(s==null)
    13       {
    14         synchronized(Single.class)
    15           {
    16              if(s==null)
    17              s=new Single();
    18            }
    19        }
    20     return s;
    21     }
    22 }
    复制代码

    六、线程间通信

            其实就是多个线程在操作同一个资源,但是操作的动作不同。

    1、使用同步操作同一资源的示例:

     

    复制代码
    package I10;
    /* 
        有一个资源 
    一个线程往里存东西,如果里边没有的话 
    一个线程往里取东西,如果里面有得话 
    */  
    //资源
    class Res{
        private String name;
        private String sex;
        boolean flag=false;//标记
        //同步函数
        public synchronized void set(String name,String sex){
            //如果有资源等待资源取出
            if(flag)
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                this.name=name;
                this.sex=sex;
                flag=true;//表示有资源
                this.notify();//唤醒等待
            }
            public synchronized void out(){
            //如果没有资源,等待存入资源  
                if(!flag)
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    //这里用打印表示取出  
                System.out.println(name+"--"+sex);
                flag=false;//资源已取出
                this.notify();//等待唤醒
            }
        }
    //存资源
    class Input implements Runnable{
        private Res r;
         Input(Res r) {
            this.r=r;
        }
         //重写run()方法
        @Override
        public void run() {
            // TODO Auto-generated method stub
            int x=0;
            while(true)
            {                
                    if(x==0){
                        r.set("小四", "男");
                    }
                    else {
                        r.set("女侠", "女");
                    }
                    x=(x+1)%2;//控制交替打印
                }    
            }
        }
    //取资源
    public class Output implements Runnable{
        private Res r;
        Output(Res r){
            this.r=r;
        }
        @Override
        public void run() {
            while(true){
                r.out();
                    }
                }
            }
    class OutInt2{
        public static void main(String[] args) {
            Res r=new Res();//表示操作的是同一个资源  
            new Thread(new Input(r)).start();//开启存线程  
            new Thread(new Output(r)).start();//开启取线程  
        }
    }
    复制代码

    几个小问题:

            1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

                    a,这些方法存在与同步中。

                    b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

                    c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

            2)wait(),sleep()有什么区别?

                  wait():释放cpu执行权,释放锁。

                  sleep():释放cpu执行权,不释放锁。

            3)为甚么要定义notifyAll?

            因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

    2、JDK1.5中提供了多线程升级解决方案。

            将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

    升级解决方案的示例:

     

    复制代码
     1 package I10;
     2 
     3 import java.util.concurrent.locks.Condition;
     4 import java.util.concurrent.locks.Lock;
     5 import java.util.concurrent.locks.ReentrantLock;
     6 
     7 class Resource2{
     8     private String name;
     9     private int count=1;
    10     boolean flag=false;//标记无资源
    11     private Lock lock=new ReentrantLock();
    12     private Condition con=lock.newCondition();
    13     private Condition pro=lock.newCondition();
    14     public void set(String name){
    15         lock.lock();//16             try {
    17                 while(flag)//重复判断标识,确认是否生产
    18                 con.await();//本方等待  
    19                 this.name=name+"....."+count++;
    20                 System.out.println(Thread.currentThread().getName()+"生产者。。。。"+this.name);
    21                 flag=true;
    22                 pro.signal();
    23                 
    24             } catch (InterruptedException e) {
    25                 e.printStackTrace();
    26             } 
    27               finally{
    28                   lock.unlock();//释放锁的机制一定要执行
    29             }
    30         }
    31         public void out(){
    32             lock.lock();
    33             try {
    34                 while(!flag)
    35                     pro.await();
    36                     System.out.println(Thread.currentThread().getName()+"消费者。。。。"+this.name);
    37                 flag=false;
    38                 con.signal();
    39             } catch (Exception e) {
    40                 // TODO: handle exception
    41             }finally{
    42                  lock.unlock();
    43             }
    44         }
    45     }
    46 //生产者
    47 public class Producker2 implements Runnable{
    48     private Resource2 res;
    49     Producker2(Resource2 res) {
    50         this.res=res;
    51     }
    52     @Override
    53     public void run() {
    54         // TODO Auto-generated method stub
    55         while(true)
    56         {
    57             res.set("商品");
    58             }    
    59         }
    60     }
    61 //消费者
    62  class customer2 implements Runnable{
    63     private Resource2 res;
    64     customer2(Resource2 res){
    65         this.res=res;
    66     }
    67     @Override
    68     public void run() {
    69         while(true){
    70             res.out();
    71                 }
    72             }
    73         }
    74 class OutInt3{
    75 
    76     public static void main(String[] args) {
    77         Resource2 r=new Resource2();
    78 //        new Thread(new customer(r)).start();
    79 //        new Thread(new Producker(r)).start();
    80         customer2 n1=new customer2(r);
    81         Producker2 o1=new Producker2(r);
    82         Thread t1=new Thread(n1);
    83         Thread t2=new Thread(o1);
    84         Thread t3=new Thread(n1);
    85         Thread t4=new Thread(o1);
    86         t1.start();
    87         t2.start();
    88         t3.start();
    89         t4.start();
    90     }
    91 }
    复制代码

     

    七、停止线程

            JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。

    那么现在我们该如果停止线程呢?

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

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

          如:run方法中有如下代码,设置一个flag标记。 

    复制代码
    1 public  void run()  
    2 {  
    3        while(flag)  
    4        {     
    5            System.out.println(Thread.currentThread().getName()+"....run");  
    6          }  
    7 }  
    复制代码

    那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。

    2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。

             当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();

    如:

     

    复制代码
     1 package I10;
     2 
     3 class StopThreadDemo implements Runnable  
     4 {  
     5     private boolean flag =true;  
     6     public  void run()  
     7     {  
     8         while(flag)  
     9         {  
    10             System.out.println(Thread.currentThread().getName()+"....run");  
    11         }  
    12     }  
    13     public void changeFlag()  
    14     {  
    15         flag = false;  
    16     }  
    17 }  
    18   
    19 class StopThread 
    20 {  
    21     public static void main(String[] args)   
    22     {  
    23         StopThreadDemo st = new StopThreadDemo();  
    24         Thread t1 = new Thread(st);  
    25         Thread t2 = new Thread(st);   
    26         t1.start();  
    27         t2.start();   
    28   
    29         int num = 0;  
    30         while(true)  
    31         {  
    32             if(num++ == 60)  
    33             {  
    34                 t1.interrupt();//清除冻结状态  
    35                 t2.interrupt();  
    36                 st.changeFlag();//改变循环标记  
    37                 break;  
    38             }  
    39             System.out.println(Thread.currentThread().getName()+"......."+num);  
    40         }  
    41         System.out.println("over");  
    42     }  
    43 }  
    复制代码

     

    1、join方法

           当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。

    2、setPriority()方法用来设置优先级

            MAX_PRIORITY 最高优先级10

            MIN_PRIORITY   最低优先级1

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

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

  • 相关阅读:
    C# 微信 生活助手 空气质量 天气预报等 效果展示 数据抓取 (二)
    2014年个人总结
    是去是留?这是个问题
    电信限制ip的解决方案
    机械学习
    mac-mysql
    如何使用代码生成工具
    如何使用自定义表单和自定义流程
    test
    编程小问题001
  • 原文地址:https://www.cnblogs.com/Hanxia/p/4476813.html
Copyright © 2011-2022 走看看