文章目录
——————————————————————————————
线程简介
什么是线程
进程是由线程组成的
线程本质是一个栈
多线程的使用
什么时候需要使用多线程?
- 在cpu产生浪费时,需要性能提升的时候
- cpu什么时候产生浪费比较严重?
- 网络IO、磁盘IO的时候浪费比较严重
- 网络IO:网络请求数据,在数据回来之前cpu一直在打空转
- 磁盘IO:向磁盘发出调度,等数据回来也是cpu一直在打空转
- 需要同时运行多个线程
写多少个线程比较合适?
一般是二十四十个线程比较合适(回答的时候答20~40之间的具体一个数),一方面因为CPU有上下文切换,消耗时间;另一方面读写数据库单批次数据不能太大,太大会影响数据库的性能
线程优先级
下面代码,设置5个高优先级,5个低优先级,让10个线程处于就绪态,一起竞争资源,谁的count越大,谁竞争到的次数越多(优先级高的,竞争到的次数应该高)。
但是结果是,高优先级和低优先级count结果一样,说明设置的优先级没用
public class Priority {
private static volatile boolean notStart = true;//为true,在29行的循环才能执行
private static volatile boolean notEnd = true;//为true,在35行的循环才能执行
public static void main(String[] args) throws Exception {
List<Job> jobs = new ArrayList<Job>();
for (int i = 0; i < 10; i++) {
int priority = i < 5 Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
Job job = new Job(priority);
jobs.add(job);
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();
}
notStart = false;//10个线程进入就绪态之后,notStart变为true,每个线程进入就绪态后都会进入第二个循环执行++操作
TimeUnit.SECONDS.sleep(10);//10个线程竞争了10秒钟,在这之前会让jobCount++
notEnd = false;
for (Job job : jobs) {
System.out.println("Job Priority : " + job.priority + ",
Count : " + job.jobCount);
}
}
static class Job implements Runnable {
private int priority;//私有的,int类型的优先级
private long jobCount;//工作次数
public Job(int priority) {//指定这一次运行的优先级
this.priority = priority;
}
public void run() {
while (notStart) {//如果notStart为true,一直执行这个循环
//先进入就绪态的线程,先执行的概率比较大
hread.yield();//保证了公平
//因为进入了运行状态但是没真正执行++操作,只是打空转后让出cpu
//让出后重新进入就绪态,是操作系统记录位置,不是程序计数器
}
while (notEnd) {
Thread.yield();//这句话是让出cpu的意思
//为什么要加上这个(因为这个方法不靠谱,并不能立刻让出cpu,cpu会执行一段之间)
//这个让出cpu后,会执行下面的++操作
jobCount++;
}
}
}
}
count数字这么大,也表示Thread.yield();方法不靠谱,并不是执行一次++操作就让出cpu,可能执行了1000次或10000次才让出cpu。
靠谱的让出CPU的方法?
sleep(0)或sleep(1):可以立刻让出cpu
线程的状态
线程的状态有哪几种?
线程状态: 新建状态,就绪状态,运行状态,阻塞状态,等待状态,终止状态(死亡状态)。
- 新建状态:线程被构建,但是还没有调用start()方法
- 就绪状态:调用了start()方法,还没开始运行
- 运行状态:开始运行后,的运行时期
- 阻塞状态:(是加锁竞争失败的)当几个线程同时竞争资源,竞争失败的线程全部进入阻塞状态,当竞争成功的线程释放锁的时候,会通知进入阻塞状态的线程,他们才会重新进入运行状态竞争资源。(如果是轻量级锁会自旋,能主动能够知道资源被释放了,而重量级锁无法知道资源什么时候释放,需要有人通知他)
- 等待状态:调用 wait() 或sleep()方法,调用 wait()需要事先加锁
- 终止状态(死亡状态):线程执行结束,被回收
线程的状态转换
Daemon线程
- 是一种守护线程,Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。
- 做辅助性的线程
- Daemon属性需要在启动线程之前设置,不能在启动线程之后设置
启动和终止线程
通过调用线程的start()方法进行启动,随着run()方法的执行完毕,线
程也随之终止
构造线程
在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要
的属性
==》如线程所属的线程组、线程优先级、是否是Daemon线程等信息(设置线程的父线程等)
==》可以通过构造方法加属性,也能通过其他方法加属性。
启动线程
调用start()方法就可以启动这个线程。
- 启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程
序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。
理解中断
中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()
方法对其进行中断操作。
如何安全的终止线程
如何安全的终止线程
直接调用interrupt会不会使线程中断?
其他线程想让线程中断,需要在该线程中写代码配合, 如果只单纯的有其他线程调用该线程的中断方法,是不起任何作用的。
线程内需要配合,才能使这个线程中断
线程通过方法isInterrupted()来进行判断是否
被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。
线程间通信
等待/通知机制
一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个
过程开始于一个线程,而最终执行又是另一个线程。
简单的办法是让消费者线程不断地循环检查变量是否符合预期。
上面这段伪代码在条件不满足时就睡眠一段时间,这样做的目的是防止过快的“无效”尝
试。
1)难以确保及时性。
2)难以降低开销。
示例:
WaitThread和NotifyThread,前者检查flag值是否为false,如果符合要求,进行后续操作,否则在lock上等待,后者在睡眠了一段时间后对lock进行通知。
(等待状态不会进入竞争,阻塞状态会被唤醒后参与竞争)
线程应用实例
等待超时模式
前面的章节介绍了等待/通知的经典范式,即加锁、条件循环和处理逻辑3个步骤,而这种
范式无法做到超时等待
假设超时时间段是T,那么可以推断出在当前时间now+T之后就会超时。
定义如下变量:
- 等待持续时间:REMAINING=T。
- 超时时间:FUTURE=now+T。
这时仅需要wait(REMAINING)即可,在wait(REMAINING)返回之后会将执行:
REMAINING=FUTURE–now。如果REMAINING小于等于0,表示已经超时,直接退出,否则将
继续执行wait(REMAINING)。
CountDownLatch
CountDownLatch属于线程同步的一种,让线程同时执行
CountDownLatch功能:
使用了CountDownLatch来确保ConnectionRunnerThread能够同时开始执行,并
且在全部结束之后,才使main线程从等待状态中返回。