欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

java的多线程,

来源: javaer 分享于  点击 41096 次 点评:285

java的多线程,


多线程

进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

 

什么是多线程呢?即就是一个程序中有多个线程在同时执行。

通过下图来区别单线程程序与多线程程序的不同:

单线程:

 

 

多线程:          

 

 

主线程 

回想我们以前学习中写过的代码,当我们在dos命令行中输入java空格类名回车后,启动JVM,并且加载对应的class文件。虚拟机并会从main方法开始执行我们的程序代码,一直把main方法的代码执行结束。如下代码演示:

/*

 *进程:进程就是系统中运行的一个应用程序,我们可以打开任务管理器查看进程那一栏

 *

 *线程:进程中包含一个或多个线程,线程控制进程的执行

 *

 *也可以说应用程序中有一个或多个线程

 *

 *main方法其实就是一个线程:所以main方法顺序执行

 *main方法所在的线程叫 主线程

 */

public class Demo01 {

    public static void main(String[] args) {

       System.out.println(1/0);

    }

}

 

我们可以看到 System.out.println(1/0)抛出异常, Exception in thread main,说明该异常是从main线程中抛出的,说明main方法在一个线程中,这个线程就是主线程

 

Thread类概述

该如何创建线程呢?通过API中搜索,查到Thread类。通过阅读Thread类中的描述。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

继续阅读,发现创建新执行线程有两种方法。

创建线程的步骤:

1 定义一个类继承Thread。

2 重写run方法。

3 创建子类对象,就是创建线程对象。

4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。

  

   /*回想自定义异常:

 *   Throwable

 *      Exception

 *      Error

 * 自己定义一个类继承Exception/RuntimeException

 * class AgeException extends Exception{

 *

 * }

 * class Frog extends Animal{

 *

 * }
 * 自定义线程思想跟上面一致

 * class 类名  extends Thread{

 *    //重写run方法

 *    public void run(){

 *       //线程要干的事(线程要执行的代码)

 *    }

 * }

 * 执行结果分析:

 *    发现每次打印顺序不同,但是main线程中的代码和自定义线程中的代码一定执行完

 */

public class Demo02 {

    public static void main(String[] args) {

       ThreadDemo01 td=new ThreadDemo01();

       //td.run();//不会开启任何自定义线程,这句话仅仅相当于创建对象调用方法

       td.start();

      

       for(int i=0;i<10;i++){

       System.out.println("main..."+i);

       }

    }

}



public class ThreadDemo01 extends Thread{

    @Override

    public void run() {

       for(int i=0;i<10;i++){

       System.out.println("run..."+i);

       }

    }

}

思考:线程对象调用 run方法和调用start方法区别?

线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。

 

继承Thread类原理

我们为什么要继承Thread类,并调用其的start方法才能开启线程呢?

继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?如下代码:

Thread t1 = new Thread();

t1.start();//这样做没有错,但是该start调用的是Thread类中的run方法,而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

Thread t1 = new Thread();
t1.start();//这样做没有错,但是该start调用的是Thread类中的run方法,而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

创建线程的目的是什么?

是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。

对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。

Thread类run方法中的任务并不是我们所需要的,只有重写这个run方法。既然Thread类已经定义了线程任务的编写位置(run方法),那么只要在编写位置(run方法)中定义任务代码即可。所以进行了重写run方法动作。

 多线程的内存图解

每个线程执行的代码都会在一个栈中,CPU在多个线程的run方法的代码中做着随机切换动作

 

 

Thread类的方法

开启的线程都会有自己的独立运行栈内存,那么这些运行的线程的名字是什么呢?该如何获取呢?既然是线程的名字,按照面向对象的特点,是哪个对象的属性和谁的功能,那么我们就去找那个对象就可以了。查阅Thread类的API文档发现有个方法是获取当前正在运行的线程对象。还有个方法是获取当前线程对象的名称。既然找到了,我们就可以试试。


public class ThreadDemo02 extends Thread{



    @Override

    public void run() {//由于Thread类中run方法没有抛出任何异常

                    //子类继承Thread类重写run方法也不能抛出任何异常

  /*    try{

    Thread.sleep(1000);//1s=1000ms

      }catch(InterruptedException e){

      

      }*/

      

       for(int i=0;i<10;i++){

       System.out.println(getName()+"..."+i);

       }

    }

}

创建线程方式—实现Runnable接口

创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

为何要实现Runnable接口,Runable是啥玩意呢?继续API搜索。

查看Runnable接口说明文档:Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法。

创建线程的步骤。

1、定义类实现Runnable接口。

2、覆盖接口中的run方法。。

3、创建Thread类的对象

4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5、调用Thread类的start方法开启线程。

public class RunnableDemo01 implements Runnable{

    @Override

    public void run() {

     for(int i=0;i<10;i++){

          System.out.println(Thread.currentThread().getName()+"..."+i);

     }

    }

}

 


/*创建线程的第二种方式:

 *   实现Runnable接口

 *   class 类名 implements Runnable{

 *      public void run(){

 *        //线程执行的代码

 *      }

 *   }

 * Thread类中的构造方法:

 *    Thread(Runnable target)



 */

public class Demo01 {

    public static void main(String[] args) {

        RunnableDemo01 rd=new RunnableDemo01();

        /*

         * new Thread()是开启一个线程

         * 传入的rd:告诉线程执行的代码

         * start():JVM调用run方法

         *

         */

        new Thread(rd).start();

       

        for(int i=0;i<5;i++){

            System.out.println(Thread.currentThread().getName()+"..."+i);

        }

    }

}

 

实现Runnable的原理

为什么需要定一个类去实现Runnable接口呢?继承Thread类和实现Runnable接口有啥区别呢?

实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法,将线程任务代码定义到run方法中。

创建Thread类的对象,只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。

实现Runnable的好处

第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。

线程的匿名内部类使用

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

   

     /*

 *匿名内部类

 *  new 父类名/父接口名(){

 *     //重写父类或父接口的方法

 *  };

 *  上面的创建匿名内部类完成两个操作:

 *   1.底层会创建一个父类或父接口的匿名子类或匿名实现类

 *   2.还会创建这个子类或实现类的对象

 *   class 匿名 extends 父类{

 *  

 *   }

 *   new 匿名();

 */

public class Demo02 {

    public static void main(String[] args) {

       //对继承Thread类使用匿名内部类方式

        Thread t=new Thread(){ //多态

            @Override

            public void run() {

                 for(int i=0;i<10;i++){

                     System.out.println(getName()+"..."+i);

                 }

            }

        };

        t.start();

   

    }

}

 

 

 public class Demo02 {

    public static void main(String[] args) {

       

        //对实现Runnable接口采用匿名内部类方式

        Runnable r=new Runnable() {

            @Override

            public void run() {

                 for(int i=0;i<10;i++){

                     System.out.println(Thread.currentThread().getName()+"..."+i);

                 }             

            }

        };

       

        new Thread(r).start();

    }

}

 

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “功夫熊猫3”,本次电影的座位共100个(本场电影只能卖100张票)。

需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟

/*多线程卖票

 *   用三个售票窗口卖100张票

 *   从第100张一直卖到第1张

 *   例如:

 *     A窗口   B窗口  C窗口

 *     100  98   96

 *     99   97   95

 *     .......

 *   用线程模拟售票窗口

 *   线程执行的代码卖票通过打印卖第几张票来模拟

 *

 */

public class Demo01 {

    public static void main(String[] args) {

       //1.创建Runnable的子类对象

        Ticket t=new Ticket();

     

      //2.创建三个线程模拟三个窗口,让它们去卖这100张票

        new Thread(t).start();//Thread-0

        new Thread(t).start();//Thread-1

        new Thread(t).start();//Thread-2

    }

}

public class Ticket implements Runnable{

    //存储卖的票数

    private int ticket=100;



    @Override

    public void run() {

      while(true){

    //打印卖第几张票 

    /*  try {

                Thread.sleep(100);

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }*/



         if(ticket>0){

                System.out.println(Thread.currentThread().getName()+"卖第"

                                 +ticket--+"张");//后置自减,先使用再自减

         }

      }

    }

}

 1 结果

     出现0票,-1票

    

      把睡眠放在if(ticket>0)后面也能有错票产生

   

  /*我们需要通过同步代码块来保证共享数据的安全

 *同步代码块

 *  synchronized(锁对象){//锁对象可以是任意对象

 *      //需要保证安全的代码

 *      //我们一般把涉及共享数据代码放在里面

 * 

 *  }

 * 注意事项:保证所有的线程使用同一个锁对象

 *

 */

public class Ticket implements Runnable{

    //存储卖的票数

    private int ticket=100;

   

    Object obj=new Object();//做为锁对象

    @Override

    public void run() {

      while(true){

    //打印卖第几张票 

    /*  try {

                Thread.sleep(100);

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }*/

    synchronized(obj){

         if(ticket>0){

                System.out.println(Thread.currentThread().getName()+"卖第"

                                 +ticket--+"张");//后置自减,先使用再自减

         }

     }

   

      }

    }

}
 /*第二种保证安全的机制:

 *  同步方法格式:

 *  修饰符 synchronized 返回值类型  方法名(){

 *          //代码

 *  }

 *  同步方法也相当于同步代码块,同步方法的锁对象是this

 *  等效代码

 *   修饰符 返回值类型  方法名(){

 *      synchronized(this){

 *           //代码

 *          

 *      }

 *  }

 */

public class Ticket02 implements Runnable {

    // 存储卖的票数

    private int ticket = 100;



    @Override

    public  void run() {

            //Thread-0

            while (true) {

                // 打印卖第几张票

                method01();

            }

     

       

    }

    private synchronized void method01() {

        if (ticket > 0) {

            System.out.println(Thread.currentThread().getName() + "卖第"

                    + ticket-- + "张");// 后置自减,先使用再自减

        }

    }

}

 

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

我们详细的解释一下为什么要使用线程池?

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

 

通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。        

  • ExecutorService:线程池类
  • 使用线程池中线程对象的步骤:
  • 代码演示:

    package com.whhp.thread04;
    
    
    
    import java.util.concurrent.ExecutorService;
    
    import java.util.concurrent.Executors;
    
    
    
    /*创建线程的时机:当我们new Thread()调用start()方法时候会创建线程
    
     *销毁线程的时机:当线程中的run方法执行完,线程会被销毁
    
     *创建和销毁动作都比较消耗资源
    
     *
    
     *线程池
    
     *
    
     */
    
    public class Demo01 {
    
        public static void main(String[] args) {
    
            //创建一个线程池,线程池中有三个线程
    
            ExecutorService es=Executors.newFixedThreadPool(3);
    
           
    
            //向线程池中提交任务
    
            es.submit(new Task());
    
        }
    
    }
    
    class Task implements Runnable{
    
        @Override
    
        public void run() {
    
           for(int i=0;i<10;i++){
    
               System.out.println(Thread.currentThread().getName()+"..."+i);
    
           }
    
        }
    
    

     

    相关文章

      暂无相关文章
    相关栏目:

    用户点评