当前位置: 首页 > news >正文

多线程的几种实现方法详解

[接触多线程]

class MyThread extends Thread{
 public void run(){
  System.out.println("Thread say:Hello,World!");
 }
}

public class MoreThreads{
 public static void main(String[] args){
  new MyThread();
  new MyThread().start();
  System.out.println("Main say:Hello,World");
 }
}

  执行这个程序,main方法第一行产生了一个线程对象,但并没有线程启动。

  main方法第二行产生了一个线程对象,并启动了一个线程。

  main方法第三行,产生并启动一个线程后,主线程自己也继续执行其它语句。

  我们先不研究Thread对象的具体内容,稍微来回想一下上面的两个概念,线程对象和线程。在JAVA中,线程对象是JVM产生的一个普通的Object子类。而线程是CPU分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。如果一时想不明白,不要急,但你要记得它们不是一回事就行了。

  [线程的并发与并行]

  在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。

  [JAVA线程对象]

  现在我们来开始考察JAVA中线程对象。

  在JAVA中,要开始一个线程,有两种方式。一是直接调用Thread实例的start()方法,二是

  将Runable实例传给一个Thread实例然后调用它的start()方法。

  在前面已经说过,线程对象和线程是两个完全不同的概念。这里我们再次深入一下,生成一个线程的实例,并不代表启动了线程。而启动线程是说在某个线程对象上启动了该实例对应的线程,当该线程结束后,并不会就立即消失。

  对于从很多书籍上可以看到的基础知识我就不用多说了。既然是基础知识,我也着重于从普通文档上读不到的内容。所以本节我重点要说的是两种线程对象产生线程方式的区别。

class MyThread extends Thread{
 public int x = 0;
 public void run(){
  for(int i=0;i<100;i++){
    try{
     Thread.sleep(10);
    }catch(Exception e){}
    System.out.println(x++);
  }
 }
}

  如果我们生成MyThread的一个实例,然后调用它的start()方法,那么就产生了这个实例对应的线程:

public class Test {
 public static void main(String[] args) throws Exception{
  MyThread mt = new MyThread();
  mt.start();
 }
}

  不用说,最终会打印出0到99,现在我们稍微玩一点花样:

public class Test {
 public static void main(String[] args) throws Exception{
  MyThread mt = new MyThread();
  mt.start();
  System.out.println(101);
 }
}

  也不用说,在基础篇(一)中我们知道由于单CPU的原因,一般会先打印101,然后打印0到99。不过我们可以控制线程让它按我们的意思来运行:

public class Test {
 public static void main(String[] args) throws Exception{
  MyThread mt = new MyThread();
  mt.start();
  mt.join();
  System.out.println(101);
 }
}

  好了,我们终于看到,mt实例对应的线程(假如我有时说mt线程请你不要怪我,不过我尽量不这么说)。在运行完成后,主线程才打印101。因为我们让当前线程(这里是主线程)等待mt线程的运行结束。"在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后才继续运行。" 请大家一定要深刻理解并熟记这句话,而我这里引出这个知识点的目的是为了让你继续看下面的例子:

public class Test {
 public static void main(String[] args) throws Exception{
  MyThread mt = new MyThread();
  mt.start();
  mt.join();
  Thread.sleep(3000);
  mt.start();
 }
}

  当线程对象mt运行完成后,我们让主线程休息一下,然后我们再次在这个线程对象上启动线程。结果我们看到:

  Exception in thread "main" java.lang.IllegalThreadStateException

  也就是这种线程对象一时运行一次完成后,它就再也不能运行第二次了。我们可以看一下它有具体实现:

public synchronized void start() {
 if (started)
  throw new IllegalThreadStateException();
  started = true;
  group.add(this);
  start0();
 }

  一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了,这意味着:

  [通过Thread实例的start(),一个Thread的实例只能产生一个线程]

  那么如果要在一个实例上产生多个线程(也就是我们常说的线程池),我们应该如何做呢?这就是Runnable接口给我们带来的伟大的功能。

