多线程
每个线程都有自己的栈空间,共用一份堆内容。
线程休眠:
Thread.sleep(tms)
线程阻塞(耗时操作):
所有需要消耗时间的操作(如读文件、等待用户输入)。
线程中断:
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
- t1.interrupt()给线程t1添加中断标记
- 线程任务内部语句(如休眠语句)抛出InterruptedException异常
- 进入catch块,执行资源释放语句,执行return语句
- 线程自杀
守护线程:
守护用户线程的,当最后一个用户线程结束时,所有线程自动死亡。
设置为守护线程要在启动前设置:t1.setDaemon(true);
线程安全问题:
由多个线程处理相同数据引起。
解决(看是否是同一把锁):
-
同步代码块
// 格式 synchronized(锁对象){...} // 某线程抢到了该锁,打上了锁标记;执行代码块语句;解锁;各线程抢该锁;。。。 // 谁先抢到,接下来继续抢到的概率比较高
-
同步方法
// 给方法添加synchronized关键字,则该方法成为同步方法 // 锁对象是this
-
显式锁Lock
1、2都属于隐式锁。
// 显式锁锁对象l private Lock l = new reentrantLock(); ... // 上锁 l.lock(); ... // 解锁 l.unlock();
公平锁与非公平锁:
以上1、2、3都是非公平锁,锁一解开,大家都在抢(抢时间片)。
->公平锁:大家排队,谁先来谁就得到锁:
Private Lock l = new ReentrantLock(fair:true);
线程死锁:
避免方法:在加锁的方法里不要再调用其他加锁的方法。
多线程通信问题:
生产者-消费者问题:需要确保生产者生产和消费者消费不同时进行,确保数据安全。
若不进行线程间通信,只进行同步,则会造成时间分配严重不均,不是原定的协作。
生产者生产时,令消费者睡着object.wait(),生产完毕,把消费者唤醒object.notifyAll();反之亦然。
package thread;
public class Demo12 {
public static void main(String[] args) {
//多线程通信 生产者与消费者问题
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
}else {
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务员
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true表示可以生产
boolean flag = true;
public synchronized void setNameAndTaste(String name,String taste){
if(flag){
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag){
System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程的6种状态
Enum Thread State
相关类与接口
Thread类
构造方法,方法,静态方法
(如何关闭一个线程:Stop方法已过时,其不安全,不释放资源;可以通过观察变量的方式控制线程关闭)
优先级
休眠(静态方法)
守护线程(所有用户死亡则守护线程自动死亡)、用户线程
name
静态方法currentThread()
接口Runnable
接口Callable
需要带有返回值的线程时使用。
get方法
clear
cancel
类ExecutorService
线程池
execute(Runnable)向线程池内添加新的任务
类Executors
创建各种线程池
实现
main方法是主线程,在其中启动的其他线程是分支线程。
他们并行执行。(抢占式分配CPU)
1. 继承Thread
覆写public void run():线程要执行的任务方法
触发方式:通过Thread对象的start()来启动任务,而非调用run方法
最简单的使用情况:匿名内部类。
2. 实现Runnable(任务对象)
覆写public void run()
// 1. 实现一个任务对象
MyRunnable r = new MyRunnable();
// 2. 创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
// 3. 执行这个线程
t.start();
与继承Thread相比的好处:
- 更适合多个线程同时执行相同任务的情况;
- 可以避免单继承带来的局限性;
- 任务与线程本身分离,提高程序的健壮性。
- 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类型的线程。
3.实现Callable
-
覆写 call() throws Exception方法。
-
可以像1.2一样与主线程并发执行,也可以让主线程等待其结果(get方法)。
-
使用较少。
// 1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
// 2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
// 3. 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
-
都是接口
-
都可以编写多线程程序
-
都采用Thread.start()启动线程
Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
线程池
不用线程池:1.创建线程;2.创建任务;3.执行任务;4.关闭线程。
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
(后端编程已经基于多线程了,可能主动使用线程池的情况会比较少)
线程列表 - 任务列表
1.缓存线程池
2.定长线程池
3.单线程线程池
4.周期定长线程池
lambda表达式:fromJDK1.8
函数式编程思想
适用于创建一个只创建1次的单方法接口实现实例,作为其他方法的参数。
/*
lambda表达式
函数式编程思想
**/
public static void main(String[] args) {
//冗余的Runnable编写方式
/* Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("锄禾日当午");
}
});
t.start();*/
Thread t = new Thread(() -> System.out.println("锄禾日当午"));
t.start();
}