线程安全
多个线程,同时操作一个共享资源的时候,可能会出现业务安全问题
线程安全问题出现的问题? 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是并行和并发同时执行的
并发/并行小结

网硕互联帮助中心







评论前必须登录!
注册