如果要想在Java之中实现多线程的定义,那么就需要有一个专门的线程主体类进行线程的执行任务的定义,而这个主体类的定义是有要求的,必须实现特定的接口或者继承特定的父类才可以完成。

1. 继承Thread类实现多线程

Java里面提供有一个有java.lang.Thread的程序类,那么一个类只要继承了此类就表示这个类为线程的主体类,但是并不是说这个类就可以直接实现多线程处理了,因为还需要覆写Thread类中提供的一个run()方法(public void run​()),而这个方法就属于线程的主方法。

范例:多线程主体类

  class MyThread extends Thread {    // 线程的主体类
private String title ;
public MyThread(String title) {
this.title = title ;
}
@Override
public void run() { // 线程的主体方法
for (int x = 0 ; x < 10 ; x ++) {
System.out.println(this.title + "运行,x = " + x);
}
}
}

MyThread

多线程要执行的功能都应该在run()方法中进行定义。需要说明的是:在正常情况下如果要想使用一个类中的方法,那么肯定要产生实例化对象,而后去调用类中提供的方法,但是run()方法是不能够被直接调用的,因为这里面牵扯到一个操作系统的资源调度问题,所以要想启动多线程必须使用start()方法完成(public void start​())。

范例:多线程启动

 public class ThreadDemo {

     public static void main(String[] args) {
// TODO Auto-generated method stub
new MyThread("线程A").start();
new MyThread("线程A").start();
new MyThread("线程A").start();
} }

ThreadDemo

通过此时的调用可以发现,虽然调用了是start()方法,但是最终执行的是run()方法,并且所有的线程对象都是交替执行的。

疑问?为什么多线程的启动不直接使用run()方法而必须使用Thread类中的start()方法呢?如果要想清楚这个问题,最好的做法是查看一下start()方法的实现操作,可以直接通过源代码观察。

