前言

上一篇文章中,回顾了Java的集合。而在本篇文章中主要介绍多线程的相关知识。主要介绍的知识点为线程的介绍、多线程的使用、以及在多线程中使用的一些方法。

线程和进程

线程

表示进程中负责程序执行的执行单元,依靠程序进行运行。线程是程序中的顺序控制流,只能使用分配给程序的资源和环境。

进程

表示资源的分配和调度的一个独立单元,通常表示为执行中的程序。一个进程至少包含一个线程。

进程和线程的区别

  1. 进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
  2. 进程是资源分配和拥有的单位,而同一个进程内的线程共享进程的资源;
  3. 线程是处理器调度的基本单位,但进程不是;

生命周期

线程和进程一样分为五个阶段:创建就绪运行阻塞终止

  • 新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start() 这个线程。
  • 就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

可以用下述图来进行理解线程的生命周期:

 

注:上述图来自http://www.runoob.com/wp-content/uploads/2014/01/java-thread.jpg。

在了解了线程和进程之后,我们再来简单的了解下单线程和多线程。

单线程

程序中只存在一个线程,实际上主方法就是一个主线程。

多线程

多线程是指在同一程序中有多个顺序流在执行。 简单的说就是在一个程序中有多个任务运行。

那么在什么情况下用多线程呢?

一般来说,程序中有两个以上的子系统需要并发执行的,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出高效的程序。

那么是不是使用很多线程就能提高效率呢?

不一定的。因为程序中上下文的切换开销也很重要,如果创建了太多的线程,CPU

花费在上下文的切换的时间将多于执行程序的时间!这时是会降低程序执行效率的。

所以有效利用多线程的关键是理解程序是并发执行而不是串行执行的。

线程的创建

一般来说,我们在对线程进行创建的时候,一般是继承Thread 类或实现Runnable 接口。其实还有一种方式是实现 Callable接口,然后与Future 或线程池结合使用, 类似于Runnable接口,但是就功能上来说更为强大一些,也就是被执行之后,可以拿到返回值。

这里我们分别一个例子使用继承Thread 类、实现Runnable 接口和实现Callable接口与Future结合来进行创建线程。

代码示例:

注:线程启动的方法是start而不是run。因为使用start方法整个线程处于就绪状态,等待虚拟机来进行调度。而使用run,也就是当作了一个普通的方法进行启动,这样虚拟机不会进行线程调度,虚拟机会执行这个方法直到结束后自动退出。

代码示例:

public class Test {
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
threadTest.start(); RunalbeTest runalbeTest=new RunalbeTest();
Thread thread=new Thread(runalbeTest);
thread.start(); CallableTest callableTest=new CallableTest();
FutureTask<Integer> ft = new FutureTask<Integer>(callableTest);
Thread thread2=new Thread(ft);
thread2.start();
try {
System.out.println("返回值:"+ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("这是一个Thread的线程!");
}
} class RunalbeTest implements Runnable{
@Override
public void run() {
System.out.println("这是一个Runnable的线程!");
}
} class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("这是一个Callable的线程!");
return 2;
}
}

运行结果:

	这是一个Thread的线程!
这是一个Runnable的线程!
这是一个Callable的线程!
返回值:2

通过上述示例代码中,我们发现使用继承 Thread 类的方式创建线程时,编写最为简单。而使用Runnable、Callable 接口的方式创建线程的时候,需要通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用start方法来运行线程代码。顺便说下,其实Thread类实际上也是实现了Runnable接口的一个类。

但是在这里,我推荐大家创建单线程的时候使用继承 Thread 类方式创建,多线线程的时候使用Runnable、Callable 接口的方式来创建创建线程。

至于为什么呢?在下面中的描述已给出理由。

  • 继承 Thread 类创建的线程,可以直接使用Thread类中的方法,比如休眠直接就可以使用sleep方法,而不必在前面加个Thread;获取当前线程Id,只需调用getId就行,而不必使用Thread.currentThread().getId() 这么一长串的代码。但是使用Thread 类创建的线程,也有其局限性。比如资源不能共享,无法放入线程池中等等。
  • 使用Runnable、Callable 接口的方式创建的线程,可以实现资源共享,增强代码的复用性,并且可以避免单继承的局限性,可以和线程池完美结合。但是也有不好的,就是写起来不太方便,使用其中的方法不够简介。

总的来说就是,单线程建议用继承 Thread 类创建,多线程建议- 使用Runnable、Callable 接口的方式创建。

线程的一些常用方法

yield

使用yield方法表示暂停当前正在执行的线程对象,并执行其他线程。

代码示例:

public class YieldTest {
public static void main(String[] args) {
Test1 t1 = new Test1("张三");
Test1 t2 = new Test1("李四");
new Thread(t1).start();
new Thread(t2).start();
}
} class Test1 implements Runnable {
private String name;
public Test1(String name) {
this.name=name;
}
@Override
public void run() {
System.out.println(this.name + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println(""+this.name + "-----" + i);
// 当为3的时候,让出资源
if (i == 3) {
Thread.yield();
}
}
System.out.println(this.name + " 线程运行结束!");
}
}

