java 多线程 Thread & Runnable 使用与区别

简介: 1.线程和进程的定义进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位

多线程

1.线程和进程的定义

  • 进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
  • 线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位
  • 1.2.线程进程的区别体现在几个方面:
  • 因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。


  • 体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。


  • 属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同的进程相互独立。


  • 线程又称为轻量级进程,进程有进程控制块,线程有线程控制块;


  • 线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;


  • 简而言之:开启酷狗音乐只是一个进程,同时可以听歌和下载歌曲(2个线程)

1.3.我们的理解:

  • 进程是指在系统中正在运行的一个应用程序;程序一旦运行就是进程,或者更专业化来说:进程是指程序执行时的一个实例。
  • 线程是进程的一个实体。
  • 进程——资源分配的最小单位,线程——程序执行的最小单位

2.多线程的2种或者4种实现方法

ps:网上说有4种实现方法,但是也有一部分人是持但对意见的,那么这4种方法究竟如何呢?


2.1.Java多线程实现的方式有四种

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
  • 通过Callable和Future/FutureTask创建多线程
  • 通过线程池创建多线程

补充:

前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果。

后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中

3.多线程的创建之Thread的步骤

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建子类对象,就是创建线程对象
  4. 调用start方法,开启多线程并执行,同时还会告诉jvm去调用run方法
package com.Li.xc01;
/**
 * @Description:通过继承thread实现多线程
 * @auther:Li Ya Hui
 * @Time:2021年4月20日下午7:39:10
 */
public class JiSuanQi extends Thread {
  @Override
  public void run() {
//      System.out.println("计算器:run()");
//      System.out.println("当前线程的名字:"+Thread.currentThread().getName());
    for (int i = 0; i < 10; i++) {
      System.out.println("主函数的名字\t"+getName());
            //Thread.currentThread().getName()可以缩写,但是为了语义化,一般都要写
    }
  }
  //此构造期为更改线程名字
  public JiSuanQi(String name) 
  {
    super(name);
  }
}
/**
 * @Description:  测试类 通过继承thread类实现多线程             多线程 之 Thread的步骤
 * @auther:Li Ya Hui
 * @Time:2021年4月20日下午7:17:50
 */
public class Test {
  public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
      System.out.println("主函数的名字\t"+Thread.currentThread().getName());
    }
    System.out.println("主函数001");
    System.out.println("主函数002");
    System.out.println("主函数003");
    //主函数的线程名字:main
    System.out.println("当前线程的名字"+Thread.currentThread().getName());
    //开启多线程,创建子类对象
    JiSuanQi jiSuanQi = new JiSuanQi("计算器线程");
    //当调用start方法的时候,jvm会自动执行run方法,
    //其他非主函数的线程的名字:Thread-0...... 但是可以通过有参数构造器进行修改
    jiSuanQi.start();
  }
}

通过测试发现thread里的run方法不会因为main方法的结束而受到影响

4.通过实现runnable接口实现多线程

  1. 定义类实现Runnable接口
  2. 覆盖/实现接口中的run方法
  3. 创建Thread类的对象
  4. 将Runnable接口的实现类对象作为参数传递给Thread类的构造函数
  5. 调用Thread类的start方法开启线程
//Runable 接口  的实现类
/**
 * @Description: 通过Runable实现类  给Thread传参数实现 多线程
 * @auther:Li Ya Hui
 * @Time:2021年4月20日下午8:34:21
 */
public class Prims implements Runnable{
  @Override
  public void run() {
    while (true)
    {
      try {
        //线程睡眠 1000毫秒
        Thread.sleep(1000);
        System.out.println("当前线程的内容:"+Thread.currentThread().getName());
      } catch (InterruptedException e) {//线程中断异常
        //异常处理
        System.out.println("当前"+Thread.currentThread().getName()+"线程终端异常");
      }
    }
  }
}
//测试类
package com.Li.xc02;
/**
 * @Description: 测试  runnable  给Thread传参数实现 多线程
 * @auther:Li Ya Hui
 * @Time:2021年4月21日下午3:05:40
 */
