多线程
线程的创建方式
继承Thread()
继承之后如果不设置一个共享变量,就会导致每个新建的进程都会有一个新的变量
实现Runnable()
需要new出来这个实现Runnable接口的类,然后再创建线程。
实现Callable()
Callable有返回值,如果想判断线程运行的结果问题,建议使用Callable返回一个结果,然后再main线程中进行判断。
1.创建多线程的参数对象
2.创建多线程运行结果的管理者对象
FutureTask
。。。。。
消费者生产者问题
进程的挂起wait();进程的唤醒notify();
线程的改进(线程池)
用之前的方法写多线程代码,用到线程的时候就创建,代码跑完之后就消失。
会浪费操作系统的资源。
这时候就引入了线程池
//线程池创建的核心逻辑,
1.创建一个线程池,池中是空的。
2.提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给线程池。下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
3.如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
线程池代码的实现
1.创建线程池。
2.所有任务全部执行完毕,关闭线程池。//一般情况下线程池是不会关闭的,因为服务器大多数情况下都是24小时运行。
Executor:线程池工具类通过调用方法返回不同类型的线程池对象。
public static ExecutorService newCachedThreadPool():创建一个没有上限的线程池(也不是没有上限,最大值是int的最大值)
public static ExecutorService newFixedThreadPool(int nThreads):创建一个有上限的线程池
自定义线程池
核心线程
临时线程
队伍长度
如果提交的任务数量超过核心线程和队伍长度的总和数量,这时才会创建临时线程
如果超过了核心线程、临时线程、队伍长度的综合,那么剩余的线程就会被丢弃
经典的任务拒绝策略
ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionExecption异常
ThreadPoolExecutor.CallerRunsPolicy:调用任务run()方法绕过线程池直接执行
死锁
锁里面套锁会导致死锁,比较经典的就是哲学家进餐问题
线程安全问题
如何解决线程安全问题呢?
- 要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
- A:是否是多线程环境
- B:是否有共享数据
- C:是否有多条语句操作共享数据
线程的状态
1.新建
2.就绪
3.阻塞
无法获得锁
4.执行
5.挂起
wait():无线等待,等待激活
sleep():计时等待,到时间后自动激活
6.激活
7.死亡
run()执行完后
阻塞队列
阻塞队列的实现类
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
–ArrayBlockingQueue
在生产者进程中,利用put()方法将内容送入queue队列中,put()方法将自动为其创建一个对象锁,然后判断队列是否满(此处可以查看源码),最后释放锁。这时候不能再自己创建锁了。
tips:有时候输出语句卸载了锁的外面,就导致有时候连续输出了两下,但是这时候是不会造成线程安全问题的。
在消费者进程中,利用take()方法取出对象,同样是在take()方法中为其创建锁,
线程的休眠sleep()
sleep()方法放在同步代码块外面和同步代码块里面有什么区别
原理:sleep()方法只会让线程休眠一会,但是不会释放锁。
结论1:写在里面当前线程会休眠一会,但是不会释放锁让出cpu,这时候临界资源别的线程就不能使用。
结论2:写在外面,当前线程会休眠,并且让出了cpu。
总结,如果一个线程占用了CPU,那么要是吧sleep()写在里面,即使线程休眠了也没有让出cpu那么其他线程怎么使用资源呢?
多线程的栈堆原理
一个小问题(已解决)
例如这段代码,每次创建线程都会运行run()方法中的代码,而且每次new一个对象都会在堆中类中增加一个为什么(可以参考new String创建了几个对象这个问题)
public void run() {
ArrayList<Integer> boxList = new ArrayList<>();
}
如果这个对象已经被new过(即存在堆中)那么这个对象只会被加载而不会被new再次分配空间,详情看下图
CSDN收到的回答(已采纳)
并不是每次线程抢占到CPU都从run()方法的第一行开始执行的,而是从哪里停止接着往下执行。
例如这里Thread.sleep(500)让出CPU的执行权,等到下一次调度获得CPU后就从Thread.sleep(500)的下一行开始执行,即while语句,所以从始至终就只new了一个ArrayList对象。
常见问题
例如抽奖问题,奖池内容过少就会导致一个线程在抢占cpu瞬间就输出完成,这时候启用sleep()方法去休眠cpu