云计算百科
云计算领域专业知识百科平台

线程安全、线程同步(三种加锁方式)、线程池(两种创建线程池方式、线程池处理Runnable任务、线程池处理Callable任务)、并发/并行

线程安全

多个线程,同时操作一个共享资源的时候,可能会出现业务安全问题

在这里插入图片描述 线程安全问题出现的问题? 1、存在多个线程在同时执行 2、同时访问一个共享资源 3、存在修改该共享资源

模拟线程安全问题

在这里插入图片描述

package com.itheima.demo3threadsafe;

public class ThreadDemo1 {
public static void main(String[] args) {
//目标:穆尼线程安全问题
//1、设计一个账户类,用于创建小明和小红的共同账户对象,存入10万
Account acc = new Account(100000, "6516666");

//2、设计线程类,创建小明和小红两个线程,模拟小明和小红同时去同一个账户取款10万
new DrawThread("小明", acc).start();
new DrawThread("小红", acc).start();
}
}

//=================分界线=================

package com.itheima.demo3threadsafe;
//取钱线程类
public class DrawThread extends Thread{
private Account acc;//记住线程对象要处理的账户对象

public DrawThread(String name, Account acc) {
super(name);
this.acc = acc;
}

@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
}

//=================分界线=================

package com.itheima.demo3threadsafe;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//共同账户类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private double money;//余额
private String cardId;//卡号

//小明和小红都到这里来取钱
public void drawMoney(double imoney) {
//拿到当前是谁来取钱
String name = Thread.currentThread().getName();
//判断余额是否充足
if (money >= imoney) {
System.out.println(name + "取钱成功,余额为:" + money);
//更新余额
money = money imoney;
System.out.println(name + "取钱成功,余额为:" + money);
} else {
//余额不足
System.out.println(name + "取钱失败,余额不足");
}
}
}

编译结果: 在这里插入图片描述 出现线程安全问题

线程同步

线程同步是线程安全问题的解决方案

在这里插入图片描述

线程同步常见方案:

加锁 在这里插入图片描述

1、同步代码块

锁的获取机制: 1、对于同一个锁对象,同一时间内只能有一个线程能够获得该锁并执行同步代码块 2、线程在进入 synchronized(锁对象) 块时获取锁 3、执行完同步代码块后自动释放锁

在这里插入图片描述 选中核心代码按住"ctrl+Alt+t"键快捷锁住 使用this只锁定特定的账户实例,不同账户间的操作不会相互阻塞

锁对象的使用规范 在这里插入图片描述

package com.itheima.demo4_synchronized_code.demo3threadsafe;

public class ThreadDemo1 {
public static void main(String[] args) {
//目标:线程同步的方式一演示:同步代码块
//1、设计一个账户类,用于创建小明和小红的共同账户对象,存入10万
Account acc = new Account(100000, "6516666");

//2、设计线程类,创建小明和小红两个线程,模拟小明和小红同时去同一个账户取款10万
new DrawThread("小明", acc).start();
new DrawThread("小红", acc).start();
}
}

//=================分界线=================

package com.itheima.demo4_synchronized_code.demo3threadsafe;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//共同账户类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private double money;//余额
private String cardId;//卡号

//小明和小红都到这里来取钱
public void drawMoney(double imoney) {
//拿到当前是谁来取钱
String name = Thread.currentThread().getName();
//判断余额是否充足
synchronized (this) //使用this只锁定特定的账户实例,不同账户间的操作不会相互阻塞
{
if (money >= imoney) {
System.out.println(name + "取钱成功,余额为:" + money);
//更新余额
money = money imoney;
System.out.println(name + "取钱成功,余额为:" + money);
} else {
//余额不足
System.out.println(name + "取钱失败,余额不足");
}
}
}
}

//=================分界线=================

package com.itheima.demo4_synchronized_code.demo3threadsafe;

//取钱线程类
public class DrawThread extends Thread{
private Account acc;//记住线程对象要处理的账户对象

public DrawThread(String name, Account acc) {
super(name);
this.acc = acc;
}

@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
}

同步代码块小结 在这里插入图片描述

2、同步方法(简单)

1、给核心方法的前缀加上synchronized即可 2、锁的获取机制: 对于同一个实例对象,同一时间内只能有一个线程能够获得该实例的锁并执行同步方法

在这里插入图片描述

public synchronized void drawMoney(double imoney) {
//拿到当前是谁来取钱
String name = Thread.currentThread().getName();
//判断余额是否充足
if (money >= imoney) {
System.out.println(name + "取钱成功,余额为:" + money);
//更新余额
money = money imoney;
System.out.println(name + "取钱成功,余额为:" + money);
} else {
//余额不足
System.out.println(name + "取钱失败,余额不足");
}
}

比较同步代码块和同步方法 在这里插入图片描述

同步方法小结 在这里插入图片描述

3、lock锁

new一个锁对象后先上锁,然后在final(Ctrl+Alt+t快捷键第7个)里解锁

在这里插入图片描述

package com.itheima.demo6_lock.demo3threadsafe;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private double money;//余额
private String cardId;//卡号
private final Lock lk = new ReentrantLock();//创建锁对象,不能静态,final是保护锁对象