public class Test {
  public static void main(String[] args) throws InterruptedException {
    System.out.println("主线程执行的内容001");    
    //实例化runnable,
    Prims a = new Prims();
    //参数形式传递给Thread
    Thread TH = new Thread(a);
    //主线程睡眠
    Thread.sleep(2000);
    System.out.println("主线程执行的内容002");    
    //子线程开启
    TH.start();   
    System.out.println("主线程执行的内容003");
  }
}

4.2.解释说明

Thread类中包括构造函数Thread(Runnable target)和Thread(Runnable target,String name),可以床底Runnable接口,说明构造函数支持传入一个Runnable接口的对象。

构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象。这样的好处是可以完全将Thread对象中的run()方法交由其他线程进行调用。


4.3.比较

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

main函数,实例化线程对象也有所不同,

extends Thread :t.start();

implements Runnable : new Thread(t).start();

使用Runnable,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类


4.4.实现Runnable的原理

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


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


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


  • 简而言之:实现Runable接口进而实现接口中的run方法,然后创建实例化Thread类,将线程任务所在的对象/类以参数的形式传递给Thread的构造函数最终实现多线程


4.5.实现Runnable的好处

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

5.利用匿名内部类+runnable接口实现多线程

package com.Li.xc02;
/**
 * @Description: 利用匿名内部类+runnable接口实现多线程
 * @auther:Li Ya Hui
 * @Time:2021年4月21日下午6:49:12
 */
public class Test02 {
  public static void main(String[] args) {
    System.out.println("主函数线程打印:001");
    Thread thread = new Thread(new Runnable() 
    {
      @Override
      public void run()
      {
        System.out.println("当前线程的名字为:"+Thread.currentThread().getName());
      }
    });
    thread.start();
    System.out.println("主函数线程打印:002");
  }
}

优点:

  1. 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他的功能
  2. 同一个线程任务对象可以被包装成多个线程对象
  3. 适合多个相同的程序代码的线程去共享同一个资源
  4. 实现解棍操作,代码可以被多个线程共享,线程任务代码和线程独立


6.Thread和Runnable的区别

6.1.通过继承Thread类模拟售票

package com.Li.xc03;
/**
 * 
 * @Description:买票类   测试通过继承Thread类模拟售票
 * @auther:Li Ya Hui
 * @Time:2021年4月21日下午9:04:36
 */
public class Mythread extends Thread{
  public int tickets = 5;
  public void run() {
    for (int i = 1; i < 10; i++) {
      System.out.println("当前线程为:"+getName()+":"+(tickets--));
    }
  }
  //线程添加名字  
  public Mythread(String name) {
    super(name);
    // TODO Auto-generated constructor stub
  }
}
package com.Li.xc03;
/**
 * @Description: 测试类 测试通过继承Thread类模拟售票
 * @auther:Li Ya Hui
 * @Time:2021年4月21日下午9:13:41
 */
public class Test {
  public static void main(String[] args) {
    Mythread myA = new Mythread("A窗口");
    Mythread myB = new Mythread("B窗口");
    myA.start();
    myB.start();
    //通过结果可以看成是卖了10张票但是一共就5张票,所以此时只能说明这种方式实现的时候是没有共享资源的
  }
}
  • 通过结果可以看成是卖了10张票但是一共就5张票,所以此时只能说明这种方式实现的时候是没有共享资源的 ( 没有共享tickets )

6.2.通过实现Runnable接口实现售票

可以实现共享资源的效果,但是会同时进行买票的问题,延伸出了互斥锁

synchronized (this)//同步锁    //  mutex互斥 在此处是锁的效果,即互斥锁
{
    if (tickets > 0) 
    {
      System.out.println("当前线程的名字:" + Thread.currentThread().getName() + ":" + (tickets--));
    }
}

互斥锁使得,多个线程不可同时访问一个资源

案例展示

package com.Li.xc04;
/**
 * @Description: 通过实现 Runnable接口  实现多线程-模拟售票
 * @auther:Li Ya Hui
 * @Time:2021年4月21日下午9:30:16 
 */