class R implements Runnable{
 private int x = 0;
 public void run(){
  for(int i=0;i<100;i++){
    try{
     Thread.sleep(10);
    }catch(Exception e){}
    System.out.println(x++);
  }
 }
}

  正如它的名字一样,Runnable的实例是可运行的,但它自己并不能直接运行,它需要被Thread对象来包装才行运行:

public class Test {
 public static void main(String[] args) throws Exception{
  new Thread(new R()).start();
 }
}

  当然这个结果和mt.start()没有什么区别。但如果我们把一个Runnable实例给Thread对象多次包装,我们就可以看到它们实际是在同一实例上启动线程:

public class Test {
 public static void main(String[] args) throws Exception{
  R r = new R();
  for(int i=0;i<10;i++)
    new Thread(r).start();
 }
}

  x是实例对象,但结果是x被加到了999,说明这10个线程是在同一个r对象上运行的。请大家注意,因为这个例子是在单CPU上运行的,所以没有对多个线程同时操作共同的对象进行同步。这里是为了说明的方便而简化了同步,而真正的环境中你无法预知程序会在什么环境下运行,所以一定要考虑同步。

  到这里我们做一个完整的例子来说明线程产生的方式不同而生成的线程的区别:

package debug;
import java.io.*;
import java.lang.Thread;
class MyThread extends Thread{
 public int x = 0;
 public void run(){
  System.out.println(++x);
 }
}

class R implements Runnable{
 private int x = 0;
 public void run(){
  System.out.println(++x);
 }
}
public class Test {
 public static void main(String[] args) throws Exception{
  for(int i=0;i<10;i++){
    Thread t = new MyThread();
    t.start();
  }
  Thread.sleep(10000);//让上面的线程运行完成
  R r = new R();
  for(int i=0;i<10;i++){
    Thread t = new Thread(r);
    t.start();
  }
 }  
}

  上面10个线程对象产生的10个线程运行时打印了10次1。下面10个线程对象产生的10个线程运行时打印了1到10。我们把下面的10个线程称为同一实例(Runnable实例)的多个线程。

转载于:https://www.cnblogs.com/cyl048/p/8506383.html

相关文章:

  • linux修改时间
  • 企业级应用与互联网应用区别
  • 地址空间映射
  • git bash 常用操作文件命令行
  • uiautomatorviewer定位App元素
  • Vue计算属性和监听属性
  • Apple激活日期查询
  • numpy教程
  • 常见设计模式,总结的不错(转)
  • 数据schemaAvro简介
  • 在C++的函数中如何指定一个数组,使得这个数组的大小由函数的输入值来决定...
  • 电感(29)之铁氧体磁珠工作原理透彻详解
  • 访问控制2php相关配置
  • java web添加mysql过程中遇到的错误及解决办法
  • 软件架构师 如何工作
  • php的引用
  • [笔记] php常见简单功能及函数
  • AHK 中 = 和 == 等比较运算符的用法
  • Octave 入门
  • RxJS: 简单入门
  • 初识MongoDB分片
  • 从0到1:PostCSS 插件开发最佳实践
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 如何借助 NoSQL 提高 JPA 应用性能
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • Python 之网络式编程
  • ​520就是要宠粉,你的心头书我买单
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • # Java NIO(一)FileChannel
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (1)常见O(n^2)排序算法解析
  • (二)换源+apt-get基础配置+搜狗拼音
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (分享)自己整理的一些简单awk实用语句
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (强烈推荐)移动端音视频从零到上手(下)
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (算法)N皇后问题
  • (五)MySQL的备份及恢复
  • (已解决)什么是vue导航守卫
  • (转)nsfocus-绿盟科技笔试题目
  • .bat批处理出现中文乱码的情况
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .Net Core 中间件验签
  • .NET Core引入性能分析引导优化
  • .net Stream篇(六)
  • .NET 动态调用WebService + WSE + UsernameToken
  • .NET简谈设计模式之(单件模式)
  • .net开发引用程序集提示没有强名称的解决办法
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .NET中统一的存储过程调用方法(收藏)
  • ??在JSP中,java和JavaScript如何交互?
  • @Autowired和@Resource装配