多线程
1.线程创建的方法
1.1.方法一 继承Thread类
继承Thread类
- 1.定义一个子类MyThread继承线程类
java.lang.Thread
,重写run()方法 - 2.创建MyThread类的对象
- 3.调用线程对象的start()方法启动线程
优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展
public class MyThread extends Thread{
@Override
public void run() {
// 线程的执行任务
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyThread -> " + i);
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// 创建MyThread线程类的对象 代表一个线程
Thread thread = new MyThread();
// 启动线程(自动执行run方法)
thread.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程MainThread -> " + i);
}
}
}
1.2.方法二 实现Runnable接口
实现Runnable接口
- 1.定义线程任务类MyRunnable实现Runnable接口,重写run()方法
- 2.创建MyRunnable任务对象
- 3.把MyRunnable任务对象交给Thread处理
- 4.调用线程的start()方法启动线程
优缺点:
- 优点:任务类只是实现接口,可以继续继承其他类/实现其他接口,拓展性强
- 缺点:需要多一个Runnable对象
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyRunnable-> " + i);
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
// 创建任务对象
Runnable target = new MyRunnable();
// 把 任务对象 交给 线程对象 处理
new Thread(target).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程Main -> " + i);
}
}
}
匿名内部类的写法
- 1.创建Runnable的匿名内部类对象
- 2.再交给Thread线程对象
- 3.再调用线程对象的start()启动线程
public class ThreadTest2_2 {
public static void main(String[] args) {
// 1.创建匿名内部类
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程 -> " + i);
}
}
};
// 2.再交给Thread线程对象
Thread thread = new Thread(target);
// 3.启动线程
thread.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程Main -> " + i);
}
}
}
1.3.方法三 实现Callable接口
实现Callable接口
- 1.创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
- 把Callable类型的对象封装成FutureTask(线程任务对象)
- 2.把线程任务对象交给Thread对象
- 3.调用线程对象的start()启动线程
- 4.线程执行完毕后,通过FutureTask对象的get方法去获取线程任务的执行结果
优缺点:
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后获取线程执行的结果
- 缺点:编码复杂
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
// 假设 求1~n的和 并返回
for (int i = 1; i <= n; i++) {
sum += i;
}
return "线程求出1~" + n + "的和:" + sum;
}
}
public class ThreadTest3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建Runnable对象
Callable<String> callable = new MyCallable(100);
// 封装成FutureTask
FutureTask<String> futureTask = new FutureTask<>(callable);
// 交给Thread对象
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
// 获取线程执行完毕后返回的结果
String result = futureTask.get();
System.out.println("result = " + result);
}
}
2.线程安全
2.0.线程不安全的案例
案例:模拟线程安全问题。小红和小明同时取钱(同一个账户)
public class ThreadTest {
public static void main(String[] args) {
Account account = new Account("ICBC-100", 100000);
new DrawThread(account, "小明").start(); // 小明
new DrawThread(account, "小红").start(); // 小红
}
}
public class DrawThread extends Thread{
private final Account account;
public DrawThread(Account account, String name) {
super(name);
this.account = account;
}
@Override
public void run() {
account.drawMoney(100000);
}
}
public class Account {
private String cardId;
private double money; // 余额
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
2.1.方式一:同步代码块
**作用:**把访问共享资源的核心代码给上锁,一次保证线程安全
synchronized (同步锁) {
访问共享资源的核心代码
}
注意事项
- 对于当前同时执行的线程来说,同步锁必须是同意把(同一个对象),否则会出bug
在案例中添加同步代码块 (实例方法使用this
作为锁对象)
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
synchronized (this) {
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
}
如果要求在静态方法中保证线程安全,同步锁应该是 类名.class
public static void test() {
synchronized (类名.class) {
// 核心代码
}
}
2.2.方式二:同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进入执行
修饰符 synchronized 返回值类型 方法名称 (形参列表) {
操作共享资源的代码
}
在案例中添加同步方法
public synchronized void drawMoney(double money) {
String name = Thread.currentThread().getName();
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
底层原理
- 底层也是与隐式锁对象,锁的范围是整个方法代码
- 如果方法是实例方法:同步方法默认用
this
作为锁的对象 - 如果方法是静态方法:同步方法默认用
类名.class
作为锁的对象
2.3.方式三:Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活/方便/强大
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象
public class Account {
// 创建一个锁对象
private final Lock lock = new ReentrantLock();
//略
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
try {
lock.lock(); //加锁
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock(); // 解锁
}
}
}
注意事项
- 1.用
final
修饰 - 2.用
try...catch...finally
包裹住加锁与解锁操作,保证即使出现异常,也可确保能解锁
3.线程池
线程池就是一个复用线程的技术
不断创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来
3.1.创建线程池
线程池接口:ExecutorService
**方式一:**使用 ExecutorService
的实现类 ThreadPoolExecutor
创建一个线程池对象
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- 参数一:
corePoolSize
指定线程池的核心线程数量 - 参数二:
maximumPoolSize
线程池的最大线程数量(maximumPoolSize > corePoolSize) - 参数三:
keepAliveTime
临时线程的存活时间 - 参数四:
unit
指定临时线程存活的时间单位(秒/时/分/天) - 参数五:
workQueue
指定线程池的任务队列 - 参数六:
ThreadFactory
指定线程池的线程工厂 - 参数七:
RejectedExecutionHandler
指定线程池的任务拒绝策略(线程都在忙,任务队列也满了,新任务来了该怎么处理)
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
策略 (参数七) | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExecutionException异常。默认策略 |
ThreadPoolExecutor.DiscardPolicy() | 丢弃任务,但是不抛出异常。(不推荐做法) |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃队列等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy() | 有主线程负责调用任务的run()方法从二绕过线程池直接执行 (老板亲自执行任务) |
注意事项
- 临时线程什么时候创建
- 新任务提交时发现核心线程在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
- 什么时候开始拒绝新任务
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
**方式二:**使用 Excutors
(线程池的工具类) 调用方法返回不同特点的线程池对象
方法名称 | 说明 |
---|---|
ExcutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池 |
ExcutorService newSingleTHreadExecutor() | 创建只有一个线程的线程池 |
ExcutorService newCachedThreadPool() | 线程数量随着任务增加而增加 |
ExcutorService newScheduledThreadPool(int corePoolSize) |
ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService pool = Executors.newSingleThreadExecutor();
核心线程数量到底配置多少
计算密集型的任务:核心线程数量 = CPU的核数 + 1
IO密集型的任务:核心线程数量 = CPU的核数 * 2
3.2.线程池处理Runnable任务
public static void main(String[] args) {
// 1.通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
Runnable target = new MyRunnable();
// 线程池会自动创建新线程,自动处理这个任务,自动执行
pool.execute(target); // 第一个线程
pool.execute(target); // 第二个线程
pool.execute(target); // 第三个线程
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 临时线程的创建时机
pool.execute(target);
pool.execute(target);
// 到了新任务拒绝的时机
pool.execute(target);
// 线程池不会主动关闭,程序会一直运行
pool.shutdown(); // 等待线程池任务全部完成后,再关闭线程池
//pool.shutdownNow();// 立即关闭线程,不管是否还有任务在执行
}
3.3.线程池处理Callable任务
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception{
// 1.通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 2.使用线程处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println("f1.get() = " + f1.get());
System.out.println("f2.get() = " + f2.get());
System.out.println("f3.get() = " + f3.get());
System.out.println("f4.get() = " + f4.get());
// 线程池不会主动关闭,程序会一直运行
pool.shutdown(); // 等待线程池任务全部完成后,再关闭线程池
//pool.shutdownNow();// 立即关闭线程,不管是否还有任务在执行
}
}
4.并发和并行
**进程:**正常运行的程序/软件就是一个独立的进程
**线程:**线程是属于进程的,一个进程中可以同时运行很多个线程
进程中的多个线程是并发和并行执行的
并发:
- 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能执行,CPU会轮询为系统的每个线程服务。由于CPU切换的速度很快,给我们感觉就是线程都在同时执行,这就是并发
并行:
- 在同一个时刻上,同时有多个线程被CPU调度执行
多线程是怎么执行的
- 并发和并行同时执行
原文地址:https://blog.csdn.net/weixin_46926189/article/details/136087130
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_68233.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!