多线程知识体系

一、线程与进程
进程:进程往往被看成一个应用或者程序的代名词,它拥有自己的内存空间。每个进程拥有自己的独立代码和数据空间。进程的切换开销大,一个进程包括1-n个线程。
线程:线程有时被看作为轻量级的进程,线程共享代码和内存空间。线程共享进程的资源,如打开的资源,这也导致了线程间通信的不安全性。

二、实现多线程
实现java多线程有三种方式:继承thread类,实现runnable接口,使用ExecutorService、Callable、Future实现由返回结果的多线程。
注意:启动一个线程,是调用该线程的start()方法,而不是直接调用run()方法;调用了start()方法后,并不会立即执行线程代码,而是将该线程变为可执行状态(runnable),线程什么时候执行是由操作系统决定的。

三、继承thread和实现runnable的区别
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

注意:

  继承thread直接new就可以得到多个线程;
  对于实现runnable的接口的类,只能new一次,然后以这个实例化的类new出线程。
  详细见示例代码。
  java代码运行至少启动了两个线程,一个main线程,一个垃圾收集线程。当java命令执行一个类时,实际上会启动jvm,每个jvm是在操作系统中启动了一个进程。

四、线程状态转换

1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。(注意:这个状态由系统分配,操作系统决定什么时候开始运行这个线程。)
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。
阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

五、线程调度
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
  static int MAX_PRIORITY
  线程可以具有的最高优先级,取值为10。
  static int MIN_PRIORITY
  线程可以具有的最低优先级,取值为1。
  static int NORM_PRIORITY
  分配给线程的默认优先级,取值为5。

  线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
  线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
  线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
  线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
  线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

六、常用函数说明
1、join():指等待t线程终止;
  例如:主线程中调用一个很耗时的线程,可能主线程执行完了,调用的线程还没执行完,但主线程又需要被调用线程的结果。因此就可以在主线程中执行调用线程的join方法,等待调用线程终止。
2、yield():暂停当前正在执行的线程对象,并执行其他线程;
  Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

注意:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
3、sleep和yield方法区别;
  sleep方法使线程进入阻塞状态,在sleep的时间段内,线程不会被执行;yield方法使线程变成可执行状态,可以会被系统立刻调用执行了。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
4、interrupt();
  中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭。
  要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。
5、wait(),notify();
  Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。
  相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

例子: 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

public class ThreadABC implements Runnable {

private String name;
private Object prev;
private Object self;

private ThreadABC(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}

@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;

self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
}

public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
ThreadABC pa = new ThreadABC("A", c, a);
ThreadABC pb = new ThreadABC("B", a, b);
ThreadABC pc = new ThreadABC("C", b, c);

new Thread(pa).start();
Thread.sleep(100); // 确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}

输出结果:
ABCABCABCABCABCABCABCABCABCABC

  先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序,故在main方法中让ABC线程以此初始化。

6、wait和sleep的区别
共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
不同点:
1. Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

七、常见线程名词解释
1、主线程:JVM调用程序main()所产生的线程。
2、当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
3、后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
4、前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。

八、线程同步
九、线程数据传递
9.1、通过构造方法传递数据
9.2、通过变量和方法传递数据
set方法传递。
9.3、通过回调函数传递数据
见例子。

时间: 2024-10-11 22:38:13

多线程知识体系的相关文章

2016年本博客知识体系引导(持续更新)