public class Mythread implements Runnable {
  //总票数
  public int tickets = 500;
  @Override
  public void run() {
    for (int i = 0 ;i<10000 ; i++)  {
      //this 指代当前类的意思  在此处表示为给当前的类加上互斥锁的效果
      synchronized (this)//同步锁    //  mutex互斥 在此处是锁的效果,即互斥锁
      {
        if (tickets > 0) {
          System.out.println("当前线程的名字:" + Thread.currentThread().getName() + ":" + (tickets--));
        }
      }
    }
  }
}
package com.Li.xc04;
/**
 * @Description: 测试类  测试Runnable的实现类运用多个线程来共享资源去买票,不会有数据读脏的可能
 * @auther:Li Ya Hui
 * @Time:2021年4月21日下午10:08:55
 */
public class Test {
  public static void main(String[] args) {
    //实例化我的售票类
    Mythread myRunThread1 = new Mythread();
    //实例两个线程  用来分工
    Thread thread1 = new Thread(myRunThread1, "1号窗口");
    Thread thread2 = new Thread(myRunThread1, "线程二");
    thread1.start();
    thread2.start();
    //通过结果我们发现,会有数据读脏的可能性
    //所以要使用互斥锁来避免同时读取数据问题
  }
}

Runnable总结:


  • 实现Runnable接口比继承Thread类所具有的优势:
  • 适合多个相同的程序代码的线程去处理同一个资源
  • 可以避免java中的单继承的限制
  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
  • 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类


目录
相关文章
|
7天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
|
11天前
|
Java
java基础(4)public class 和class的区别及注意事项
本文讲解了Java中`public class`与`class`的区别和注意事项。一个Java源文件中只能有一个`public class`,并且`public class`的类名必须与文件名相同。此外,可以有多个非`public`类。每个类都可以包含一个`main`方法,作为程序的入口点。文章还强调了编译Java文件生成`.class`文件的过程,以及如何使用`java`命令运行编译后的类。
15 3
java基础(4)public class 和class的区别及注意事项
|
6天前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
15 4
ly~
|
9天前
|
安全 Java 大数据
php跟java有什么区别
PHP 和 Java 是两种常用编程语言,各有特色。PHP 语法简洁灵活,适用于快速开发中小型网站,尤其在 Web 脚本和数据库交互中表现出色。Java 则语法严谨,强类型特性使其在企业级应用、移动开发及大数据处理中更受欢迎,具备高稳定性和安全性。通过优化,PHP 性能可提升,而 Java 在大规模应用中表现更佳。总体而言,PHP 开发效率高但维护性稍差,Java 则更注重代码质量和安全性。
ly~
17 5
|
5天前
|
NoSQL 网络协议 Unix
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
17 1
|
8天前
|
Java 调度
Java-Thread多线程的使用
这篇文章介绍了Java中Thread类多线程的创建、使用、生命周期、状态以及线程同步和死锁的概念和处理方法。
Java-Thread多线程的使用
|
8天前
|
Java 数据处理 调度
Java中的多线程编程:从基础到实践
本文深入探讨了Java中多线程编程的基本概念、实现方式及其在实际项目中的应用。首先,我们将了解什么是线程以及为何需要多线程编程。接着,文章将详细介绍如何在Java中创建和管理线程,包括继承Thread类、实现Runnable接口以及使用Executor框架等方法。此外,我们还将讨论线程同步和通信的问题,如互斥锁、信号量、条件变量等。最后,通过具体的示例展示了如何在实际项目中有效地利用多线程提高程序的性能和响应能力。
|
9天前
|
安全 算法 Java
Java中的多线程编程:从基础到高级应用
本文深入探讨了Java中的多线程编程,从最基础的概念入手,逐步引导读者了解并掌握多线程开发的核心技术。无论是初学者还是有一定经验的开发者,都能从中获益。通过实例和代码示例,本文详细讲解了线程的创建与管理、同步与锁机制、线程间通信以及高级并发工具等主题。此外,还讨论了多线程编程中常见的问题及其解决方案,帮助读者编写出高效、安全的多线程应用程序。
|
2月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
64 1
|
5月前
|
设计模式 监控 Java
Java多线程基础-11:工厂模式及代码案例之线程池(一)
本文介绍了Java并发框架中的线程池工具,特别是`java.util.concurrent`包中的`Executors`和`ThreadPoolExecutor`类。线程池通过预先创建并管理一组线程,可以提高多线程任务的效率和响应速度,减少线程创建和销毁的开销。
119 2
下一篇
无影云桌面