执行结果一:

	张三 线程运行开始!
张三-----1
张三-----2
张三-----3
李四 线程运行开始!
李四-----1
李四-----2
李四-----3
张三-----4
张三-----5
张三 线程运行结束!
李四-----4
李四-----5
李四 线程运行结束!

执行结果二:

张三 线程运行开始!
李四 线程运行开始!
李四-----1
李四-----2
李四-----3
张三-----1
张三-----2
张三-----3
李四-----4
李四-----5
李四 线程运行结束!
张三-----4
张三-----5
张三 线程运行结束!

上述中的例子我们可以看到,启动两个线程之后,哪个线程先执行到3,就会让出资源,让另一个线程执行。

在这里顺便说下,yieldsleep的区别。

  • yield: yield只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
  • sleep:sleep使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;

join

使用join方法指等待某个线程终止。也就是说当子线程调用了join方法之后,后面的代码只有等待该线程执行完毕之后才会执行。

如果不好理解,这里依旧使用一段代码来进行说明。

这里我们创建两个线程,并使用main方法执行。顺便提一下,其实main方法也是个线程。如果直接执行的话,可能main方法执行完毕了,子线程还没执行完毕,这里我们就让子线程使用join方法使main方法最后执行。

代码示例:

public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "主线程开始运行!");
Test2 t1=new Test2("A");
Test2 t2=new Test2("B");
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
} } class Test2 extends Thread{
public Test2(String name) {
super(name);
}
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}

执行结果:

	main主线程开始运行!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
main主线程运行结束!

上述示例中的结果显然符合我们的预期。

priority

使用setPriority表示设置线程的优先级。

每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式

  • static int MAX_PRIORITY 线程可以具有的最高优先级,取值为10。
  • static int MIN_PRIORITY 线程可以具有的最低优先级,取值为1。
  • static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。

但是设置优先级并不能保证线程一定先执行。我们可以通过一下代码来验证。

代码示例:

public class PriorityTest {
public static void main(String[] args) {
Test3 t1 = new Test3("张三");
Test3 t2 = new Test3("李四");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
} class Test3 extends Thread {
public Test3(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}

执行结果一:

李四 线程运行开始!
子线程李四运行 : 1
张三 线程运行开始!
子线程张三运行 : 1
子线程张三运行 : 2
子线程李四运行 : 2
子线程李四运行 : 3
子线程李四运行 : 4
子线程张三运行 : 3
子线程李四运行 : 5
李四 线程运行结束!
子线程张三运行 : 4
子线程张三运行 : 5
张三 线程运行结束!

执行结果二:

张三 线程运行开始!
子线程张三运行 : 1
李四 线程运行开始!
子线程李四运行 : 1
子线程张三运行 : 2
子线程张三运行 : 3
子线程李四运行 : 2
子线程张三运行 : 4
子线程李四运行 : 3
子线程张三运行 : 5
子线程李四运行 : 4
张三 线程运行结束!
子线程李四运行 : 5
李四 线程运行结束!

执行结果三:

李四 线程运行开始!
子线程李四运行 : 1
张三 线程运行开始!
子线程张三运行 : 1
子线程李四运行 : 2
子线程李四运行 : 3
子线程李四运行 : 4
子线程张三运行 : 2
子线程张三运行 : 3
子线程张三运行 : 4
子线程李四运行 : 5
子线程张三运行 : 5
李四 线程运行结束!
张三 线程运行结束!

线程中一些常用的方法

线程中还有许多方法,但是这里并不会全部细说。只简单的列举了几个方法使用。更多的方法使用可以查看相关的API文档。这里我也顺便总结了一些关于这些方法的描述。

  1. sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);不会释放对象锁。
  2. join:指等待t线程终止。
  3. yield:暂停当前正在执行的线程对象,并执行其他线程。
  4. setPriority:设置一个线程的优先级。
  5. interrupt:一个线程是否为守护线程。
  6. wait:强迫一个线程等待。它是Object的方法,也常常和sleep作为比较。需要注意的是wait会释放对象锁,让其它的线程可以访问;使用wait必须要进行异常捕获,并且要对当前所调用,即必须采用synchronized中的对象。
  7. isAlive: 判断一个线程是否存活。
  8. activeCount: 程序中活跃的线程数。
  9. enumerate: 枚举程序中的线程。
  10. currentThread: 得到当前线程。
  11. setDaemon: 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)。
  12. setName: 为线程设置一个名称。
  13. notify(): 通知一个线程继续运行。它也是Object的一个方法,经常和wait方法一起使用。

结语