//jdk1.8.0_201中的源代码
public synchronized void start() {
if (threadStatus != 0) //判断线程的状态
throw new IllegalThreadStateException(); //抛出一个异常
group.add(this);
boolean started = false;
try {
start0(); //在start()方法里面调用了start0()方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
} private native void start0();//只定义了方法名称,但是没有实现

  发现在start()方法里面会抛出一个“IllegalThreadStateException”异常类对象,但是整个的程序并没有使用throws或者是明确的try……catch处理,因此该异常一定是RuntimeException的子类,每一个线程类的对象只允许启动一次,如果重复启动则就抛出此异常。例如:下面的代码就会抛出异常。

public class ThreadDemo {
  public static void main(String[] args) {
    MyThread mt = new MyThread("线程A") ;
    mt.start() ;
    mt.start() ; // 重复进行了线程的启动
  }
}

Exception in thread "main"  java.lang.IllegalThreadStateException

在Java程序执行的过程之中考虑到对于不同层次开发者的需要,所以其支持有本地的操作系统函数调用,而这项技术就被称为JNI(Java Native Inteface)技术,但是Java开发过程之中并不推荐这样使用,利用这项技术可以使用一些操作系统提供底层函数进行一些特殊的处理,而在Thread类里面提供的start0()就表示需要将此方法依赖于不同的操作系统实现。   任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类中的start()方法。

2. 基于Runnable接口实现多线程

虽然可以通过thread类的继承来实现多线程的定义,但是在Java程序里面对于继承永远都是存在有单继承局限的,所以在Java里面又提供有第二种多线程的主体定义结构形式:实现java.lang.Runnable接口,此接口定义如下:

@FunctionalInterface

public interface Runnable{ //JDK1.8引入了Lambda表达式之后就变为了函数式接口

  public void run​()

}

范例:通过Runnable实现多线程的主体类

 class MyThread implements Runnable {    // 线程的主体类
private String title ;
public MyThread(String title) {
this.title = title ;
}
@Override
public void run() { // 线程的主体方法
for (int x = 0 ; x < 10 ; x ++) {
System.out.println(this.title + "运行,x = " + x);
}
}
}

MyThread

但是此时由于不在继承Thread父类了,那么对于此时的MyThread类中也就不再支持有start()这个继承的方法,可是如果不使用Thread.start()方法是无法进行多线程启动的,那么这个时候就需要去观察一下Thread类提供的构造方法:

·构造方法:public Thread​(Runnable target);

    ·构造方法:public Thread(Runnable target,String name);

范例:启动多线程

 public class ThreadDemo {
public static void main(String[] args) {
Thread threadA = new Thread(new MyThread("线程对象A")) ;
Thread threadB = new Thread(new MyThread("线程对象B")) ;
Thread threadC = new Thread(new MyThread("线程对象C")) ;
threadA.start(); // 启动多线程
threadB.start(); // 启动多线程
threadC.start(); // 启动多线程
}
}

ThreadDemo

这个时候的多线程实现里面可以发现,由于只是实现了Runnable接口对象,所以此时线程主体类上就不再有单继承局限了,那么这样的设计才是一个标准型的设计。

在以后的开发之中对于多线程的实现,优先考虑的就是Runnable接口实现,并且永恒都是通过Thread类对象启动多线程。这也是符合java的设计思想。

3. Thread与Runnable关系

经过一系列的分析之后可以发现,在多线程的实现过程之中已经有了两种做法:Thread类、Runnable接口,如果从代码的结构本身来讲肯定使用Runnable是最方便的,因为其可以避免单继承的局限,同时也可以更好的进行功能的扩充。

但是从结构上也需要来观察Thread与Runnable的联系,打开Thread类定义:

public class Threadextends Objectimplements Runnable{}

发现现在Thread类也是Runnable接口的子类,那么在之前继承Thread类的时候实际上覆写的还是Runnable接口的run()方法,也是此时来观察一下程序的类结构:

 class MyThread implements Runnable {    // 线程的主体类
private String title ;
public MyThread(String title) {
this.title = title ;
}
@Override
public void run() { // 线程的主体方法
for (int x = 0 ; x < 10 ; x ++) {
System.out.println(this.title + "运行,x = " + x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread threadA = new Thread(new MyThread("线程对象A")) ;
Thread threadB = new Thread(new MyThread("线程对象B")) ;
Thread threadC = new Thread(new MyThread("线程对象C")) ;
threadA.start(); // 启动多线程
threadB.start(); // 启动多线程
threadC.start(); // 启动多线程
}
}

ThreadDemo

多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由Thread类来处理。

在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,当通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存,在start()方法执行的时候回调用Thread类中的run()方法,而这个run()方法去调用Runnable接口子类被覆写过的run()方法。

多线程开发的本质上是在于多个线程可以进行同一资源的抢占,那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的。

范例:利用卖票程序来实现多个线程的资源并发访问

 package com.pluto.ticket;

 public class MyThread implements Runnable {

     private int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
for(int x=0;x<100;x++){
if(this.ticket>0){
System.out.println("卖票,ticket="+this.ticket--);
}
}
} }
package com.pluto.ticket;
public class ThreadDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread mt = new MyThread();
new Thread(mt,"线程1").start(); // 第一个线程启动
new Thread(mt,"线程2").start(); // 第二个线程启动
new Thread(mt,"线程3").start(); // 第三个线程启动
} }

ThreadDemo

4. Callable实现多线程

从最传统的开发来讲如果要进行多线程的实现肯定依靠的就是Runnable,但是Runnable接口有一个缺点:当线程执行完毕之后无法获取一个返回值,所以从JDK1.5之后就提出一个新的线程实现接口:java.util.concurrent.Callable接口,首先来观察这个接口的定义:

@FunctionalInterface
public interface Callable<V>{
public V call​() throws Exception;
}

可以发现Callable定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的好处是可以避免向下转型所带来的安全隐患。

Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。

范例:使用Callable实现多线程处理

 import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
for (int x = 0 ; x < 10 ; x ++) {
System.out.println("********* 线程执行、x = " + x);
}
return "线程执行完毕。";
}
}
public class CallableDemo{
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask<>(new MyThread()) ;
new Thread(task).start();
System.out.println("【线程返回数据】" + task.get());
}
}

CallableDemo

解释Runnable与Callable的区别?
·Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
·java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值;
·java.util.concurrent.Callable接口提供有call()方法,可以有返回值;

5. 线程运行状态

对于多线程的开发而言,编写程序的过程之中总是按照:定义线程主体类,而后通过Thread类进行线程的启动,但是并不意味着你调用了start()方法,线程就已经开始运行了,因为整体的线程处理有自己的一套运行的状态。

1、任何一个线程的徐爱那个都应该使用Thread类进行封装,所以线程的启动使用的是start(),但是启动的时候实际上若干个线程都将进入到一种就绪状态,现在并没有执行。

2、进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间之后就需要让出资源,而后这个线程就将进入到阻塞状态,随后重新回归到就绪状态;

3、当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态;

