(目录)
1. 问题的提出
一台计算机为何能够执行多个程序?它们是怎么执行多个程序的?
- 电脑可以同时做很多事情,一边聊天,一边听歌,一边上网查资料等
- 原因是电脑有多个核心(脑子),一个核心可以做一件事情,多个核心就可以做多件事情
- 而多线程就是一台电脑,CPU可以同时运行两个程序(表面上),实际上是进程切换的快,第一个进程打开,第二个进程挂起,给你一种错觉
例如CPU的6核12线程,就相当于有6个工人去运行进程
2. 核心数、进程、线程
- 一个核心下有多个进程,而一个进程下又会有多个线程
3. 进程和线程的区别以及对应应用
进程和线程的区别
- 线程只是一个进程中不同执行的路径
- 进程与进程之间不会相互影响,因为它们是占有独立内存的
- 而线程是占用共同的内存,所以一个线程出问题,那这个进程下的线程都会出问题
- 多进程的程序要比多线程的程序健壮,而多线程运行效率更高,但是线程不能独立执行,必须依存在应用程序中,操作系统不会把线程看作多个独立应用
- 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。例如很多人共同抢一双鞋,就要用到多线程
并发与并行
- 并发(Concurrent)指一个CPU需要进行多个进程,这样就需要不停的切换,让进程不断的交替执行
- 并行(Parallel)指多个CPU同时执行多个进程
4. 多线程程序含义、多线程的作用
创建多线程DemoThread
类,Alt+Insert
调出Generate
选择Override Methods
选择run():void
mainThread
和DemoThread
两个字符串交替执行
如果用.run()
的话会出现问题,死循环一直在跑,用.start()
可以多开启一个线程,然后去自动调用.run()
,再继续进行当前线程
显示的结果是两个死循环的内容在交替执行,其原因就是使用了.start()
后两个线程一直在执行
5. 多线程的执行过程
多线程的执行方式
- 一般的程序是从
main
出发,直线向下进行,只有一条主线 - 多线程在
main
主线程序遇到线程程序时会转到线程程序,并返回到主线程序中,这样main程序和线程程序同时执行
6. Runnable
前面我们创建线程单继承了Thread,无法继承别的类,因为Java不支持多继承
使用Runable
接口来创建线程,要使用其方法,必须创建Thread
对象实现
Ctrl + 左键
查看Thread源码
源码中,Thread
类就是实现了Runnable
接口,而在Thread
的构造方法中也有许多方法函数需要传递Runnable
接口类型
而我们在主函数中实现的应该是第一种传递方式,Thread
的构造方法还有很多方式,这些构造函数都有一个特点就是全部使用init
这个方法对线程进行实现
init
源码的变量注释及源码
- 在原始的init中名字是不能为空的,如果名字为空会报空指针异常,但是在其他的函数中,如果程序员不给
thread.name
赋值的话也可以自动生成一些值 - 涉及线程组相关的安全问题
- 变量的赋值和其他函数的初始化相关
下面init
方法是对上面的函数的衍生,是构造方法中使用的初始方法
7. 简化操作以及线程名
Thread
源码里面有传线程名的构造方法,要在原来线程类中自动获取我们在主线程中设置的名字,使用Thread.currentThread().getName()
方法,.currentThread()
是指当前线程,.getName()
是指获取名字
8. 抢购鞋——多线程案例
两种创建线程的方式,一种使用继承,一种使用接口实现,解决了线程名的问题,接下来我们模拟一个多线程的抢鞋程序
抢鞋的逻辑代码涵盖在线程当中,假设有10双鞋,有三个人来抢,一个线程就是一个用户,所以这就有三个名称不一样的线程名
使用Runnable
接口创建
9. 后台、守护进程的提出
电脑任务管理器
- Apps是前台进程,Background processes是后台进程也叫守护进程,这些进程在电脑开机时就被启动,这样电脑才能正常且安全的运作起来,在程序中也是同理
- 与进程同理,前台线程为用户提供服务,也有后台线程为前台线程提供的服务进行保护或者守护
后台线程的创建过程
-
创建一个
DaemonThread
,实现Runnable
接口 -
重写
run()
方法 -
在运行类中创建先一个
DaemonThread
,再用Thread
用来实现DaemonThread
-
最后调用
setDaemon(true)
设置成后台守护线程,.start()
开启线程
10. 匿名内部类创建多线程——你们老师喜欢的
将Runnable
接口进行匿名处理
11. 发现问题,提出synchronized的概念和用途
现实情况中,抢鞋肯定是有延时操作的,如果我们用.sleep()
设置每次抢鞋之间的间隙,会产生了一个问题,就是线程不同步导致线程不安全,两个人同时抢了第7双鞋
解决这个问题要用到线程同步,及时更新数据,即创建一个synchronized
锁对象,同步数据
12. synchronized同步方法
如何理解锁呢?当用户一抢到第一双鞋时,锁住第一双鞋,其它用户就无法抢了
synchronized
可以创建成一个同步方法,将同步代码块抽离出来
13 Lock、ReentrantLock同步锁
synchronized与reentrantLock区别
-
synchronized
不需要用户去手动释放锁,代码执行完后系统会自动让线程释放对锁的占用 -
reentrantLock
则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象
14. Unlock遗留问题,释放锁
释放reentrantLock
锁,try catch
要放在if
外面,最后finally
调用reentrantLock.unlock()
方法
15. 浅谈synchroized和Lock的区别
- JDK1.5中,
synchroized
是重量级操作,性能低效,Lock
性能高,更稳定 - JDK1.6中,
synchroized
加入很多优化,更加稳定了
锁的释放
-
synchronized
以获取锁的线程执行完同步代码,如果线程执行发生异常,jvm会让线程释放锁 -
Lock
在finally
中必须释放锁,不然容易造成线程死锁
死锁产生
-
synchronized
在发生异常时候会自动释放占有的锁,不会出现死锁 -
Lock
发生异常时候,不会主动释放,必须手动unlock
来释放锁,可能引起死锁的发生
用法
-
synchronized
在需要同步的对象中加入,可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象 -
Lock
一般使用ReentrantLock
类做为锁,通过lock()
加锁和unlock()
解锁指出,在finally
中写unlock()
防止死锁
16. Thread API说明
17. CPU线程调度、Priority线程优先级、优先级常量、剩余小问题
CPU线程调度
- 每一个线程的优先使用权都是系统随机分配的,人人平等,谁先分配到谁先用
- 可以设置优先级赋予某一个线程拥有至高适用权,最高为10,最低为1,默认为5,Java可以抢占CPU
线程1-10中,main()
主线程的value = 5,创建 MaxPriorityThread
类和MinPriorityThread
来查看线程执行顺序
在.start()
前面加优先级.setPriority()
方法即可越权
但有时会发现优先级没有调换过来,是操作系统的原因,程序执行太快了没有反应过来,还没调度程序就结束了
18. join线程插队
.join()
方法可以抢占优先级,实现插队
19. sleep线程休眠
还是上一个例子,使用.sleep()
方法休眠后,thread_1
线程插队时,会等待1000毫秒再打印出结果
20. yield线程让步
.yield()
方法可以实现线程让步,让其它线程执行,thread_1
输出一次的时候给thread_2
让步了,有时程序运行的太快了,以至于还没打印出让步输出,thread_2
已经输出完毕了
21. 线程状态?嗯,还是来玩一盘游戏吧!
Java中线程的状态分为6种--以斗地主为例
1.新建(NEW)-新建一局游戏
2.可运行(RUNNABLE)-初始状态是可运行的
3.阻塞(BLOCKED)-谁出牌谁获得一个锁,导致阻塞,出好牌则疏通阻塞
4.等待(WAITING)-不出牌的等待通知
5.计时等待(TIMED_WAITING)-出牌时,其他人计时等待超时或通知
6.终止(TERMINATED)-游戏结束
22. 发现实际问题,抛出线程通信的含义
线程优先级
- Win10任务管理器中,线程有6个优先级设置
- 线程的调度目的就是通知另一个线程去执行,也有其它办法去通知
23. 线程的通信:wait和notify
线程通信,即等待唤醒机制
- 最简单的例子如
Producer
生产者与Customer
消费者和Condom
产品的关系 - 当产品
Condom
产品生产出来之后,消费者购买完,需要联系Producer
厂商继续生产 -
.notify()
方法用于唤醒一个在此对象监视器上等待的线程 - 一个线程在对象监视器上等待可以调用
.wait()
方法
24. notifyAll
.notifyAll()
方法用于唤醒在该对象上等待的所有线程
25. 提及Process进程。点到为止,章节结束语和建议。
多线程掌握基础,当学习到框架时,需要深入并发编程
参考:操作进程拓展Class ProcessBuilder