多线程基础知识.docx
- 文档编号:5345277
- 上传时间:2022-12-15
- 格式:DOCX
- 页数:25
- 大小:133.13KB
多线程基础知识.docx
《多线程基础知识.docx》由会员分享,可在线阅读,更多相关《多线程基础知识.docx(25页珍藏版)》请在冰豆网上搜索。
多线程基础知识
多线程
1.一扩展javalangThread类
2.二实现javalangRunnable接口
3.三Thread和Runnable的区别
4.四线程状态转换
5.五线程调度
6.六常用函数说明
1.使用方式
2.为什么要用join方法
7.七常见线程名词解释
8.八线程同步
9.九线程数据传递
本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
在这之前,首先让我们来了解下在操作系统中进程和线程的区别:
进程:
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
(进程是资源分配的最小单位)
线程:
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
(线程是cpu调度的最小单位)
线程和进程一样分为五个阶段:
创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个,有兴趣看这里Java并发编程与技术内幕:
Callable、Future、FutureTask、CompletionService )
一、扩展java.lang.Thread类
这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。
没有什么其它特殊的要求,那么可以使用Thread.(笔者推荐使用Runable,后头会说明为什么)。
下面来看一个简单的实例
1.package com.multithread.learning;
2./**
3. *@functon 多线程学习
4. *@author 林炳文
5. *@time 2015.3.9
6. */
7.class Thread1 extends Thread{
8. private String name;
9. public Thread1(String name) {
10. this.name=name;
11. }
12. public void run() {
13. for (int i = 0; i < 5; i++) {
14. System.out.println(name + "运行 :
" + i);
15. try {
16. sleep((int) Math.random() * 10);
17. } catch (InterruptedException e) {
18. e.printStackTrace();
19. }
20. }
21.
22. }
23.}
24.public class Main {
25.
26. public static void main(String[] args) {
27. Thread1 mTh1=new Thread1("A");
28. Thread1 mTh2=new Thread1("B");
29. mTh1.start();
30. mTh2.start();
31.
32. }
33.
34.}
输出:
A运行 :
0
B运行 :
0
A运行 :
1
A运行 :
2
A运行 :
3
A运行 :
4
B运行 :
1
B运行 :
2
B运行 :
3
B运行 :
4
再运行一下:
A运行 :
0
B运行 :
0
B运行 :
1
B运行 :
2
B运行 :
3
B运行 :
4
A运行 :
1
A运行 :
2
A运行 :
3
A运行 :
4
说明:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。
随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
注意:
start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。
因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。
1.Thread1 mTh1=new Thread1("A");
2.Thread1 mTh2=mTh1;
3.mTh1.start();
4.mTh2.start();
输出:
Exceptioninthread"main"java.lang.IllegalThreadStateException
atjava.lang.Thread.start(UnknownSource)
atcom.multithread.learning.Main.main(Main.java:
31)
A运行 :
0
A运行 :
1
A运行 :
2
A运行 :
3
A运行 :
4
二、实现java.lang.Runnable接口
采用Runnable也是非常常见的一种,我们只需要重写run方法即可。
下面也来看个实例。
1./**
2. *@functon 多线程学习
3. *@author 林炳文
4. *@time 2015.3.9
5. */
6.package com.multithread.runnable;
7.class Thread2 implements Runnable{
8. private String name;
9.
10. public Thread2(String name) {
11. this.name=name;
12. }
13.
14. @Override
15. public void run() {
16. for (int i = 0; i < 5; i++) {
17. System.out.println(name + "运行 :
" + i);
18. try {
19. Thread.sleep((int) Math.random() * 10);
20. } catch (InterruptedException e) {
21. e.printStackTrace();
22. }
23. }
24.
25. }
26.
27.}
28.public class Main {
29.
30. public static void main(String[] args) {
31. new Thread(new Thread2("C")).start();
32. new Thread(new Thread2("D")).start();
33. }
34.
35.}
输出:
C运行 :
0
D运行 :
0
D运行 :
1
C运行 :
1
D运行 :
2
C运行 :
2
D运行 :
3
C运行 :
3
D运行 :
4
C运行 :
4
说明:
Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。
run()方法是多线程程序的一个约定。
所有的多线程代码都在run方法里面。
Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnabletarget)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。
因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
三、Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。
但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):
适合多个相同的程序代码的线程去处理同一个资源
2):
可以避免java中的单继承的限制
3):
增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):
线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
提醒一下大家:
main方法其实也是一个线程。
在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。
一个是main线程,一个是垃圾收集线程。
因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。
四、线程状态转换
下面的这个图非常重要!
你如果看懂了这个图,那么对于多线程的理解将会更加深刻!
1、新建状态(New):
新创建了一个线程对象。
2、就绪状态(Runnable):
线程对象创建后,其他线程调用了该对象的start()方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):
就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。
直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(一)、等待阻塞:
运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(wait会释放持有的锁)
(二)、同步阻塞:
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:
运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
五、线程调度
线程的调度
1、调整线程优先级:
Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
1.static int MAX_PRIORITY
2. 线程可以具有的最高优先级,取值为10。
3.static int MIN_PRIORITY
4. 线程可以具有的最低优先级,取值为1。
5.static int NORM_PRIORITY
6. 分配给线程的默认优先级,取值为5。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。
主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。
如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
2、线程睡眠:
Thread.sleep(longmillis)方法,使线程转到阻塞状态。
millis参数设定睡眠的时间,以毫秒为单位。
当睡眠结束后,就转为就绪(Runnable)状态。
sleep()平台移植性好。
3、线程等待:
Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()唤醒方法。
这个两个唤醒方法也是Object类中的方法,行为等价于调用wait(0)一样。
4、线程让步:
Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
5、线程加入:
join()方法,等待其他线程终止。
在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
6、线程唤醒:
Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。
如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
选择是任意性的,并在对实现做出决定时发生。
线程通过调用其中一个wait方法,在对象的监视器上等待。
直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
注意:
Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。
因为有死锁倾向。
六、常用函数说明
①sleep(longmillis):
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
②join():
指等待t线程终止。
使用方式。
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:
“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。
也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
1.Thread t = new AThread(); t.start(); t.join();
为什么要用join()方法
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
不加join。
1./**
2. *@functon 多线程学习,join
3. *@author 林炳文
4. *@time 2015.3.9
5. */
6.package com.multithread.join;
7.class Thread1 extends Thread{
8. private String name;
9. public Thread1(String name) {
10. super(name);
11. this.name=name;
12. }
13. public void run() {
14. System.out.println(Thread.currentThread().getName() + " 线程运行开始!
");
15. for (int i = 0; i < 5; i++) {
16. System.out.println("子线程"+name + "运行 :
" + i);
17. try {
18. sleep((int) Math.random() * 10);
19. } catch (InterruptedException e) {
20. e.printStackTrace();
21. }
22. }
23. System.out.println(Thread.currentThread().getName() + " 线程运行结束!
");
24. }
25.}
26.
27.public class Main {
28.
29. public static void main(String[] args) {
30. System.out.println(Thread.currentThread().getName()+"主线程运行开始!
");
31. Thread1 mTh1=new Thread1("A");
32. Thread1 mTh2=new Thread1("B");
33. mTh1.start();
34. mTh2.start();
35. System.out.println(Thread.currentThread().getName()+ "主线程运行结束!
");
36.
37. }
38.
39.}
输出结果:
main主线程运行开始!
main主线程运行结束!
B线程运行开始!
子线程B运行:
0
A线程运行开始!
子线程A运行:
0
子线程B运行:
1
子线程A运行:
1
子线程A运行:
2
子线程A运行:
3
子线程A运行:
4
A线程运行结束!
子线程B运行:
2
子线程B运行:
3
子线程B运行:
4
B线程运行结束!
发现主线程比子线程早结束
加join
1.public class Main {
2.
3. public static void main(String[] args) {
4. System.out.println(Thread.currentThread().getName()+"主线程运行开始!
");
5. Thread1 mTh1=new Thread1("A");
6. Thread1 mTh2=new Thread1("B");
7. mTh1.start();
8. mTh2.start();
9. try {
10. mTh1.join();
11. } catch (InterruptedException e) {
12. e.printStackTrace();
13. }
14. try {
15. mTh2.join();
16. } catch (InterruptedException e) {
17. e.printStackTrace();
18. }
19. System.out.println(Thread.currentThread().getName()+ "主线程运行结束!
");
20.
21. }
22.
23.}
运行结果:
main主线程运行开始!
A线程运行开始!
子线程A运行:
0
B线程运行开始!
子线程B运行:
0
子线程A运行:
1
子线程B运行:
1
子线程A运行:
2
子线程B运行:
2
子线程A运行:
3
子线程B运行:
3
子线程A运行:
4
子线程B运行:
4
A线程运行结束!
主线程一定会等子线程都结束了才结束
③yield():
暂停当前正在执行的线程对象,并执行其他线程。
Thread.yield()方法作用是:
暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:
yield()从未导致线程转到等待/睡眠/阻塞状态。
在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
可看上面的图。
1./**
2. *@functon 多线程学习 yield
3. *@author 林炳文
4. *@time 2015.3.9
5. */
6.package com.multithread.yield;
7.class ThreadYield extends Thread{
8. public ThreadYield(String name) {
9. super(name);
10. }
11.
12. @Override
13. public void run() {
14. for (int i = 1; i <= 50; i++) {
15. System.out.prin
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多线程 基础知识