Java 多线程编程——多线程的更多相关文章

  1. Linux多线程编程——多线程与线程同步

    多线程 使用多线程好处: 一.通过为每种事件类型的处理单独分配线程,可以简化处理异步事件的代码,线程处理事件可以采用同步编程模式,启闭异步编程模式简单 二.方便的通信和数据交换 由于进程之间具有独立的 ...

  2. Java并发编程(多线程)中的相关概念

    众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试中必问的题,一个好的Java程序员是必须对并发编程这块有所了解的. 并发必须知道的概念 在深入学习并发编程之前,我们需要了解几个基本 ...

  3. java网络编程——多线程数据收发并行

    基本介绍与思路 收发并行 前一篇博客中,完成了客户端与服务端的简单TCP交互,但这种交互是触发式的:客户端发送一条消息,服务端收到后再回送一条.没有做到收发并行.收发并行的字面意思很容易理解,即数据的 ...

  4. Java并发编程--多线程中的join方法详解

    Java Thread中, join()方法主要是让调用该方法的thread在完成run方法里面的部分后, 再执行join()方法后面的代码 例如:定义一个People类,run方法是输出姓名年龄. ...

  5. Java多线程编程(1)--Java中的线程

    一.程序.进程和线程   程序是一组指令的有序集合,也可以将其通俗地理解为若干行代码.它本身没有任何运行的含义,它只是一个静态的实体,它可能只是一个单纯的文本文件,也有可能是经过编译之后生成的可执行文 ...

  6. 廖雪峰Java11多线程编程-3高级concurrent包-4Concurrent集合

    Concurrent 用ReentrantLock+Condition实现Blocking Queue. Blocking Queue:当一个线程调用getTask()时,该方法内部可能让给线程进入等 ...

  7. Windows多线程编程入门

    标签(空格分隔): Windows multithread programming 多线程 并发 编程 背景知识 在开始学习多线程编程之前,先来学习下进程和线程 进程 进程是指具有一定独立功能的程序在 ...

  8. Java多线程编程核心技术---学习分享

    继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...

  9. Java—多线程编程

    一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程.一个线程不能独立的存 ...

随机推荐

  1. SDL2学习(二):常用枚举值和函数

    1. 高频枚举值或结构体 1.1 SDL_WindowFlags /** * \brief The flags on a window * * \sa SDL_GetWindowFlags() */ ...

  2. Ninja——小而快的构建系统

    介绍 Ninja 是Google的一名程序员推出的注重速度的构建工具,一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度 ...

  3. HTML与CSS学习笔记(2)

    1.CSS背景样式? background-color 背景色 background-image 背景图 url(背景地址) 默认:会水平垂直铺满背景图 background-repeat 平铺方式 ...

  4. python27期day06:小数据池、深浅拷贝、集合、作业题。

    0.pycharm是代码块.黑窗口是小数据池.如下图: 1.驻留机制(长得像的共用一个内存地址)分小数据池缓存机制:后期开发时能明确知道.为什么不能正常使用.把经常用的东西放入规则(黑窗口)里. 数字 ...

  5. zz姚班天才少年鬲融凭非凸优化研究成果获得斯隆研究奖

    姚班天才少年鬲融凭非凸优化研究成果获得斯隆研究奖 近日,美国艾尔弗·斯隆基金会(The Alfred P. Sloan Foundation)公布了2019年斯隆研究奖(Sloan Research ...

  6. LeetCode 145. Binary Tree Postorder Traversal二叉树的后序遍历 (C++)

    题目: Given a binary tree, return the postorder traversal of its nodes' values. Example: Input: [1,nul ...

  7. MySQL实战45讲学习笔记:第九讲

    一.今日内容概要 今天的正文开始前,我要特意感谢一下评论区几位留下高质量留言的同学.用户名是 @某.人 的同学,对文章的知识点做了梳理,然后提了关于事务可见性的问题,就是先启动但是后提交的事务,对数据 ...

  8. java web开发入门六(spring mvc)基于intellig idea

    spring mvc ssm=spring mvc+spring +mybatis spring mvc工作流程 1A)客户端发出http请求,只要请求形式符合web.xml文件中配置的*.actio ...

  9. JAVA8之StringJoiner

    作用:运用了StringBuilder的一个拼接字符串的封装处理 示例: StringJoiner sj = new StringJoiner("-", "[" ...

  10. golang web 方案

    概要 开发 web 框架 数据库 认证 日志 配置 静态文件服务 上传/下载 发布 docker 打包 部署中遇到的问题 时区问题 概要 轻量的基于 golang 的 web 开发实践. golang ...