这篇是Java多线程基本用法的一个总结。

本篇文章会从一下几个方面来说明Java多线程的基本用法:

  1. 如何使用多线程
  2. 如何得到多线程的一些信息
  3. 如何停止线程
  4. 如何暂停线程
  5. 线程的一些其他用法

所有的代码均可以在char01

如何使用多线程

启动线程的两种方式

Java 提供了2种方式来使用多线程, 一种是编写一个类来继承Thread,然后覆写run方法,然后调用start方法来启动线程。这时这个类就会以另一个线程的方式来运行run方法里面的代码。另一种是编写一个类来实现Runnable接口,然后实现接口方法run,然后创造一个Thread对象,把实现了Runnable接口的类当做构造参数,传入Thread对象,最后该Thread对象调用start方法。

这里的start方法是一个有启动功能的方法,该方法内部回调run方法。所以,只有调用了start方法才会启动另一个线程,直接调用run方法,还是在同一个线程中执行run,而不是在另一个线程执行run

此外,start方法只是告诉虚拟机,该线程可以启动了,也就说该线程在就绪的状态,但不代表调用start就立即运行了,这要等待JVM来决定什么时候执行这个线程。也就是说,如果有两个线程A,B ,A先调用start,B后调用start,不代表A线程先运行,B线程后运行。这都是由JVM决定了,可以认为是随机启动。

下面我们用实际的代码,来说明两种启动线程的方式:

第一种,继承Thread

public class ExampleThread extends Thread{

    @Override
public void run() {
super.run();
System.out.println("这是一个继承自Thread的ExampleThread");
}
}

测试的代码可以看test目录下的ExampleThreadTest

另一种,实现了Runnable接口

public class ExampleRunable  implements Runnable{

    public void run() {
System.out.println("这是实现Runnable接口的类");
}
}

测试的代码可以看test目录下的ExampleRunableTest类。

如何得到多线程的一些信息

我们在启动多线程之后,希望能通过一些API得到启动的线程的一些信息。JDK给我们提供了一个Thread类的方法来得到线程的一些信息。

  • 线程的名字 —— getName()
  • 线程的ID —— getId()
  • 线程是否存活 —— isAlive()

得到线程的名字

这些方法是属于Thread的内部方法,所以我们可以用两种方式调用这些方法,一个是我们的类继承Thread来使用多线程的时候,可以用过this来调用。另一种是通过Thread.currentThread() 来调用这些方法。但是这两个方法在不同的使用场景下是有区别的。

我们先简单来看两个方法的使用。

第一个Thread.currentThread()的使用,代码如下:

public class ExampleCurrentThread extends Thread{

    public ExampleCurrentThread(){
System.out.println("构造方法的打印:" + Thread.currentThread().getName());
} @Override
public void run() {
super.run();
System.out.println("run方法的打印:" + Thread.currentThread().getName());
}
}

测试的代码如下:

public class ExampleCurrentThreadTest extends TestCase {

    public void testInit() throws Exception{
ExampleCurrentThread thread = new ExampleCurrentThread();
} public void testRun() throws Exception {
ExampleCurrentThread thread = new ExampleCurrentThread();
thread.start();
Thread.sleep(1000);
}
}

结果如下:

构造方法的打印:main
run方法的打印:Thread-0
构造方法的打印:main

为什么我们在ExampleCurrentThread内部用Thread.currentThread()会显示构造方法的打印是main,是因为Thread.currentThread()返回的是代码段正在被那个线程调用的信息。这里面很显然构造方法是被main线程执行的,而run方法是被我们自己启动的线程执行的,因为没有给他起名字,所以默认是Thread-0。

接下来,我们在看一看继承自Thread,用this调用。

public class ComplexCurrentThread extends Thread{

    public ComplexCurrentThread() {
System.out.println("begin=========");
System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName()); System.out.println("this.getName()=" + this.getName());
System.out.println("end===========");
} @Override
public void run() {
super.run();
System.out.println("run begin=======");
System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("run end==========");
}
}

