提示:该文档仅保留个人学习过程观点,如有不对,请各位大佬指正
文章目录
- 多线程
- 基础概念
- 实现多线程的方式
- 1.Thread 类
- 2.Runnable 接口
- 3.Callable 接口
- 线程安全问题解决方式
- 1. synchronized 线程同步 [隐式锁]
- 2. synchronized 同步方法 [隐式锁]
- 3. Lock 的子对象 ReentrantLock [显示锁]
- 公平锁和不公平锁
- 线程死锁
- 多线程通信
- 线程池
- Java中的四种线程池 . ExecutorService
- 1. 缓存线程池
- 2. 定长线程池
- 3. 单线程线程池
- 4. 周期性任务定长线程池
多线程
多个线程并发执行,提高程序运行效率。
基础概念
进程:运行的一个程序或者应用,每个进程都有独立的内存空间 线程:进程的一个任务(执行路径),一个进程至少有一个线程,一个进程里面的多个线程共享一个内存空间
并发:同一时间段内发生 并行:同一时刻发生
同步:排队执行,效率低,数据安全 异步:同时执行,效率高,数据不安全
用户线程:当一个进程不包括任何存活的用户线程时,进程结束 守护线程:守护用户线程的,当最后一个用户线程结束时,所有的守护线程自动死亡
// 设置为守护线程,在线程启动前设置
线程.setDeamon(true);
实现多线程的方式
1.Thread 类
类继承 Thread 类,重写run方法,start()方法调用线程
public class Test {
public static void main(String[] args) {
Thread my = new MyThread();
my.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("执行此线程方法。。");
}
}
2.Runnable 接口
实现 Runnable 接口,重写run方法(里面写任务方法) 1)创建任务对象 2)创建一个线程,并为其分配一个任务 3)执行线程
public class Test01 {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
Thread t = new Thread(my);
t.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("执行了此任务方法。。");
}
}
- 实现 Runnable 与 继承 Thread 的优势
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况 2.可以避免单继承所带来的局限性 3.任务与线程本身分离,降低耦合性
3.Callable 接口
实现 Callable 接口,通过 Callable 创建 FutureTask 对象,通过 FutureTask 创建 Thread,启动线程
public class Test02 {
public static void main(String[] args) {
// 实现 Callable 接口,通过 Callable 创建 FutureTask 对象,通过 FutureTask 创建 Thread ,启动线程
MyCallable my = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(my);
Thread t = new Thread(task);
t.start();
// 执行主线程打印
System.out.println("主线程打印。。");
// 获取分支线程返回值
try {
int x = task.get();
System.out.println("x = " + x);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
/* 与run()方法不同的是,call()方法具有返回值*/
@Override
public Integer call() throws Exception {
System.out.println("callbale执行了");
return 0;
}
}
- Runnable 与 Callable不同点
Runnable没有返回值 Callable可以返回执行结果 Callable接口的call()允许抛出异常 Runnable的run()不能抛出 Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞
- 获取当前线程
Thread.currentThread();
- 线程休眠
Thread.sleep(毫秒数);
- 线程阻塞
耗时操作(例如文件读取)
- 线程中断
线程是否应该结束应该由其线程自生决定 方式:外部调用 线程.interrupt() 方法,标记线程中断,线程里面try-catch块捕获InterruptedException,在catch块里面结束线程(return;)
线程安全问题解决方式
- 线程安全问题: 多个线程争抢到的数据,在拿到与使用时不一致导致的一系列问题
1. synchronized 线程同步 [隐式锁]
同步代码块 synchronized 互斥锁 格式:synchronized(锁对象){} 锁对象必须是同一个锁对象
锁对象:
public class Test03 {
public static void main(String[] args) throws InterruptedException {
MyRun my = new MyRun();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
Thread t3 = new Thread(my);
t1.start();
t2.start();
t3.start();
}
}
class MyRun implements Runnable {
/**
* 共享资源
*/
private int count = 10;
Object obj = new Object();
/**
* synchronized 锁对象
*/
@Override
public void run() {
while (true) {
synchronized (obj) {
if (count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + count);
count--;
} else {
break;
}
}
}
}
}
2. synchronized 同步方法 [隐式锁]
格式:public synchronized void 方法名(){} 普通的锁对象是:this static的锁对象是:类名.class
锁方法:
public class Test03 {
public static void main(String[] args) throws InterruptedException {
MyRun my = new MyRun();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
}
class MyRun implements Runnable {
/**
* 共享资源
*/
int i = 0;
/**
* synchronized 修饰实例方法
*/
public synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 5; j++) {
increase();
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
3. Lock 的子对象 ReentrantLock [显示锁]
- 用法:
// 创建锁
private Lock lock = new ReentrantLock();
// 加锁
lock.lock();
// 解锁
lock.unlock();
// 编程规范:
// 锁【lock()方法】必须紧跟try代码块,且unlock()方法要放到finally第一行。
public class Test04 {
public static void main(String[] args) throws InterruptedException {
MyRu my = new MyRu();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
Thread t3 = new Thread(my);
t1.start();
t2.start();
t3.start();
}
}
class MyRu implements Runnable {
/**
* 共享资源
*/
private int count = 10;
/**
* 隐式锁
*/
private Lock lo = new ReentrantLock();
@Override
public void run() {
while (true) {
lo.lock();
try {
if (count > 0) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ":" + count);
count--;
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lo.unlock();
}
}
}
}
控制台最后都是差不多的打印:
- Java显示锁和隐式锁的区别(点击跳转)
公平锁和不公平锁
公平锁:设置与创建 private Lock lock = new ReentrantLock(true); 不公平锁:Java的三个锁默认都是不公平锁,随机抢锁
非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。
线程死锁
线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,并且互相等待导致程序执行不下去的情况
死锁详细解释说明【Java多线程产生死锁的4个必要条件?如何避免死锁?】
多线程通信
Object的方法 wait() notify() notifyAll()
【线程间通信的几种实现方式】
线程池
【深入理解线程和线程池(图文详解)】 【详细的线程池讲解】
在一个应用程序中,需要多次创建并销毁线程。创建并销毁过程中耗费大量宝贵的内存资源,所以,就有了线程池的概念。
- 线程池用处:
方便管理线程,减少内存的消耗。
- 创建一个线程池:
Java中提供了创建线程池的一个类:Executor 创建时,一般使用它的子类:ThreadPoolExecutor
Java中的四种线程池 . ExecutorService
1. 缓存线程池
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
2. 定长线程池
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
3. 单线程线程池
/**
* 单线程线程池.
* 执行流程:
* 1. 判断线程池的哪个线程是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待池中的单个线程空闲后使用
*/
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
4. 周期性任务定长线程池
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
/**
* 定时执行, 当某个时机触发时, 自动执行某任务
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("定时执行。。。");
}
},5,TimeUnit.SECONDS);
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("周期执行。。。");
}
},5,2,TimeUnit.SECONDS);
注意:阿里编程规范中不建议使用Executors直接创建线程池,而是建议手动创建线程池,原因是Executors创建的线程池存在OOM的风险。
【Java创建线程池正确方式】