zoukankan      html  css  js  c++  java
  • Java多线程之Runnable与Thread

    Java多线程之Thread与Runnable

    一、Thread VS Runnable

      在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口;Thread类和Runnable接口都是在java.lang包中定义的。接下来本文给大家介绍下Java中Runnable和Thread的区别,当然啦,也算做是我整理的学习笔记吧,一起看看吧 

    • 实现Runnable接口方式可以避免继承Thread方式由于Java单继承特性带来的缺陷。具体什么缺陷呢?

      ①首先来从接口实现和类继承的区别来谈谈

      如果你想写一个类C,但这个类C已经继承了一个类A,此时,你又想让C实现多线程。用继承Thread类的方式不行了。(因为单继承的局限性),此时,只能用Runnable接口,Runnable接口就是为了解决这种情境出现的

      ②从Thread和Runnable的实现机制再来谈谈这个问题

      首先 ThreadRunnable 实际上是一种静态代理的实现方式。我们可以简单看一下源代码就了解了:

    public interface Runnable {
        public abstract void run();
    }
    
    public class Thread implements Runnable {
        ...
        private Runnable target;
        public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
        public void run() {
            if (target != null) {
                target.run(); //代理的target的run方法
            }
        }
    }
    

      另外一个我们知道线程启动是调用 thread.start() 方法,但是 start() 方法会调用 nativestart0()方法,继而由 JVM 来实现多线程的控制,因为需要系统调用来控制时间分片。

      现在我们可以深入理解一下两种线程实现方式的异同。

    Class MyThread extends Thread(){
        public int count = 10;
        public synchronized void run(){
            while(count>0){
                count--;
            }
        } 
    }
    
    new Mythread().start(); //启动 n 个线程
    new Mythread().start();

      这种实现方式实际上是重写了 run() 方法,由于线程的资源和 Thread 实例捆绑在一起,所以不同的线程的资源不会进行共享。

    Class MyThread implements Runnable{
        public int count = 10;
        public synchronized void run(){
            while(count>0){
                count--;
            }
        } 
    }
    MyThread mt = new MyThread();
    new Thread(mt).start();  //启动 n 个线程
    new Thread(mt).start();

      这种实现方式就是静态代理的方式,线程资源与 Runable 实例捆绑在一起,Thread 只是作为一个代理类,所以资源可以进行共享。

      ③从 Java 语言设计者的角度来看

      Runnable 可以理解为 Task,对应的是具体的要运行的任务,而 Thread 对应某一个具体的线程运行的载体。综上,继承 Thread 来实现,可以说是不推荐的。

      实现Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况。

      下面以典型的买票程序来说明这点(这里我为了让大家理解,没有用synchronized同步代码块,可能运行结果会不按正常出牌):

      ①通过继承Thread来实现

     1 package me.demo.thread;
     2 
     3 class MyThread extends Thread {
     4     private int ticketsCont = 5;// 一共有五张火车票
     5     private String name; // 窗口,也即是线程的名字
     6 
     7     public MyThread(String name) {
     8     this.name = name;
     9     }
    10 
    11     @Override
    12     public void run() {
    13     while (ticketsCont > 0) {
    14         ticketsCont--; // 如果还有票就卖一张
    15         System.out.println(name + "卖了一张票,剩余票数为:" + ticketsCont);
    16     }
    17     }
    18 }
    19 
    20 public class TicketsThread {
    21 
    22     public static void main(String[] args) {
    23     // 创建三个线程,模拟三个窗口卖票
    24     MyThread mt1 = new MyThread("窗口1");
    25     MyThread mt2 = new MyThread("窗口2");
    26     MyThread mt3 = new MyThread("窗口3");
    27 
    28     // 启动这三个线程,也即是窗口,开始卖票
    29     mt1.start();
    30     mt2.start();
    31     mt3.start();
    32     }
    33 }
    View Code

      我们运行这个程序会发现,三个线程各买了五张票,总共买了15张票,而我们模拟的火车站窗口总共就只剩下五张票,这显然会出问题的啊,更别谈资源共享了,因为程序中,我们创建了三个MyThread的实例对象,而作为普通成员变量的ticketsCont(注意,这里是非静态成员变量,如果是静态成员变量那就另当别论了)显然被初始化了三次,存在于三个不同的对象中,所以也就造成了这个结果,可见Thread不适合资源共享。

      ②通过实现Runnable接口来实现:

     1 package me.demo.runnable;
     2 
     3 class MyThread implements Runnable {
     4 
     5     private int ticketsCont = 5; // 一共有五张火车票
     6 
     7     @Override
     8     public void run() {
     9     while (ticketsCont > 0) {
    10         ticketsCont--;// 如果有票就卖掉一张
    11         System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数为:" + ticketsCont);
    12     }
    13     }
    14 
    15 }
    16 
    17 public class TicketsRunnable {
    18 
    19     public static void main(String[] args) {
    20     MyThread thread = new MyThread();
    21     // 创建三个线程来模拟三个售票窗口
    22     Thread th1 = new Thread(thread, "窗口1");
    23     Thread th2 = new Thread(thread, "窗口2");
    24     Thread th3 = new Thread(thread, "窗口3");
    25 
    26     // 启动这三个线程,也即是三个窗口开始卖票
    27     th1.start();
    28     th2.start();
    29     th3.start();
    30     }
    31 }
    View Code

      我们运行这个程序发现,三个Thread总共卖了五张票,这显然符合日常生活中的情况,因为Thread共享了实现了Runnable接口的MyThread类的实例中的成员变量ticketsCont,也就不存在上述问题了。

      另外,针对以上代码补充三点:

    • 在第二种方法(Runnable)中,ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测,ticket--并不是原子操作。

    • 在第一种方法中,我们new了3个Thread对象,即三个线程分别执行三个对象中的代码,因此便是三个线程去独立地完成卖票的任务;而在第二种方法中,我们同样也new了3个Thread对象,但只有一个Runnable对象,3个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象,作为参数分别传入3个Thread对象中,那么3个线程便会独立执行各自Runnable对象中的代码,即3个线程各自卖5张票。

    • 在第二种方法中,由于3个Thread对象共同执行一个Runnable对象中的代码,因此可能会造成线程的不安全,比如可能ticket会输出-1(如果我们System.out....语句前加上线程休眠操作,该情况将很有可能出现),这种情况的出现是由于,一个线程在判断ticket为1>0后,还没有来得及减1,另一个线程已经将ticket减1,变为了0,那么接下来之前的线程再将ticket减1,便得到了-1。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执行每次for循环中的操作。而在第一种方法中,并不需要加入同步操作,因为每个线程执行自己Thread对象中的代码,不存在多个线程共同执行同一个方法的情况。

    二、总结

      Thread类也是Runnable接口的子类,可见, 实现Runnable接口相对于继承Thread类来说,有如下显著的好处:

    • 适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。

    • 可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。

    • 有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。

      参考文章:

        https://segmentfault.com/q/1010000006056386

        http://www.jb51.net/article/105487.htm

        https://www.cnblogs.com/lt132024/p/6438750.html

    如果,您对我的这篇博文有什么疑问,欢迎评论区留言,大家互相讨论学习。
    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
    如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
    如果,您对我的博文感兴趣,可以关注我的后续博客,我是【AlbertRui】。

    转载请注明出处和链接地址,欢迎转载,谢谢!

  • 相关阅读:
    PAT 1006 Sign In and Sign Out
    PAT 1004. Counting Leaves
    JavaEE开发环境安装
    NoSql数据库探讨
    maven的配置
    VMWARE 下使用 32位 Ubuntu Linux ,不能给它分配超过3.5G 内存?
    XCODE 4.3 WITH NO GCC?
    在苹果虚拟机上跑 ROR —— Ruby on Rails On Vmware OSX 10.7.3
    推荐一首让人疯狂的好歌《Pumped Up Kicks》。好吧,顺便测下博客园可以写点无关技术的帖子吗?
    RUBY元编程学习之”编写你的第一种领域专属语言“
  • 原文地址:https://www.cnblogs.com/albertrui/p/8380017.html
Copyright © 2011-2022 走看看