测试代码如下:

public class ComplexCurrentThreadTest extends TestCase {
public void testRun() throws Exception {
ComplexCurrentThread thread = new ComplexCurrentThread();
thread.setName("byhieg");
thread.start(); Thread.sleep(3000);
}
}

结果如下:

begin=========
Thread.currentThread().getName=main
this.getName()=Thread-0
end===========
run begin=======
Thread.currentThread().getName=byhieg
this.getName()=byhieg
run end==========

首先在创建对象的时候,构造器还是被main线程所执行,所以Thread.currentThread()得到的就是Main线程的名字,但是this方法指的是调用方法的那个对象,也就是ComplexCurrentThread的线程信息,还没有setName,所以是默认的名字。然后run方法无论是Thread.currentThread()还是this返回的都是设置了byhieg名字的线程信息。

所以Thread.currentThread指的是具体执行这个代码块的线程信息。构造器是main执行的,而run方法则是哪个线程start,哪个线程执行run。这么看来,this能得到的信息是不准确的,因为如果我们在run中执行了this.getName(),但是run方法却是由另一个线程start的,我们是无法通过this.getName得到运行run方法的新城的信息的。而且只有继承了Thread的类才能有getName等方法,这对于Java没有多继承的特性语言来说,是个灾难。所有后面凡是要得到线程的信息,我们都用Thread.currentThread()来调用API。

得到线程的ID

调用getID取得线程的唯一标识。这个和上面的getName用法一致,没什么好说的,可以直接看ExampleIdThread和他的测试类ExampleIdThreadTest

所有的代码均可以在char01

判断线程是否存活

方法isAlive()的作用是测试线程是否处于活动状态。所谓活动状态,就是线程已经启动但是没有终止。即该线程start之后,被认为是存活的。

我们看一下具体的例子:

public class AliveThread extends Thread{

    @Override
public void run() {
super.run();
System.out.println("run方法中是否存活" + " " + Thread.currentThread().isAlive());
}
}

测试方法如下:

public class AliveThreadTest extends TestCase {
public void testRun() throws Exception {
AliveThread thread = new AliveThread();
System.out.println("begin == " + thread.isAlive());
thread.start();
Thread.sleep(1000);
System.out.println("end ==" + thread.isAlive()); Thread.sleep(3000);
}
}

结果如下:

begin == false
run方法中是否存活 true
end ==false

我们可以发现在start之前,该线程被认为是没有存活,然后run的时候,是存活的,等run方法执行完,又被认为是不存活的。

如何停止线程

判断线程是否终止

JDK提供了一些方法来判断线程是否终止 —— isInterrupted()和interrupted()

停止线程的方式

这个是得到线程信息中比较重要的一个方法了,因为这个和终止线程的方法相关联。先说一下终止线程的几种方式:

  1. 等待run方法执行完
  2. 线程对象调用stop()
  3. 线程对象调用interrupt(),在该线程的run方法中判断是否终止,抛出一个终止异常终止。
  4. 线程对象调用interrupt(),在该线程的run方法中判断是否终止,以return语句结束。

第一种就不说了,第二种stop()方法已经废弃了,因为可能会产生如下原因:

  1. 强制结束线程,该线程应该做的清理工作,无法完成。
  2. 强制结束线程,该线程已操作的加锁对象强制解锁,造成数据不一致。

    具体的例子可以看StopLockThread以及他的测试类StopLockThreadTest

第三种,是目前推荐的终止方法,调用interrupt,然后在run方法中判断是否终止。判断终止的方式有两种,一种是Thread类的静态方法interrupted(),另一种是Thread的成员方法isInterrupted()。这两个方法是有所区别的,第一个方法是会自动重置状态的,如果连续两次调用interrupted(),第一次如果是false,第二次一定是true。而isInterrupted()是不会的。

例子如下:

public class ExampleInterruptThread extends Thread{