前言 为了方便大家的阅读以及自己的知识体系的建立,特意来写出这个引导文章.以前我是遇到什么写什么,想到什么写什么,从2016年开始我将围绕这个知识体系来写文章,从点到面来逐步建立并完善自己的知识体系,同时也欢迎大家关注我的博客. 1.Android IPC机制 Android IPC机制(一)开启多进程 Android IPC机制(二)用Messenger进行进程间通信 Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用 Android IPC机制(四

大型运维知识体系与Python高效自动化运维免费沙龙活动

2015-10-17(周六 下午13:30)大型运维架构运维知识体系讲座 2015-10-18(周日下午13:30)Python运维自动化讲座 以上内容全免费,回馈网友!极其难得的饕餮盛宴! ================================ 大型电商平台架构演变及大型运维知识体系免费讲座 2015-10-17(周六 下午13:30) 主题1:大型电商平台架构演变及大型运维体系知识讲解 内容简介: 通过一个电商网站的架构演变来阐述一个相对完整的<大型运维架构知识体系>.该运维体系

Android安卓开发环境搭建应用游戏项目实战知识体系_极客学院

Java是Android开发的主要语言,所以掌握Java语言基础非常重要,本阶段讲解了Java的基本语法,要深入掌握Java语言,可以通过Java学习路径图学习. 17课程 10小时 19分钟 1.Java编程基础知识入门:变量与数据类型 本课学习变量的命名.定义和初始化及整数.浮点.字符数据类型. 25课时,141分钟 626人学习 2.Java语言Switch语句详解 本课学习switch语句,switch语句是分支语句的一组,适用于判断同一变量的多种状态,进行流程控制. 5课时,17分钟

JavaSE课程知识体系总结

要想成为Java全栈工程师,我们需要学习JavaSE基础.这也是零基础的我入门首先要学习的东西,这里总结下JavaSE里到底有哪些知识,并通过绘制思维导图的方式形成一个初步的知识体系. Java简介.学习什么是Java,Java的应用领域,Java的版本,JavaAPI文档,Java语言特性,以及Java环境的搭建,最后写我们第一个Java小程序(经典的Hello World). Java语言基础.学习Java类,包等基本概念,Java的基本数据类型,变量与常量,运算符,数据类型转换,代码的注释

Android 开发知识体系

知识体系 1.Unix/Linux平台技术:基本命令,Linux下的开发环境 2.企业级数据库技术:SQL语言.SQL语句调优.Oracle数据库技术 3.Java 语言核心技术:Java语言基础.Java面向对象编程.JDK核心API.Java集合框架.Java网络编 程.JavaI/O编程.Java多线程编程.Java异常机制.Java安全.JDBC.XML 4.软件工程和设计模式:软件工程概述.配置管理及SVN.UML.基本设计模式 5.Android应用开发基础:Android开发平台.

阿里巴巴、百度、腾讯都在用的Java架构师知识体系

一.源码分析 源码分析是一种临界知识,掌握了这种临界知识,能不变应万变,源码分析对于很多人来说很枯燥,生涩难懂. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 我认为是阅读源码的最核心驱动力.我见到绝大多数程序员,对学习的态度,基本上就是这几个层次(很偏激哦): 下图是我总结出目前最应该学习的源码知识点: 二.分布式架构 分布式系统是一个复杂且宽泛的研究领域,学习一两门在线课程,看一两本书可能都是不能完全覆盖其所有内容的. 总的来说,分布式系统要做的任务就是把多台机器有机的组合.

聊聊我的知识体系

不知不觉树义已经工作 5 年了,一路走来磕磕碰碰但总算有了自己的一点小体会.对于一个 Java 开发人员来说,到了 5 年的关键节点,需要掌握哪些知识点呢?经过我自己的总结,我列出了下面的思维导图. 从上面的图片我们可以看出大致分为三个部分:JDK 源码.JVM 原理.框架源码. JDK源码 JDK 源码是一切的基础,许多框架都参考了 JDK 源码的实现思路,因此弄懂 JDK 源码是一件非常重要的事情.而 JDK 源码又可以分为下面 4 大块: 集合源码 并发集合源码 并发包源码 阻塞队列源码

前端知识体系(一)浏览器机制以及进程线程的关系

看了一篇大神的博客,对前端学习体系突然明悟了起来.于是准备参考着大神的脚步开始体系化的学习.博客链接:https://segmentfault.com/a/1190000013662126. 很多时候被问到从输入url地址之后,会发生什么?很多时候回答都很笼统,没有自己的核心,所以学习一下大神的思路,以下总结的只是骨干,只有将每一个部分都学习到,这样才是一个知识体系,才能很好的理解上下结构与关系. 1. 从浏览器接收url到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系)

Java并发编程核心知识体系精讲

第1章 开宗明义[不看错过一个亿]本章一连串设问:为什么学并发编程?学并发编程痛点?谁适合学习本课?本课程包含内容和亮点?首先4大个理由告诉你为什么要学,其实源于JD岗位要求就不得不服了.其次5个痛点+12个亮点,是否说服你要死磕Java并发编程呢?... 第2章 跨越第一座山“线程八大核心”[适用于纵观全貌]八大核心-序章.从本章开始将带你攻克并发编程领域的“第一座大山”:多线程八大核心. 第3章 核心1:实现多线程的正确姿势[解读官方文档,够权威]相信很多小伙伴经常在各大技术博客或者论坛甚至