学习笔记 01 --- Java多线程
进程和线程:
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)多进程是指操作系统能同时运行多个任务(程序)。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)多线程是指在同一程序中有多个顺序流在执行。
Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
线程的状态:
新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new
Thread()。
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
start()和run()方法:
start()方法直白一点就是表示这个线程可以运行了,等待cpu调用线程对象的run()方法,产生一个异步执行的效果。在启动多个线程的时候,如果没有特殊的处理,线程调用start()的顺序并不代表线程启动的顺序,线程的启动顺序(cpu执行哪个线程)具有不确定性。
run()方法线程开始执行,虚拟机调用的是线程run()方法里面的具体内容。如果直接调用run()方法的话,线程里面的内容是没有任何异步效果的,多线程也就没有任何意义了。
getPriority()和setPriority(int newPriority):
线程优先级范围1-10,默认情况下都是5,如果不特别设定的话,线程优先级具有继承性,如果线程A启动线程B,那么线程B的优先级和线程A是相同的。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
线程优先级越高并不一定代表线程就一定比优先级低的线程先执行,只能表示优先级高的线程越容易被cpu选择执行。-----不知道这样理解是否正确?
守护线程isDaeMon、setDaemon(boolean on):
守护线程为用户线程运行提供便利的服务,当用户线程全部结束的时候,时候线程也随着jvm一起结束工作。
用户可以利用setDaemon(true)自己设置守护线程。(setDaemon(true)必须是在线程start()之前设置,正在运行的线程是不允许被设置为守护线程的;守护线程中产生的线程也是Daemon的;一些读写操作或者计算逻辑不建议分配给守护线程来服务)
***join()方法***:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
public final void join() throws InterruptedException {
join(0);
}
join() ---等待调用的线程在此执行完毕之后再执行;
join(long millis)---等待调用的线程millis时间之后继续执行,不管调用的线程是否执行完毕;
join()可以把两个交替执行的线程合并为顺序执行的线程,比如线程B中调用了线程A的join()方法,那么直到线程A执行完毕后,线程B才能继续执行。
join()方法的是用wait()来实现的,所以join()方法必须拿到线程对象的锁,否则无法警醒wait。wait的作用是让“当前线程”等待,也就是cpu上运行的线程,因为我们是在“主线程”上调用的“子线程”的wait,所以等待的还是“主线程”,而不是“子线程”。
join()方法因为是用wait实现的所以join的时候会释放锁。
Thread类中的静态方法:
this.xxx() ----线程实例本身
Thread.currentThread.xxx()或Thread.xxx()----正在执行Thread.currentThread().xxx()所在代码块的线程
sleep(long millis)
在执行毫秒内让当前“正在执行的线程”休眠,既Thread.currentThread()返回的线程,sleep不会释放cpu资源,也就是不会释放线程对象的锁。
yield()
线程让步,暂停当前执行的对象,并执行其他线程。这个暂停会释放cpu资源,并且放弃的时间不确定。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
终止线程的形式(阻塞状态或运行状态):
interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
public void run() {
try {
// 1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) {
// 执行任务...
}
} catch (InterruptedException ie) {
// 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}
sleep()、yeild()、wait()比较:
wait()是object的方法,让线程由“运行状态”进入到“等待(阻塞)状态”,同时会释放同步锁;
yield()是Thread的方法作用是让步,让线程由“运行状态”进入到“就绪状态”,同时不会释放同步锁;
sleep()是Thread的方法,作用是让当前线程由“运行状态”进入到“休眠(阻塞)状态”,同时不会释放同步锁;
***wait(),notify(),notifyAll()***:
wait()
wait()的作用是使当前执行代码的线程进行等待,将当前线程置入"预执行队列"中,并且wait()所在的代码处停止执行,直到接到通知或被中断。在调用wait()之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用wait()方法。
notify()
notify()的作用是,如果有多个线程等待,那么线程规划器随机挑选出一个wait的线程,对其发出通知notify(),并使它等待获取该对象的对象锁。注意"等待获取该对象的对象锁",这意味着,即使收到了通知,wait的线程也不会马上获取对象锁,必须等待notify()方法的线程释放锁才可以。和wait()一样,notify()也要在同步方法/同步代码块中调用。
notifyAll()
唤醒在此对象监视器上等待的所有线程。唤醒线程的顺序就和线程启动顺序一样是jvm随机的。
wait(long timeout)
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos)
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
------------------------------------wait()和notify()示例:----------------start------------------------------------
// WaitTest.java的源码
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" call notify()");
// 唤醒当前的wait线程
notify();
}
}
}
public class WaitTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
// 主线程等待t1通过notify()唤醒。
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();
System.out.println(Thread.currentThread().getName()+" continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
main start t1 main wait() t1 call notify() main continue结果说明:
(01) 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
(02) “主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
(03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
(04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
(05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。
******t1.wait()应该是让“线程t1”等待;但是,为什么却是让“主线程main”等待了呢?****
注:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!
---------------------------------------wait()和notify()示例:----------------end---------------------------------------
---------------------------------------wait(long timeout)和notify()示例:----------------start------------------------
// WaitTimeoutTest.java的源码
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " run ");
// 死循环,不断运行。
while(true)
;
}
}
public class WaitTimeoutTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start();
// 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
System.out.println(Thread.currentThread().getName() + " call wait ");
t1.wait(3000);
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
main start t1
main call wait
t1 run // 大约3秒之后...输出“main continue”
main continue
结果说明:
如下图,说明了“主线程”和“线程t1”的流程。
(01) 注意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程t1" 代表WaitTest中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
(02) 主线程main执行t1.start()启动“线程t1”。
(03) 主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
(04) “线程t1”运行之后,进入了死循环,一直不断的运行。
(05) 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。
---------------------------------------wait(long
timeout)和notify()示例:----------------end------------------------------
---------------------------------------wait()和notifyAll()示例:----------------start---------------------------------------
1 public class NotifyAllTest {
2
3 private static Object obj = new Object();
4 public static void main(String[] args) {
5
6 ThreadA t1 = new ThreadA("t1");
7 ThreadA t2 = new ThreadA("t2");
8 ThreadA t3 = new ThreadA("t3");
9 t1.start();
10 t2.start();
11 t3.start();
12
13 try {
14 System.out.println(Thread.currentThread().getName()+" sleep(3000)");
15 Thread.sleep(3000);
16 } catch (InterruptedException e) {
17 e.printStackTrace();
18 }
19
20 synchronized(obj) {
21 // 主线程等待唤醒。
22 System.out.println(Thread.currentThread().getName()+" notifyAll()");
23 obj.notifyAll();
24 }
25 }
26
27 static class ThreadA extends Thread{
28
29 public ThreadA(String name){
30 super(name);
31 }
32
33 public void run() {
34 synchronized (obj) {
35 try {
36 // 打印输出结果
37 System.out.println(Thread.currentThread().getName() + " wait");
38
39 // 唤醒当前的wait线程
40 obj.wait();
41
42 // 打印输出结果
43 System.out.println(Thread.currentThread().getName() + " continue");
44 } catch (InterruptedException e) {
45 e.printStackTrace();
46 }
47 }
48 }
49 }
50 }
运行结果:
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
结果说明:
参考下面的流程图。
(01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
(03) 主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!
--------------------------------wait()和notifyAll()示例:----------------end---------------------------------------
注:负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
**********************************未完待续****************************************
该文为本人学习的笔记,方便以后自己查阅。参考网上各大帖子,取其精华整合自己的理解而成。如有不对的地方,请多多指正!自勉!共勉!
********************************************************************************