参考书籍:
Java多线程编程实战指南(核心篇)——黄文海
图解Java多线程设计模式 —— 【日】结城浩
何谓线程
单线程和多线程、Thread类、run方法、start方法
线程的启动
Thread类、Runnable接口
线程的暂停
sleep方法
线程的互斥处理
synchronized、synchronized语句、锁
synchronized 英 /'sɪŋkrənaɪzd/
adj. 同步的;同步化的
线程的协作
等待队列(wait set)、wait方法、notify方法、notifyAll方法
线程的生命周期图
2.什么是线程明为跟踪处理流程,实为跟踪线程
比如:我们阅读程序时,我们会按照处理流程来阅读
对于这种处理流程始终如一条线的程序,称为单线程
我们在阅读程序时,表面看来是在跟踪程序的处理流程,实际上跟踪的是线程的执行。
如图:
1)单线程程序简单的多线程程序
public class ThreadTest1 {
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
System.out.println("Good!");
}
}
}
运行情况:
2)多线程程序多个线程组成的程序
常见实例:
GUI应用程序耗时的I/O处理多个客户端
public class MyThreadTest extends Thread{
public void run(){
for (int i = 0; i <100 ; i++) {
System.out.println("Nice!");
}
}
public static void main(String[] args) {
MyThreadTest myThreadTest = new MyThreadTest();
myThreadTest.start();
for (int i = 0; i <100 ; i++) {
System.out.println("Good!");
}
}
}
跟踪线程的执行:
3.进程、线程与任务进程(Process) 是程序的运行实例
例如:一个运行的IDEA就是一个进程进程与程序之间的关系就比如:《星际穿越》与相应的视频文件之间的关系
前者是从动态的角度去刻画事物后者是从静态的绝对去刻画事物 运行一个Java程序的实质是启动一个Java虚拟机进程
进程是程序向操作系统申请资源的基本单位
线程是进程中可独立执行的最小单位例如:一个下载文件的程序为了提高效率,可以使用多个线程
一个进程可以包含多个线程
同一进程中的所有线程共享该进程中的资源,如内存空间、文件句柄等进程与线程的关系就好比如:营业的饭店与正在工作的员工之间的关系
饭店对外为顾客提供服务,而这种服务最终是通过饭店员工工作来实现的这些工作中的员工则共享该饭店的资源,如食材、餐具、清洁工具等等
线程所要完成的计算就被称为任务,特定的线程总是在执行着特定的任务
4.Java线程API简介Java标准库类java.lang.Thread就是Java平台对线程的实现
Thread类或其子类的一个实例就是一个线程
线程的创建、启动、运行、暂停和协作
1)线程的创建有两种方式:
继承Thread类实现Runnable接口 2)线程的启动
Thread类中的start方法
3)线程的运行Thread类中的run方法
4)线程的暂停Thread类的sleep方法
sleep方法通常放在try…catch中,因为sleep方法有可能抛出IllegalArgumentException异常InterruptedException异常能够取消线程的处理millis:纳秒,毫秒
1000秒==1毫秒
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
5)协作(wait、notify)
所有实例都拥有一个等待队列。它是在实例的wait方法执行后停止操作的线程的队列。
在执行wait方法后,线程便会暂停操作,进入等待队列这个休息室。
当下列任意一种情况发生时,线程便会退出等待队列
有其他线程的notify方法 或 notifyAll 方法来唤醒线程有其他线程的interrupt方法来唤醒线程wait方法超时
obj.wait() 是将当前线程放入obj的等待队列
obj.notify() 会从obj的等待队列中唤醒一个线程
obj.notifyAll() 会从obj的等待队列中唤醒所有线程
no
6)实例1_1继承Thread类创建线程、启动、运行1
public class ThreadExample extends Thread{
public static void main(String[] args) {
//创建线程
ThreadExample threadExample = new ThreadExample();
//启动线程
threadExample.start();
//输出 当前线程 的线程名称
System.out.printf("1.当前线程:%s.%nn",Thread.currentThread().getName());
}
//在该方法中实现线程的任务
@Override
public void run(){
System.out.printf("2.我是ThreadExample线程:%s.%nn",Thread.currentThread().getName());
}
}
执行结果: 1.当前线程:main. 2.我是ThreadExample线程:Thread-0.
总结:
线程的切换是操作系统来随机决定的步骤:创建、启动、运行
实现Runnable接口创建线程、启动、运行、暂停
public class RunnableExample implements Runnable {
public void run() {
System.out.printf("2.RunnableExample:%s.%nn",Thread.currentThread().getName());
}
public static void main(String[] args) {
//创建线程
Thread thread = new Thread(new RunnableExample());
//启动线程
thread.start();
System.out.printf("1.RunnableMain 主类 :%s.%nn",Thread.currentThread().getName());
}
}
运行结果: 1.RunnableMain 主类 :main. 2.RunnableExample:Thread-0. 可能是: 2.RunnableExample:Thread-0. 1.RunnableMain 主类 :main.
在main方法中创建一个实例并以该实例作为构造器参数直接通过new创建一个实例 7)总结:
为什么多次运行结果可能不一致:
不管采用哪种方式创建线程,一旦线程的Run方法执行结束,相应的线程的运行也就结束了
运行结束的线程所占用的资源会如同其他Java对象一样被Java虚拟机垃圾回收
线程属于“一次性用品” 不能重新调用一个运行结束的线程start方法来重新运行。
Start方法只能够被调用一次,多次调用同一个Thread实例的start方法会导致抛出java.lang.IllegalThreadStateException异常
run不能直接调用(避免这样做!!!)
如果我们没有启动线程而是在应用代码中直接调用线程的run 方法,那么这个线程的run 方法其实运行在当前线程(调用run方法的线程),而不是运行在其线程中。
new 一个 Thread,线程进入了新建状态。
调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。
start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
2.RunnableExample:main. // 运行在被调用的线程中 1.RunnableMain 主类 :main. // 主线程 2.RunnableExample:Thread-0. // 新建线程8)线程两种创建方式的区别
面向对象编程角度:
Thread是基于继承的技术(创建Thread的子类)Runnable是基于组合的技术(以Runnable接口实例为构造器参数直接通过new创建Thread实例)组合相对于继承来说,其类和类之间的耦合性更低。 一般我们认为组合是优先选用的技术 对象共享角度:
Thread不适合资源共享Runnable多个线程实例可以被多个线程共享,某些情况下可能导致程序运行结果出乎意料(竞态、线程安全) 对象创建成本角度:
Thread是创建一个线程实例,虚拟机需要给它分配调用栈空间、内核线程等资源。其成本相对昂贵Runnable是创建Runnable实例再将其作为方法参数传递给其他对象使用,而不必利用它来创建相应的线程。 即可满足我们的计算需要。(JDK标准库中有不少API都是使用了Runnable接口) 9) notify 和notifAll的区别
notify :
从等待队列中唤醒任意一个线程,使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。处理速度要更快,但处理不好,可能程序会停止 notifAll :
使==所有==正在等待队列中线程退出等待队列,进入就绪状态。代码更加健壮 10)sleep 和wait 的区别
使用场景:
sleep()方法可以在任何地方使用wait()方法则只能在同步方法或同步块中使用 所属方法:
sleep() 方法是线程类(Thread)的静态方法wait()是Object对象的方法 对象锁:
sleep()会占着对象锁不放wait()会释放对象锁 5.线程的属性
线程的属性包括:
编号ID
名称Name
线程类别Daemon
/ˈdiːmən/ n. 守护进程;后台程序
优先级Priority
6.Thread类常用方法 7.线程的生命周期状态Java线程的状态可以使用监控工具查看,也可以通过Thread.getState()调用获取
Thread.getState返回值是一个枚举类型。
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
Thread.State所定义的线程状态包括以下几种:
**新建(new):**新创建了一个线程对象。
**可运行(runnable):**线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
包括两个子状态:Ready和Running
Ready:表示该状态的线程可以被程序调度器进行调度而使之处于Running状态。也称为 活跃线程
Running:表示处于该状态的线程正在运行,即相应线程对象的run方法所对应的指令正在由处理器执行。
若执行Thread.yield()的线程,其状态可能会由Running转换为Ready
**阻塞(block):**处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
阻塞的情况分三种:
**(一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting queue)**中,使本线程进入到等待阻塞状态;
**(二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)**中,线程会进入同步阻塞状态;
(三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
等待(Waiting): 一个线程执行了某些特定方法,就会处于等待状态。
转为Waiting状态的方法包括:
Object.wait()Thread.join()LockSupport.park(Object) 从Waiting转为Runnable的方法包括:
Object.notify()/ notifyAll()LockSupport.unpark(Object)
带时间等待(Timed_Waiting): 与waiting的区别是 该操作是带有时间限制的等待状态。当其他线程没有在指定时间内执行该线程的操作,该线程状态自动转换为Runnable
**终结(terminated):**线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。
一个线程在其整个生命周期中,只可能有一次处于New状态和Terminated状态
8. 多线程编程的优势和风险优势:
提高系统的吞吐率提高响应性充分利用多核处理器资源最小化对系统资源的使用简化程序的结构
风险:
线程安全问题(脏读、丢失更新)线程活性问题(死锁)上下文切换可靠性