    @Override
public void run() {
super.run();
try{
for(int i = 0 ; i < 50000000 ; i++){
if (interrupted()){
System.out.println("已经是停止状态,我要退出了");
throw new InterruptedException("停止.......");
}
System.out.println("i=" + (i + 1));
}
}catch (InterruptedException e){
System.out.println("顺利停止");
} }
}

测试的代码如下:

public class ExampleInterruptThreadTest extends TestCase {
public void testRun() throws Exception {
ExampleInterruptThread thread = new ExampleInterruptThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}

第四种方法和第三种一样,唯一的区别就是将上面的代码中的抛出异常换成return,个人还是喜欢抛出异常,这里处理的形式就比较多,比如打印信息,处理资源关闭或者捕捉之后再重新向上层抛出。

注意一点,我们上面抛出的异常是InterruptedException,这里简单说一下可能产生这个异常的原因,在原有线程sleep的情况下,调用interrupt终止线程,或者先终止线程,再让线程sleep。

如何暂停线程

在JDK中提供了以下两个方法用来暂停线程和恢复线程。

  • suspend()——暂停线程
  • resume()——恢复线程

这两个方法和stop方法一样是被废弃的方法,其用法和stop一样,暴力的暂停线程和恢复线程。这两个方法之所以是废弃的主要由以下两个原因:

  1. 线程持有锁定的公共资源的情况下,一旦被暂停,则公共资源无法被其他线程所持有。
  2. 线程强制暂停,导致该线程执行的操作没有执行完全,这时访问该线程的数据会出现数据不一致。

线程的一些其他用法

线程的其他的一些基础用法如下:

  1. 线程让步
  2. 设置线程的优先级
  3. 守护线程

线程让步

JDK提供yield()方法来让线程放弃当前的CPU资源,将它让给其他的任务去占用CPU时间,但是这也是随机的事情,有可能刚放弃资源,又马上占用时间片了。

具体的例子可以参考ExampleYieldThread以及他的测试类ExampleYieldThreadTest

设置线程的优先级

我们可以设置线程的优先级来让CPU尽可能的将执行的资源给优先级高的线程。Java设置了1-10这10个优先级,又有三个静态变量来提供三个优先级:

    /**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1; /**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5; /**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

我们可以通过setPriority来设置线程的优先级,可以直接传入上诉三个静态变量,也可以直接传入1-10的数字。设置后线程就会有不同的优先级。如果我们不设置优先级,会是什么情况?

线程的优先级是有继承的特性,如果我们在A线程中启动了B线程,则AB具有相同的优先级。一般我们在main线程中启动线程,就和main线程有一致的优先级。main线程的优先级默认是5。

下面说一下优先级的一些规则:

  1. 优先级高的线程一般会比优先级低的线程获得更多的CPU资源,但是不代表优先级高的任务一定先于优先级低的任务先执行完。因为不同优先级的线程中run方法内容可能不一样。
  2. 优先级高的线程一定会比优先级低的线程执行的快。如果两个线程是一样的run方法,但是优先级不一样,确实优先级高的线程先执行完。

线程守护

JDK中提供setDaemon的方法来设置一个线程变成守护线程。守护线程的特点是其他非守护线程执行完,守护线程就自动销毁,典型的例子是GC回收器。

具体可以看ExampleDaemonThreadExampleDaemonThreadTest

总结

这篇文章主要总结了Java线程的一些基本的用法,关于线程安全,同步的知识,放到了第二篇。

Java多线程基础知识篇的更多相关文章

  1. Java 多线程——基础知识

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  2. Java多线程基础知识笔记(持续更新)

    多线程基础知识笔记 一.线程 1.基本概念 程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 进程(process):是程序的一次执行过程,或是 ...

  3. Java多线程基础知识总结

    2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...

  4. JAVA多线程基础知识(一)

    一. 基础知识 要了解多线程首先要知道一些必要的概念,如进程,线程等等.开发多线程的程序有利于充分的利用系统资源(CPU资源),使你的程序执行的更快,响应更及时. 1. 进程,一般是指程序或者任务的执 ...

  5. Java多线程基础知识总结笔记

    本篇笔记记录一些在Java多线程编程中常见的关键字,比较简单和基础的就不写太详细了. 一.Thread类(其实也是应用了Runnable接口)和Runnable接口(只有一个run方法,应用该类必须重 ...

  6. Java多线程基础知识例子

    一.管理 1.创建线程 Thread public class Main { public static void main(String[] args) { MyThread myThread = ...

  7. Java多线程基础知识(二)

    一. Java线程具有6种状态  NEW 初始状态,线程被创建,但是还没有调用start方法. RUNNABLE 运行状态,java线程将操作系统中的就绪和运行两种状态笼统的称作进行中. BLOCKE ...

  8. Java多线程-基础知识

    一. 进程是执行中的程序,程序是静态的(我们写完以后不运行就一直放在那里),进程是执行中的程序,是动态概念的.一个进程可以有多个线程. 二. 多线程包含两个或两个以上并发运行的部分,把程序中每个这样并 ...

  9. Java多线程基础知识(五)

    一. Java中的13个原子操作类 在Jdk1.5中,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全的更新一个变量的方式. 1. 原子更新基本类型类 AtomicBoolean : 原子更 ...

随机推荐

  1. 关于textview显示特殊符号居中的问题

    话说这是2017年的第一篇博客,也是一篇技术博客.先从简单的一篇解决问题开始吧,千里之行,始于足下! ------------------------------------------------- ...

  2. Python标准模块--Iterators和Generators

    1 模块简介 当你开始使用Python编程时,你或许已经使用了iterators(迭代器)和generators(生成器),你当时可能并没有意识到.在本篇博文中,我们将会学习迭代器和生成器是什么.当然 ...

  3. 《Django By Example》第一章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:本人目前在杭州某家互联网公司工作, ...

  4. Android学习探索之Java 8 在Android 开发中的应用

    前言: Java 8推出已经将近2年多了,引入很多革命性变化,加入了函数式编程的特征,使基于行为的编程成为可能,同时减化了各种设计模式的实现方式,是Java有史以来最重要的更新.但是Android上, ...

  5. jQuery动画-圣诞节礼物

    ▓▓▓▓▓▓ 大致介绍 下午看到了一个送圣诞礼物的小动画,正好要快到圣诞节了,就动手模仿并改进了一些小问题 原地址:花式轮播----圣诞礼物传送 思路:动画中一共有五个礼物,他们平均分布在屏幕中,设置 ...

  6. Mac OS、Ubuntu 安装及使用 Consul

    Consul 概念(摘录): Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,比如 Airbnb 的 SmartStac ...

  7. CGI与FastCGI nginx+PHP-FPM

    本文转载自CGI与FastCGI 1.当我们在谈到cgi的时候,我们在讨论什么 最早的Web服务器简单地响应浏览器发来的HTTP请求,并将存储在服务器上的HTML文件返回给浏览器,也就是静态html. ...

  8. 如何用Java类配置Spring MVC(不通过web.xml和XML方式)

    DispatcherServlet是Spring MVC的核心,按照传统方式, 需要把它配置到web.xml中. 我个人比较不喜欢XML配置方式, XML看起来太累, 冗长繁琐. 还好借助于Servl ...

  9. 初识JavaScript

    JavaScript ECMA-262: 变量,函数,对象,数据类型....唯独没有输入和输出. Javascript:包含 ECMA-262,核心 BOM 浏览器对象模型, DOM 文档对象模型 什 ...

  10. 超千个节点OpenStack私有云案例(1):CERN 5000+ 计算节点私有云

    CERN:欧洲核子研究组织 本文根据以下几篇文章整理而来: https://www.openstack.org/summit/tokyo-2015/videos/presentation/unveil ...