//小明和小红都到这里来取钱
public void drawMoney(double imoney) {
//拿到当前是谁来取钱
String name = Thread.currentThread().getName();
lk.lock();//上锁
try {
//判断余额是否充足
if (money >= imoney) {
System.out.println(name + "取钱成功,余额为:" + money);
//更新余额
money = money imoney;
System.out.println(name + "取钱成功,余额为:" + money);

} else {
//余额不足
System.out.println(name + "取钱失败,余额不足");
}
} finally {
lk.unlock();//解锁,使用Ctrl+Alt+t 把该代码放在final里解锁,即使出现异常也能顺利解锁
}
}
}

lock锁小结

在这里插入图片描述

线程池

假设有3个线程,9个任务,这三个线程各自处理一个任务,当前任务处理完后就可以继续处理下一个任务,即完成了线程复用,这个线程复用的技术就称为线程池 在这里插入图片描述

创建线程池

1、使用实现类创建 2、使用线程池的工具类 在这里插入图片描述

方式一:通过ThreadPoolExectuor创建线程池

7个参数:前四个简单,后三个固定 在这里插入图片描述

package com.itheima.demo7executiorService;
import java.util.concurrent.*;

public class ExecutorServiceDemo1 {
public static void main(String[] args) {
//目标:创建线程池对象
//1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

//2、使用线程池处理任务,看看会不会复用线程
}
}

方式二:通过Executors创建线程池

在这里插入图片描述 最后一个方法属于底层,重点是第一个

package com.itheima.demo7executiorService;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorsDemo3 {
public static void main(String[] args) {
//通过线程池工具类:Executors,调用其静态方法直接得到线程池
ExecutorService pool = Executors.newFixedThreadPool(3);//创建固定大小的线程池

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));//线程复用

try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}

通过Executors创建线程池缺点: 在这里插入图片描述

通过Executors创建线程池小结

在这里插入图片描述

处理Runnable任务

在这里插入图片描述

//目标:创建线程池对象
//1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

//2、使用线程池处理任务,看看会不会复用线程
Runnable target = new MyRunnable();
pool.execute(target);//提交第1个任务 创建线程 自动启动线程处理这个任务
pool.execute(target);//提交第2个任务 创建线程 自动启动线程处理这个任务
pool.execute(target);//提交第3个任务 创建线程 自动启动线程处理这个任务
pool.execute(target);//线程复用,重新利用了以上三个线程,因为线程池规定只能有三个线程
pool.execute(target);

//3、关闭线程池:一般不关闭线程池
pool.shutdown();//等所有任务执行完毕后再关闭线程池
pool.shutdownNow();//不等所有任务执行完毕,直接关闭线程池

在这里插入图片描述 第四个意思是老板(主线程main)亲自来执行新的线程

package com.itheima.demo7executiorService;

import java.util.concurrent.*;

public class ExecutorServiceDemo1 {
public static void main(String[] args) {
//目标:创建线程池对象
//1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

//2、使用线程池处理任务,看看会不会复用线程
Runnable target = new MyRunnable();
pool.execute(target);//提交第1个任务 创建线程 自动启动线程处理这个任务
pool.execute(target);//提交第2个任务 创建线程 自动启动线程处理这个任务
pool.execute(target);//提交第3个任务 创建线程 自动启动线程处理这个任务
pool.execute(target);//线程复用,重新利用了以上三个线程,因为线程池规定只能有三个线程
pool.execute(target);
pool.execute(target);
pool.execute(target);//三个核心线程满了,三个任务队列也满了,到了临时线程创建的时机
pool.execute(target);//临时线程继续创建
pool.execute(target);//到了任务拒绝策略,忙不过来,会抛异常

//3、关闭线程池:一般不关闭线程池
pool.shutdown();//等所有任务执行完毕后再关闭线程池
pool.shutdownNow();//不等所有任务执行完毕,直接关闭线程池
}
}

//=================分界线=================

package com.itheima.demo7executiorService;

//1、定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable{
//2、重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"输出:" + i);
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

处理Callable任务

在这里插入图片描述

package com.itheima.demo7executiorService;

import java.util.concurrent.*;

public class ExecutorServiceDemo2 {
public static void main(String[] args) {
//目标:处理Callable任务
//1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

//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));

try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}

//=================分界线=================

package com.itheima.demo7executiorService;

import java.util.concurrent.Callable;

//1、定义一个实现类实现Callable接口
//这个类的泛型是什么类型重写的call方法就要是什么类型,返回的也就是什么类型的数据
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) //把循环值通过有参构造器在具体对象中获取
{
this.n = n;
}
//2、重写call方法,将线程任务封装成字符串返回
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
System.out.println(i);
sum += i;
}
return Thread.currentThread().getName()+"计算1-" + n +"的和是:"+ sum;
}
}

并发/并行

线程是属于进程的 在这里插入图片描述

并发的含义:只是执行速度过快让我们认为在同时执行这就是并发

在这里插入图片描述

并行的含义:在同一时刻同时有多个线程被CPU调度

在这里插入图片描述 CPU是并行和并发同时执行的

并发/并行小结

在这里插入图片描述

赞(0)
未经允许不得转载:网硕互联帮助中心 » 线程安全、线程同步(三种加锁方式)、线程池(两种创建线程池方式、线程池处理Runnable任务、线程池处理Callable任务)、并发/并行
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!