zoukankan      html  css  js  c++  java
  • Java 多线程 之 线程的创建和使用

    一、主线程

      主线程:执行主(main)方法的线程

      单线程程序:java程序中只有一个线程,执行从main方法开始,从上到下依次执行

                 下面这个只有一个 main 线程,并不是多线程

                  

    二、线程的创建和启动

      1、线程的创建

        (1)Java 语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来体现;

        (2)Thread 类的特性

        •  每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的,经常把 run() 方法的主体称为 线程体;
        •     通过该 Thread 对象的 start() 方法来启动这个线程,而非直接调用 run();      

        (3)Thread 类

          构造器:

    Thread(): 创建新的Thread对象;
    
    Thread(String threadname): 创建线程并指定线程实例名;
    
    Thread(Runnable target): 指定创建线程的目标对象,它实现了Runnable接口中的run方法;
    
    Thread(Runnable target, String name): 创建新的Thread对象
    

      

      2、创建线程

        JDK1.5 之前创建新执行线程有两种方法:

        •  继承 Thread 类的方式;
        •    实现 Runnable 接口的方式

      3、方式一:继承 Thread 类

        (1)步骤:

          ① 定义子类继承 Thread 类;

          ② 子类中重写 Thread 类中的 run 方法——>将此线程执行的操作声明在run()中;

          ③ 创建 Thread 子类对象,即创建了线程对象;

          ④ 调用线程对象 start 方法:启动线程,调用 run 方法;

        (2)示例:

     1 // 1. 创建一个继承于 Thread 类的子类
     2 class MyThread extends Thread {
     3 
     4     //2 重写 Thread 类的 run() 方法
     5     @Override
     6     public void run() {
     7         for (int i = 0; i <100; i++) {
     8             if (i % 2 == 0) {
     9                 System.out.println( Thread.currentThread().getName() + ":" + i);
    10             }
    11         }
    12     }
    13 }
    14 
    15 public class ThreadTest {
    16     public static void main(String[] args) {
    17         //3、创建Thread 类的子类的对象
    18         MyThread myThread = new MyThread();
    19 
    20         //4、通过此对象调用 start()
    21         myThread.start();
    22 
    23         //仍然在 main线程中执行的。
    24         for (int i = 0; i <100; i++) {
    25             if (i % 2 == 0) {
    26                 System.out.println(i + "*********main()***********");
    27             }
    28         }
    29     }
    30 }

      4、方式二:实现 Runnable 接口

        (1)步骤

          ① 定义子类,实现 Runnable 接口;

          ② 子类中重写 Runnable 接口中的 run 方法

          ③ 通过 Thread 类含参构造器创建线程对象;

          ④ 将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造器中

          ⑤ 调用 Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法;

        (2)示例

     1 //1. 创建一个实现了Runnable接口的类
     2 class MThread implements Runnable{
     3 
     4     //2. 实现类去实现Runnable中的抽象方法:run()
     5     @Override
     6     public void run() {
     7         for (int i = 0; i < 100; i++) {
     8             if(i % 2 == 0){
     9                 System.out.println(Thread.currentThread().getName() + ":" + i);
    10             }
    11 
    12         }
    13     }
    14 }
    15 public class ThreadTest {
    16     public static void main(String[] args) {
    17         //3. 创建实现类的对象
    18         MThread mThread = new MThread();
    19         //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    20         Thread t1 = new Thread(mThread);
    21         t1.setName("线程1");
    22         //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
    23         t1.start();
    24 
    25         //再启动一个线程,遍历100以内的偶数
    26         Thread t2 = new Thread(mThread);
    27         t2.setName("线程2");
    28         t2.start();
    29     }
    30 }

      5、注意点

        (1)如果自己手动调用 run() 方法,那么就只是普通方法,没有启动多线程模式

        (2)run() 方法由 JVM 调用,什么时候调用,执行的过程控制都由操作系统的 CPU调度决定;

        (3)想要启动多线程,必须调用 start 方法,start() 方法作用:① 启动当前线程; ② 调用当前线程的run()

        (4)一个线程对象只能调用一次 start() 方法启动,如果重复调用了,则将抛出异常信息 "IllegalThreadStateException";

          

          即调用 start() 方法时,如果该线程的状态不为 0,就会抛出该异常。 

      6、比较创建线程的两种方式

        日常开发中:优先选择:实现Runnable接口的方式

        实现方式的好处:① 避免了单继承的局限性;

                ② 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源;

        两者的联系:可以发现 Thread 类实现了 Runnable接口,

          

            Runnable 接口中的 run() 方法为抽象方法。

             

           Thread 类继承 Runnable 接口并重写了 run() 方法,此处的 target 就是含参的构造器中给我们传递的过来的 Runnable 的子类对象:

            

        区别:① 继承 Thread 类:线程代码存放 Thread 子类 run 方法中;

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

      7、Runnable 接口(推荐使用)

        实现了 Runnable 接口,很容器实现资源共享,而继承 Thread ,则不适合资源共享。(多个 Thread 对象共用同一个 Runnable子类对象,Runnable 中的变量就是公共的,可以共享)

        实现 Runnable 接口比继承 Thread 类所具有的好处:

          ① 适合多个相同的程序代码的线程去共享同一个资源 

          ② 可以避免java中的单继承的局限性。(一个类可以实现多个接口)

          ③ 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立 (实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦))

          ④ 线程池只能放入实现RunableCallable类线程,不能直接放入继承Thread的类;

      8、匿名内部类方式实现线程的创建

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

        匿名类:没有名字的类

        内部类:写在其他类内部的类

           匿名内部类作用:简化代码

          把子类继承父类,重写父类的方法,创建子类对象和一步完成;

          把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成;

        语法格式:

    new 父类/接口(){
            重复父类/接口中的方法
    };
    

        Demo:

     1 public class DemoInnerClassThread {
     2     public static void main(String[] args) {
     3         //线程的父类是Thread
     4         // new MyThread().start();
     5         new Thread(){
     6             //重写run方法,设置线程任务
     7             @Override
     8             public void run() {
     9                 for (int i = 0; i <10 ; i++) {
    10                     System.out.println(Thread.currentThread().getName()+"-->"+"java");
    11                 }
    12             }
    13         }.start();
    14 
    15         //线程的接口Runnable
    16         //Runnable r = new RunnableImpl();//多态
    17         Runnable r = new Runnable(){
    18             //重写run方法,设置线程任务
    19             @Override
    20             public void run() {
    21                 for (int i = 0; i <20 ; i++) {
    22                     System.out.println(Thread.currentThread().getName()+"-->"+"程序猿");
    23                 }
    24             }
    25         };
    26         new Thread(r).start();
    27 
    28         //简化接口的方式
    29         new Thread(new Runnable(){
    30             //重写run方法,设置线程任务
    31             @Override
    32             public void run() {
    33                 for (int i = 0; i <20 ; i++) {
    34                     System.out.println(Thread.currentThread().getName()+"-->"+"程序媛");
    35                 }
    36             }
    37         }).start();
    38     }
    39 }

    三、子线程的创建和启动过程

      多线程的执行过程原理:

      示例代码:

     1 // 自定义线程类
     2 public class MyThread extends Thread{
     3     /*
     4     * 利用继承中的特点
     5     * 将线程名称传递 进行设置
     6     */
     7     public MyThread(String name){
     8       super(name);
     9     } 
    10     /*
    11     *     
    12     *重写run方法
    13     * 定义线程要执行的代码
    14     */
    15     public void run(){
    16         for (int i = 0; i < 20; i++) {
    17             //getName()方法 来自父亲
    18             System.out.println(getName()+i);
    19         }
    20     }
    21 }
    22 // 测试类
    23 public class Demo {
    24     public static void main(String[] args) {
    25         System.out.println("这里是main线程");
    26         MyThread mt = new MyThread("小强");
    27         mt.start();//开启了一个新的线程
    28         for (int i = 0; i < 20; i++) {
    29             System.out.println("旺财:"+i);
    30         }
    31     }
    32 }

      流程图:

      

      原理:

        ① 程序启动运行main时候,java虚拟机启动一个进程,主线程mainmain()调用时候被创建;

        ② 随着调用mt的对象的 start 方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。 

      并发原理:

         多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。 

        

         当有多个线程存在,CPU 就有了选择的权利,可以执行 main() 方法,也可以执行 run() 方法。

         当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

         多线程好处:多个线程之间互不影响(因为在不同的栈区)

    四、线程常用方法

      常用方法:

    void start(): 启动线程,并执行对象的run()方法;
    run(): 线程在被调度时执行的操作,将创建的线程要执行的操作声明在此方法中;
    String getName(): 返回线程的名称;主线程的名称:main;其他线程:默认是 Thread-编号
    void setName(String name):设置该线程名称;
    static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类;
    static void yield(): 线程让步,释放当前 CPU 的执行权;
        暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程;
        若队列中没有同优先级的线程,忽略此方法;
    
    join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
        低优先级的线程也可以获得执行;
          在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。 static void sleep(long millis): 让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。 抛出InterruptedException异常 stop(): 强制线程生命期结束,不推荐使用(已过时) boolean isAlive(): 返回boolean,判断线程是否还活着
    public void interrupt(): 中断线程
    
    

      

    五、线程的调度

      1、调度策略

        •  时间片

             

        •     抢占式:高优先级的线程抢占 CPU;

      2、Java 的调度方法

      •  同优先级线程组成先进先出队列(先到先服务),使用时间片策略;
      •  对高优先级,使用优先调度的抢占式策略;

    六、线程的优先级

      1、线程的优先级等级

        在 Thread 中有三个常量是关于 线程的优先级的:

    MAX_PRIORITY: 10
    MIN _PRIORITY: 1
    NORM_PRIORITY: 5    默认的优先级
    

      

      2、关于优先级的方法

    getPriority() : 返回线程优先值
    setPriority(int newPriority) : 改变线程的优先级
    

      

      3、说明

        (1)线程创建时继承父线程的优先级;

        (2)低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用;

        (3)高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

    扩展:

       java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。 

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

      •   它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
      •        守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true可以把一个用户线程变成一个守护线程。
      •        Java垃圾回收就是一个典型的守护线程。
      •        若JVM中都是守护线程,当前JVM将退出
  • 相关阅读:
    【myEcplise2015】导入喜欢的主题
    【SVN】删除SVN上的历史资源路径和SVN上的历史用户信息
    【Linux】linux命令大全
    【Linux】在虚拟机上安装CentOS7
    Spring MVC+Mybatis 多数据源配置
    Spring 加载类路径外的资源文件
    OkHttp使用详解
    在虚拟机搭建JStrom
    在Windows下搭建RocketMQ
    解决confluence的乱码问题
  • 原文地址:https://www.cnblogs.com/niujifei/p/14401729.html
Copyright © 2011-2022 走看看