多线程 - java
多线程
进程
- 正在运行的程序就是一个独立的进程
- 线程属于进程,一个进程中可以同时运行很多个线程
- 进程中的线程是并发和并行执行的
线程
- 线程(Thread) 是一个程序内部的一条执行流程
- 程序中如果只有一条执行流程,那这个程序就是单线程的程序。
生命周期
- New 新建
- Runnable 运行
- Teminated 终止
- Blocked 锁阻塞
- Waiting 无限等待
- Timed Waiting 计时等待 sleep 不会释放锁 wait会释放
多线程
- 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)
- main 方法是一个主线程
- 启动线程必须调用start方法而不是run方法,run方法不会被认为是一个线程。
- 不要把主线程任务放在启动子线程之前,主线程任务在前面的话会先执行完主线程任务。
创建线程
通过继承Thread类
- 优点:编写简单
- 缺点:不能继承其他类
1 | //1、通过继承Thread类 |
实现Runnable接口
优点:可以继承其他类和其他接口
缺点:多一个Runnable对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class MyRunnable implements Runnable{
//必须重写run方法
public void run(){
//描述线程执行任务
}
}
//启动线程
//创建任务
Runnable target = new MyRunable();
//把任务交给线程对象
new Thread(target).start();
//匿名内部类
Runnable target = new Runnalbe(){
public void run(){
//描述线程执行任务
}
};
new Thread(target).start();
//简化
new Thread(() ->{
//描述线程执行任务
}).start();实现Callable接口
- 优点:可以返回线程执行完毕的结果
- 缺点:编码复杂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyCallable implements Callable<String>{
int n;
Mycallable(int n){
this.n = n;
}
//必须重写call方法
public String call() throws Exception{
//描述线程执行任务
return n + "";
}
}
Callable<String> call = new MyCallable(100);
//FutureTask 实现了Runnable 对象
FutureTask<String> f1 = new FutureTask<>(call);
new Thread(f1).start();
//得到结果,会等待上面线程执行完毕
String rs = f1.get();
线程安全问题
- 多个线程,同时操作同一个共享资源,出现的业务安全问题
线程同步
- 解决线程安全问题的方案
解决方案
加锁:每次只允许一个线程加锁
同步代码块
- 作用:把访问共享资源的核心代码上锁,以此来保证线程安全
- 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
- 注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug
1
2
3synchronized(同步锁(一般为共享资源)){
}同步方法
- 作用:把访问共享资源的核心方法给上锁,以此保护线程安全
- 有隐式锁,锁整个方法
- 对于实例方法一般为this
- 对于静态方法为 类名.class
- 锁的范围越小,性能更好
1
2
3public synchronized void test(){
}Lock 锁
- Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
- Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
1
2
3
4
5
6
7
8
9
10
11
12
13private final Lock lk = new ReentrantLock();
//加锁
lk.lock();
try{
//代码
}finally{
//解锁
lk.unlock();
}
线程通信
- 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程池
- 一个可以复用线程的技术
- 工作原理
- 工作线程
- 任务队列
- 任务接口 - Runnable Callable
创建线程池
线程池接口 ExecutorService
线程池实现类 ThreadPoolExecutor
方式一:使用ThreadPoolExecutor自创建一个线程池对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 核心线程,最大线程,临时线程持续时间,时间单位,任务队列,线程创建工厂,任务拒绝策略
ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExector.AbortPolicy());
//执行Runnable 任务 自动创建一个新线程,自动处理,自动执行
pool.execute(Runnable command);
pool.execute(Runnable command);
pool.execute(Runnable command);
//复用前面的线程
pool.execute(Runnable command);
pool.execute(Runnable command);
pool.execute(Runnable command);
pool.execute(Runnable command);
//任务队列满了,提供临时线程
pool.execute(Runnable command);
pool.execute(Runnable command);
//都满了开始拒绝任务策略
pool.execute(Runnable command);
//执行Callable 任务
Future<T> res = pool.submit(Callable<T> task)
//等待所有任务执行完毕,然后关闭线程池
pool.shutdown();
//立即关闭
pool.shutdownNow();- 拒绝策略
- AbortPolicy 拒绝被抛出异常
- DiscardPolicy 拒绝不抛出异常
- DiscardOldestPolicy 抛弃队列中等待最久的任务
- CallerRunsPolicy 主线程绕过线程池自己来做
- 拒绝策略
方式二:使用Executors 调用方法返回不同特点的线程池对象
- newFixedThreadPool(3) 创建固定线程数的线程池
- newSingleThreadPool 创建一个线程数的线程池
- newCachedThreadPool 线程数量随着任务增加,空闲了60s后线程会删除
- newScheduledThreadPool() 创建固定线程数的定时器线程池
悲观锁
- 一上来就加锁,每次只能一个线程进入,访问完毕后再解锁,线程安全,性能较差Ω
乐观锁
- 一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
- 改变之后比较,比较之后发现原数据没变就把改变的数据放回。
- 整数修改的乐观锁,原子类实现
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 White Horse Village!