一、线程与进程
一个进程就是一个应用程序,进程包含线程
一个进程至少包含一个线程,大部分都是有多条线程在执行任务
进程是系统资源分配资源的最小单位
线程是进程内部的一个执行任务
线程内多个线程是共享进程资源的,线程是资源调度的最小单位
Java程序是否是多线程的吗?
答:Java是多线程的,至少有main线程
二、线程创建【重点】
Thread
创建线程的方式有很多
1. 继承Thread
2. 实现Runnable接口
3. 使用Callable和Future来完成
4. 使用线程池获得线程
2.1继承Thread
自定义类
继承Thread类
重写run方法
创建子类对象
调用start方法启动线程
start()方法内部执行时会调用run()方法
2.2实现Runnable接口
自定义类
实现Runnable接口
重写run方法
创建自实现类对象
把子实现类对象当构造方法的方法参数来创建Thread对象
有thread调用start方法开启线程
2.3区别
继承Thread开启线程和实现Runnable接口开启线程的区别?
答:
1.一个继承,一个实现
2.继承Thread后,值集创建对象即可调用start开启线程
3.实现Runnable接口的子类,还需要在创建Thread类对象才开调用start开启线程
4.从使用便捷度来说,继承Thread开启线程会方便一点
5.继承Thread类就限制了该类在继承别的类,因为只能单继承,而实现接口的同时可以继承别的类,并且还允许多继承
所以推荐使用接口来实现(扩展性比较好)
三、线程状态
ps:草图,后续更改完善
四、线程优先级
默认优先级是5,最高是10,最低是1,不能越界
注意:不是一定优先级高的先执行,只是大概率上会是
五、线程名
默认线程名,从Thread-0开始
通过setName设置线程名
eg: thread0.setName("绿皮");
获得线程的名字
System.out.println(Thread.currentThread( ).getName( ));
通过构造方法设置线程名
Thread thread = new Thread("和谐号")
六、线程安全
6.1线程安全:
1、完整的步骤可能会被破坏,(要保证方法执行步骤的完整性)
线程内的数据可能会被别的线程修改
临界资源:共享资源(同⼀个对象),一次只可以有一个线程操作,才可以保证准确性
原子操作:不可拆分的步骤,被视作一个整体。其步骤不能打乱
6.2线程的安全方式
同步方法 (
给方法加锁,即设置同步锁关键词synchronized
锁对象是当前对象,即this)
同步代码块(
将需要安全的代码块使用同步代码块包裹,设置锁对象
锁可以是任意对象,但是锁必须是同一个锁,才能锁住,保证安全)
其实就是给需要“同步”,需要“安全”,“步骤一致,不能打乱”的代码加锁
6.3sleep和wait
加锁后,sleep"抱着锁睡觉",是说线程休眠不会让出锁,即不会让出资源,其他线程无法执行,等着这个线程休眠结束并且执行完毕。
wait会让出锁,让可以执行、所需资源充足的线程执行。
七、线程通信[熟悉]
7.1 介绍
线程通信,就是线程之间产生联系.
即通知,例如线程A执行到一定时候会停下,同时通知另外的线程B执行,线程B执行到一定时候,也停下,通知线程A执行
以上操作需要Object类的方法
wait() 让当前线程等待
notify() 唤醒一个(随机一个)处于等待状态的线程
notifyAll() 唤醒所有处于等待状态的线程
7.2 两个个线程通信
// 具体哪台打印机执行的标志
private int flag = 1;
// 现在使用同步方法,print1和print2方法由同一个对象打印机对象调用
// print1方法和print2方法锁是同一个,是this,即打印机对象
public synchronized void print1() {
if (flag != 1) {
try {
// 锁是谁,就用谁调用wait
// 当前线程就陷入等待,会让出资源释放锁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
System.out.print("1 ");
System.out.print("2 ");
System.out.print("3 ");
System.out.print("4 ");
System.out.print("\r\n");
// 干完活,修改标志
flag = 2;
// 通知另外一个处于等待状态的线程
// 锁是谁,用谁调用方法
this.notify();
}
// print2同print1,flag为2时运行
锁是谁,就用谁调用wait
如果没有加锁,直接调用该方法唤醒线程,会报错IllegalMonitorStateException
锁是谁,用谁调用方法
// 锁对象
private Object obj = new Object();
// 具体哪台打印机执行的标志
private int flag = 1;
// 现在使用同步方法,print1和print2方法由同一个对象打印机对象调用
// print1方法和print2方法锁是同一个,是this,即打印机对象
public void print1() {
// 同步代码块,现在锁是字节码文件
synchronized(Printer.class) {
if (flag != 1) {
try {
// 锁是谁,就用谁调用wait
// 当前线程就陷入等待,会让出资源释放锁
// 用字节码锁来调用wait方法
Printer.class.wait( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
System.out.print("1 ");
System.out.print("2 ");
System.out.print("3 ");
System.out.print("4 ");
System.out.print("\r\n");
// 干完活,修改标志
flag = 2;
// 通知另外一个处于等待状态的线程
// 只能唤醒在此对象监视器(加过锁的)上等待的单个线程.
// 如果没有加锁,直接调用该方法唤醒线程,会报错IllegalMonitorStateException
// 锁是谁,用谁调用方法
Printer.class.notify( );
}
}
7.5 总结
特殊的:
wait和notify方法需要在同步方法或者同步代码块内执行
wait会让当前线程进入等待状态,让出资源,其他线程可以执行
wait和notify()方法谁调用? 当前锁对象是谁,就是谁调用该方法
问 wait和sleep有什么区别?
答:
wait是Object类的方法,sleep是Thread类方法
wait和sleep都可以让当前线程进入阻塞状态
但是wait阻塞当前线程,会让出系统资源,其他线程可执行;但是sleep阻塞当前线程,会持有锁不释放,其他线程无法执行
wait需要在同步方法或同步代码快中使用,但是sleep可以在同步或非同步都可以使用
问 为什么wait和notify方法要设计在Object类中?
答: 因为锁可以是任意对象,又因为wait和notify需要被 锁对象调用,那么锁对象是任意的,wait和notify方法也能被任意对象调用,所以就设计在Object类中,因为Object类是所有类的父类
八、 线程池
8.1ThreadPoolExecutor[重要]
ThreadPoolExecutor 很重要,有7个参数
参数名 | 解释 | 备注 |
int corePoolSize | 指定线程池的线程数量(核心线程数) | 不能小于0 |
int maximumPoolSize | 指定线程池可支持的最大线程数 | 最大数量>=核心线程数 |
long keepAliveTime | 指定临时线程的最大存活时间 | 不能小于0 |
TimeUnit unit | 指定存活时间的单位(秒,分,时,天) | 时间单位 |
BlockingQueue<Runnable> workQueue | 指定任务队列 | |
ThreadFactory threadFactory | 指定哪个线程工厂创建线程 | |
RejectedExecutionHandler handler | 指定线程忙,任务队列满的时候新任务来了怎么办?拒绝 |
问: 什么是创建临时线程?
答: 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建线程
问:什么时候开始拒绝任务?
答:核心线程和临时线程都在忙,任务队列也满了,新的任务过来就会拒绝