juc-1
多线程-入门知识
线程
概念
线程:我们追踪程序运行的流程,其实就是在追踪线程的流程。
单线程: “在某一时间点执行的处理只有一个”。(在研究单线程的时,我们忽略Java的后台线程,如:GC等)
多线程:常见的多线程常见有以下几个。
- 耗时的IO处理:
- 多个客户端:如果让服务端针对不同的客户端执行处理,程序是很复杂的。 客户端连接到服务器时,为客户端准备一个线程,这样看来服务器程序好像只处理一个客户端。
ps:nio即便不使用多线程,也可用执行兼具性能和可扩展性的IO处理。
“本线程” & “当前线程”
- 本线程: 表示this(以及this对应的线程)的意思
- 当前线程: 表示调用对象方法的线程
顺序 并行 并发
- 顺序:依次处理
- 并行:同时处理
- 并发: 将一个操作分隔成多个部分并且允许无序处理
启动
两种方式
- Thread类: 继承Thread类的例子
- Runnable接口:实现Runnable接口的例子
其他方式
-
java.util.concurrent.ThreadFactory利用ThreadFactory启动线程
无论那种方式,启动新线程的方法最终都是Thread类的start方法。
ps:Thread类本身还实现Runnable接口,并持有run方法,但run()主体是空的。仍需要子类重写。
线程类的实例 & 线程
需要注意的是线程类的实例并不等于线程。线程就算停止,线程类的实例并不会消失。
暂停(休眠)
在实际程序中,使用sleep的频率并不高。
Sleep的例子
- interrupt方法:用于中途唤醒被Thread.sleep休眠的线程
互斥
线程A和线程B相互竞争引起与预期相反的情况称为竞态条件(race condition)或数据竞争(data race)。
处理上述的竞态的操作称为互斥。
synchronized方法(同步方法)
synchronized代码块
synchronized代码块用于精准控制互斥处理的执行范围。
synchronized实例方法和synchronized代码块(this加锁)
以下两种写法是等效的。也就是说synchronizedui实例方法是使用this的锁来执行线程的互斥处理的。
1 | synchronized void method(){ |
1 | void method(){ |
synchronized静态方法和synchronized代码块(类对象加锁)
synchronized静态方法每次只能由一个线程运行,这一点和synchronized实例方法相同。
但是,synchronized静态方法使用的锁和synchronized实例方法使用的锁是不一样的。
1 | class Something{ |
1 | class Something{ |
协作
wait方法,notify方法,notifyAll方法(都是java.lang.Object类的方法,所以既不是Thread类中的方法,但又是)
注意:
它们只能由持有要调用的实例的锁的线程调用。如果未持有锁的线程调用上面方法,异常java.lang.IllegalMonitorStateException会抛出。
- 为什么放在java.lang.Object包? 这三个方法与其说是针对线程的操作,不如说是针对实例的等待队列的操作。由于所有的实例都有等待队列。它们确实
不是Thread包的方法,但Object是所有类的父类,所以也可用说它们是Thread类的方法。
等待队列(线程的休息室)
所有实例都拥有一个等待队列,它是在实例的wait方法执行后停止操作的线程的队列。
线程退出等待队列
等待队列是一个虚拟的概念, 既不是实例中的字段,也不是用于获取正在实例上等待的线程的列表的方法。
- 其他线程的notify或notifyAll方法来唤醒线程
- 其他线程的interrupt方法流唤醒线程
- wait方法超时
wait方法:线程放入等待队列
要执行wait方法,线程必须持有锁(这是规则)。如果线程进入等待队列之后,便会释放其实例的锁。
1 | wait(); |
notify方法
要执行notify方法,线程也必须持有调用的实例的锁。
1 | obj.notify(); |
notify唤醒的线程并不会在执行notify的一瞬间重新运行。因为在执行notify的那一瞬间,执行notify的线程还持有着锁,所以其他
线程还无法获取这个实例的锁。执行notify后如何选择线程:假如在执行notify方法之后,正在等待队列中等待的线程不止一个,对于“这时该如何来选择线程”这个
问题规范中并没有做出规定。究竟是选择最先wait的线程,还是随机选择,或采取其他方法取决于Java平台运行环境。因此编写程序时需要
注意,最好不要编写依赖于所选线程的程序。
notifyAll方法
notifyAll方法会将等待队列中所有的线程都取出来。
1 | notifyAll(); |
- 锁在谁的手里? 被唤醒的线程会去获取其他线程在进入wait状态时释放的锁。其实是执行notifyAll的线程正持有着锁。因此,唤醒的线程虽然都推出了
等待队列,但都在等待获取锁,处于阻塞状态。只有在执行notifyAll的线程释放锁以后,其中的一个幸运儿才能够实际运行。
-
使用notify方法还是notifyAll方法?由于notify唤醒的线程较少,所以处理速度要比使用notifyAll快。但使用notify时,如果处理不好,程序便
可能会停止。一般来说,使用notifyAll时的代码会比notify时更为健壮。
线程的状态转移
参考下图的线程状态转移: