1. 线程简介

1.1 线程的定义

现代操作系统调度的最小单元是线程,也叫轻量级进程(LightWeight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

1.2 使用多线程编程的好处

  1. 更多的处理器核心
  2. 更快的响应时间
  3. 更好的编程模型

1.3 线程优先级

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线
程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。

使用线程优先级的方法

Thread.setPriority(int)

线程的优先级的范围是1-10。默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

设置线程优先级时:

  • 针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级
  • 偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定。(在我的暗影精灵PC下,java通过调用setPriority(int)可以实现线程优先级的定义)

1.4 线程的状态

Java线程在运行的生命周期中可能处于表4-1所示的6种不同的状态,在给定的一个时刻,
线程只能处于其中的一个状态。

Thread.yield方法():使当前线程从运行状态(运行状态)变为可以执行状态(就绪状态)。CPU会从众多就绪状态中调度一个线程,使其成为运行状态。当前线程调用yield()方法之后还是可能会被CPU执行,并不是一定会执行其他线程而该线程一次也不会执行到了。

线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。

1.5 Daemon线程

守护线程:该线程会随着程序进程的退出而退出(因为它是一个守护线程)。

main线程(非Daemon线程)在启动了线程DaemonRunner之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机需要退出。Java虚拟机中的所有Daemon线程都需要立即终止。

2. 启动和终止线程

2.1 构造线程

在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要
的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。

Thread parent = currentThread();
this.group = g;
// 将daemon、priority属性设置为父线程的对应属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
this.target = target;
setPriority(priority);

2.2 启动线程

线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。

2.3 理解中断

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行
了中断操作。

中断的作用:线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。因为其中的标志位被清除了。

深入理解中断

  • 在对一个线程对象调用Thread.interrupted()方法之后,一般情况下对这个线程不会产生任何影响。因为调用Thread.interrupted()方法只是将增线程的中断标志位设置为true。
  • 如果一个线程被调用Thread.interrupted()方法之后,如果它的状态是阻塞状态或者是等待状态,而且这个状态正是因为正在执行的wait、join、sleep线程造成的,那么是会改变运行的结果(抛出InterruptException异常)

2.4 过期的suspend()、resume()和stop()

// 将PrintThread进行暂停,输出内容工作停止
printThread.suspend();// 将PrintThread进行恢复,输出内容继续
printThread.resume();TimeUnit.SECONDS.sleep(3);
// 将PrintThread进行终止,输出内容停止
printThread.stop();

2.5 安全地终止线程

中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。

实例代码:

public class Shutdown {public static void main(String[] args) throws Exception {Runner one = new Runner();Thread countThread = new Thread(one, "CountThread");countThread.start();// 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束TimeUnit.SECONDS.sleep(1);countThread.interrupt();Runner two = new Runner();countThread = new Thread(two, "CountThread");countThread.start();// 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束TimeUnit.SECONDS.sleep(1);two.cancel();}private static class Runner implements Runnable {private long             i;private volatile boolean on = true;@Overridepublic void run() {while (on && !Thread.currentThread().isInterrupted()) {i++;}System.out.println("Count i = " + i);}public void cancel() {on = false;}}
}

3. 线程间通信

3.1 volatile和synchronized关键字

关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要
从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问
的可见性。

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性
和排他性。

对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方式,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取由synchronized所保护对象的监视器。

任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。

3.2 等待/通知机制

一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B
调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

对于调用wait()、notify()以及notifyAll()时需要注意的细节,我们需要注意一下细节:

  1. 使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
  2. 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的
    等待队列。
  3. notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  4. notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
  5. 从wait()方法返回的前提是获得了调用对象的锁。

下面我们来分析一下wiat()和notify()的调用过程:

WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。

3.3 等待/通知的经典范式

等待方(消费者)需要遵循如下原则:

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件满足则执行对应的逻辑。
synchronized(对象) {while(条件不满足) {对象.wait();}对应的处理逻辑
}

通知方(生成者)需要遵循如下原则:

  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。
synchronized(对象) {改变条件对象.notifyAll();
}

3.4 管道输入/输出流

管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。

管道输入/输出流主要包括了如下4种具体实现:PipedOutputStreamPipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。

实例代码:

public class Piped {public static void main(String[] args) throws Exception {PipedWriter out = new PipedWriter();PipedReader in = new PipedReader();// 将输出流和输入流进行连接,否则在使用时会抛出IOExceptionout.connect(in);Thread printThread = new Thread(new Print(in), "PrintThread");printThread.start();int receive = 0;try {while ((receive = System.in.read()) != -1) {out.write(receive);}} finally {out.close();}}static class Print implements Runnable {private PipedReader in;public Print(PipedReader in) {this.in = in;}public void run() {int receive = 0;try {while ((receive = in.read()) != -1) {System.out.print((char) receive);}} catch (IOException ex) {}}}
}运行结果:
aaa
aaa
bbb
bbb
ccc
ccc

3.5 Thread.join()的使用

如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。

3.6 ThreadLocal的使用

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。

public class Profiler {// 第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){@Overrideprotected Long initialValue() {return System.currentTimeMillis();}};public static final void begin() {TIME_THREADLOCAL.set(System.currentTimeMillis());}public static final long end() {return System.currentTimeMillis() - TIME_THREADLOCAL.get();}public static void main(String[] args) throws Exception {Profiler.begin();TimeUnit.SECONDS.sleep(1);System.out.println("Cost: " + Profiler.end() + " mills");}
}

4. 线程应用案例

4.1 等待超时模型

开发人员经常会遇到这样的方法调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。

// 对当前对象加锁
public synchronized Object get(long mills) throws InterruptedException {long future = System.currentTimeMillis() + mills;long remaining = mills;// 当超时大于0并且result返回值不满足要求while ((result == null) && remaining > 0) {wait(remaining);remaining = future - System.currentTimeMillis();}return result;
}

Java高并发编程(四):并发编程基础相关推荐

  1. Java并发编程(四):计算机基础-门电路与加法器

    一.门电路 用以实现基本逻辑运算和复合逻辑运算的单元电路称为门电路.上一部分我们已经知道了逻辑门电路的概念(逻辑运算.继电器和门电路),并且引出了一个基础的门电路:与门 事实上,常用的门电路有很多,除 ...

  2. JUC并发编程四 并发架构--并发之共享模型

    共享问题 synchronized 利用对象锁保证了临界区内代码的原子性. 线程安全分析 String,Integer都是不可变类.因为其内部的状态不可改变.因此他们的方法都是线程安全的. monit ...

  3. Java 高并发_JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过!...

    JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过! 1.JPG (37.82 KB, 下载次数: 0) 2018-12-3 09:40 上传 2.JPG (28 ...

  4. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  5. 《Java高并发核心编程.卷2,多线程、锁、JMM、JUC、高并发设计模式》

    <Java高并发核心编程.卷2,多线程.锁.JMM.JUC.高并发设计模式> 目录 第1章 多线程原理与实战 1.2 无处不在的进程和线程 1.2.1 进程的基本原理 1.2.2 线程的基 ...

  6. Java高并发编程(二):Java并发机制的底层实现机制

    Java代码在编译后会变成Java字节码,字节码在之后被类加载机制加载到JVM中,JVM执行字节码,最终需要转换为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. ...

  7. Java并发编程实战_阿里P9整理分享的亿级流量Java高并发与网络编程实战PDF

    前言 为了帮助初级开发者快速掌握高并发.网络编程.微服务.海量数据的处理这些实用技术,本文以"理论+范例"的形式对各个知识点进行了详细的讲解,力争让读者在实践中快速掌握相关知识. ...

  8. java 并发框架源码_某网Java并发编程高阶技术-高性能并发框架源码解析与实战(云盘下载)...

    第1章 课程介绍(Java并发编程进阶课程) 什么是Disruptor?它一个高性能的异步处理框架,号称"单线程每秒可处理600W个订单"的神器,本课程目标:彻底精通一个如此优秀的 ...

  9. 阿里P9整理分享的亿级流量Java高并发与网络编程实战PDF

    前言 有人调侃我们说: 程序员不如送外卖.送外卖是搬运食物,自己是搬运代码,都不产出新的东西-- 透支体力,又消耗健康,可替代性极强,30岁之后就要面临被优化的危险-- 想跳槽,但是更高的平台难进,同 ...

  10. Java高并发编程学习(三)java.util.concurrent包

    简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...

最新文章

  1. 【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )
  2. mysql php pdo_php PDO mysql写法
  3. 推荐一款图片轮换jQuery插件
  4. linux下python安装包_Linux服务器中安装python包管理工具pip
  5. 自动登录Windows系统
  6. ubuntu安装包常用下载地址
  7. 通过云计算机管理档案,云计算下档案信息管理研究
  8. Word如何快速打出公式
  9. 车盲普及贴系列.一.到底什么样车才安全
  10. 【微服务】(十)—— 统一网关Gateway
  11. 软件环境(dev、sit、uat、prod、test)
  12. 带‘’文件”APP的苹果手机和电脑之间的文件互传
  13. java 容器排序_Java攻略第四章 容器类、排序
  14. 自制反汇编工具使用实例 其二(使用xmm寄存器初始化对象,以及空的成员函数指针)...
  15. 成功之路散文连载之伪君子岳不群
  16. 浅谈3D网游引擎变迁史 看国产游戏引擎趋势
  17. 姚芊羽整容过度脸变残 娱乐圈假面女神大盘点
  18. 三星s10android10功能,三星s10+有nfc功能吗 三星s10+支持NFC吗
  19. (Win10)基于Anaconda的 Tensorflow 1.15.0 安装教程
  20. 陈力:传智播客古代 珍宝币 泡泡龙游戏开发第十二讲:盒子的定位方式

热门文章

  1. angular js $post,$get请求传值
  2. [bzoj4590][Shoi2015]自动刷题机
  3. 夺命雷公狗---node.js---3commonJs 与 nodeJs的简介
  4. NOIP2012普及组 (四年后的)解题报告 -SilverN
  5. 根据参数显示类别(三级联动,需要JSON数据)
  6. 只有一流的人才能干出一流的工作,只有具备大量一流的人才的企业才能成为一流的企业...
  7. vhd安装windows 7 64位
  8. php流媒体视频下载,如何使用PHP流媒体文件?
  9. php的内存划分,解析PHP中的内存管理,PHP动态分配和释放内存
  10. c语言输出最后不带空格,新人提问:如何将输出时每行最后一个空格删除