其实这篇文章很久之前都已经打好草稿了,但是由于各种原因,只到今天才写完。虽然也只是简单的介绍了一下多线程的相关知识,也只能算个入门级的教程吧。不过写完之后,感觉自己又重新复习了一遍多线程,对多线程的理解又加深了一些。

话已尽此,不在多说。

原创不易,如果感觉不错,希望给个推荐!您的支持是我写作的最大动力!

参考:https://blog.csdn.net/evankaka/article/details/44153709#t1

版权声明:

作者:虚无境

博客园出处:http://www.cnblogs.com/xuwujing

CSDN出处:http://blog.csdn.net/qazwsxpcm    

个人博客出处:http://www.panchengming.com

Java基础知识回顾之五 ----- 多线程的更多相关文章

  1. Java基础知识回顾之七 ----- 总结篇

    前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数 ...

  2. java基础知识回顾之---java String final类普通方法

    辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /*     * 按照面向对象的思想对字符串进行功能分类.     *      ...

  3. Java基础知识回顾(一):字符串小结

    Java的基础知识回顾之字符串 一.引言 很多人喜欢在前面加入赘述,事实上去技术网站找相关的内容的一般都应当已经对相应知识有一定了解,因此我不再过多赘述字符串到底是什么东西,在官网中已经写得很明确了, ...

  4. Java基础知识回顾

    Java回顾之I/O Java回顾之网络通信 Java回顾之多线程 Java回顾之多线程同步 Java回顾之集合 Java回顾之序列化 Java回顾之反射 Java回顾之一些基础概念 Java回顾之J ...

  5. Java基础知识强化之多线程笔记01:多线程基础知识(详见Android(java)笔记61~76)

    1. 基础知识: Android(java)学习笔记61:多线程程序的引入    ~    Android(java)学习笔记76:多线程-定时器概述和使用 

  6. Java基础知识回顾之一 ----- 基本数据类型

    前言 在开始工作至今,学习各种各样的技术之中发现自己的很多Java的基础知识都忘了⊙﹏⊙b汗... 而且越是学习越是发现Java基础的重要性,所以准备单独抽一下时间进行Java基础的重新学习.在重新学 ...

  7. java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

     *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时 ...

  8. java基础知识回顾之java Thread类学习(四)--java多线程安全问题(锁)

    上一节售票系统中我们发现,打印出了错票,0,-1,出现了多线程安全问题.我们分析为什么会发生多线程安全问题? 看下面线程的主要代码: @Override public void run() { // ...

  9. java基础知识回顾之java Thread类学习(五)--java多线程安全问题(锁)同步的前提

    这里举个例子讲解,同步synchronized在什么地方加,以及同步的前提: * 1.必须要有两个以上的线程,才需要同步. * 2.必须是多个线程使用同一个锁. * 3.必须保证同步中只能有一个线程在 ...

随机推荐

  1. conn.go 源码阅读

    ),         Conn: conn,     }     return k, v } // 返回远程节点地址 func (self *Connect) Addr() string {      ...

  2. sonyflake.go

        time := id >> (BitLenSequence + BitLenMachineID)     sequence := id & maskSequence > ...

  3. bzoj3812&uoj37 主旋律

    正着做不好做,于是我们考虑反着来,如何计算一个点集s的答案呢,一定是所有的方案减去不合法的方案,不合法的方案一定是缩完点后是一个DAG,那么就一定有度数为0的scc,于是我们枚举s的子集,就是说这些点 ...

  4. BZOJ_4530_[Bjoi2014]大融合_LCT

    BZOJ_4530_[Bjoi2014]大融合_LCT Description 小强要在N个孤立的星球上建立起一套通信系统.这套通信系统就是连接N个点的一个树. 这个树的边是一条一条添加上去的.在某个 ...

  5. BZOJ_1697_[Usaco2007 Feb]Cow Sorting牛排序_贪心

    BZOJ_1697_[Usaco2007 Feb]Cow Sorting牛排序_贪心 Description 农夫JOHN准备把他的 N(1 <= N <= 10,000)头牛排队以便于行 ...

  6. 【爆料】-《维多利亚大学毕业证书》Victoria一模一样原件

    ☞维多利亚大学毕业证书[微/Q:865121257◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归& ...

  7. java线程同步小结

    1.线程同步的目的是为了防止多个线程同时访问一个资源时对资源的破坏 2.线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无 ...

  8. css中固定宽高div与不固定宽高div垂直居中的处理办法

    固定高宽div垂直居中 如上图,固定高宽的很简单,写法如下: position: absolute; left: 50%; top: 50%; width:200px; height:100px; m ...

  9. Vue 进阶之路(九)

    之前的文章我们介绍了 vue 中父组件之间的传值,本章我们再来看一下父子组件间传值的参数校验和非 Props 特性. <!DOCTYPE html> <html lang=" ...

  10. 让你分分钟理解 JavaScript 闭包

    闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是 ECMAScript 规范给的定义,如果没有实战经验,很难从定义去理解它.因此,本文不会对闭包的概 ...