1. 认识多线程*
- 是软件或硬件的多个线程并发执行的技术。
- 在硬件的支持下,提高计算机在同一时间执行多个线程的性能。
2. 前置基础概念
- 并发:同一时刻,多个指令在单个CPU上交替执行
- 并行:同一时刻,多个指令在多个CPU上同时执行
3. 进程和线程
- 进程:正在执行的任务(软件)
* 独立性:进程是独立运行的基本单位,为系统分配资源和调度的独立单位。
* 动态性:程序的一次执行过程,进程是动态产生,动态消亡。程序=>编译=>打包=>部署…
* 并发性:多个进程之间执行的任务是一起并发执行- 线程:是进程中的一条控制路径
* 单线程:进程只有一条路径执行程序
* 多线程:进程有多条路径执行程序
4. 多线程的实现方式
4.1 继承Thread类
两条线程效果图:
@Override
public void run() {
//模拟线程一
for (int i = 0; i < 100; i++) {
System.out.println("=========111======>"+i);
}
//模拟线程二
for (int i = 0; i < 100; i++) {
System.out.println("==========222======>"+i);
}
}
public static void main(String[] args) {
//创建线程一
MyThread t1 = new MyThread();
//开启线程一
t1.start();
//创建线程二
MyThread t2 = new MyThread();
//开启线程二
t2.start();
}
思考:
- 继承Thread类,为什么要重写run()方法?
run()用来封装被线程执行的代码。
- run()方法和start()方法有什么区别?
start()开启线程由虚拟机调用线程的run()方法;run()没有开启线程,相当于普通方法的调用。
4.2 实现Runnable接口
两条线程效果图:
@Override
public void run() {
System.out.println("runnable.....");
for (int i = 0; i < 100; i++) {
System.out.println("=====111=====>"+i);
}
}
public static void main(String[] args) {
//创建一个参数对象
MyRunnable mr1 = new MyRunnable();
//创建一个线程对象,并把参数传递给线程
//线程启动后执行run方法
Thread t1 = new Thread(mr1);
t1.start();
MyRunnable mr2 = new MyRunnable();
Thread t2 = new Thread(mr2);
t2.start();
}
4.3 实现Callable接口
//Callable<Object> 泛型表示返回值的数据类型:字符串 integer List集合 对象 ....
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("=====和心上的女孩表白====="+i);
}
//返回值表示线程运行完毕之后的结果
return "愿意做女朋友";
}
//线程开启后执行call方法
MyCallable mc1 = new MyCallable();
//可以获取线程执行完毕之后的结果,也可以作为参数传递给Thread对象
FutureTask<String> ft1 = new FutureTask<>(mc1);
//创建线程对象
Thread t1 = new Thread(ft1);
//开启线程
t1.start();
String s1 = ft1.get();
System.out.println("=====女孩返回结果111=========="+s1);
MyCallable mc2 = new MyCallable();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2);
t2.start();
String s2 = ft2.get();
System.out.println("======女孩返回结果222========>"+s2);
5. Thread方法中常用的API
获取线程名称:getName
System.out.println("线程:"+getName()+"=========111======>"+i);
设置线程名称:setName 构造器
MyThread t1 = new MyThread();
t1.setName("hsp001");
获取当前线程对象以及当前对象的名称:Thread.currentThread().getName();
System.out.println(Thread.currentThread().getName()+"=====111=====>"+i);
设置线程休眠:单位: ms Thread.sleep(3000);
@Override
public void run() {
System.out.println("runnable.....");
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"=====111=====>"+i);
}
}
守护线程:setDaemon(true);
线程调度:
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同,就随机选择
* getPriority(); //获取线程优先级
* setPriority(1); //设置线程的优先级 优先级范围:1 – 10 默认值:5
6 线程的生命周期
7 线程安全的问题
案例引入
- 需求:电影院售票系统(12306火车票),一共100张票,有3个窗口卖票,模拟卖票的过程
核心代码
//当前系统的总票数(张)
private Integer ticketCount = 100;
@Override
public void run() {
while (true){
if (ticketCount == 0){
//卖完啦
//线程结束
break;
}else {
//还有余票
ticketCount--;
System.out.println(Thread.currentThread().getName()+"正在卖票,余票"+ticketCount+"张");
}
}
}
测试类
Ticket ticket = new Ticket(); //共享100张票的资源
//创建三个线程 ====> 三个窗口
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
问题:
- 实际的卖票过程中买完票后出票需要时间等待?
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
出现以上两个问题的原因?
- 多个线程共享了操作数据
如何解决这两个问题呢?
- 多条语句操作共享数据的代码给锁起来,在任意时刻只给一条线程执行。
//创建锁对象
private Object obj = new Object();
@Override
public void run() {
while (true){
//同步代码块:给我们操作共享数据的代码锁上啦
synchronized (obj) {
if (ticketCount == 0) {
//卖完啦
//线程结束
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//还有余票
ticketCount--;
System.out.println(Thread.currentThread().getName() + "正在卖票,余票" + ticketCount + "张");
}
}
}
}
如果同步代码块锁不唯一出现的现象?
核心代码
//当前系统的总票数(张)
private static Integer ticketCount = 100;
@Override
public void run() {
while (true){
synchronized (this){ //当前线程对象
if (ticketCount == 0) {
//卖完啦
//线程结束
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//还有余票
ticketCount--;
System.out.println(Thread.currentThread().getName() + "正在卖票,余票" + ticketCount + "张");
}
}
}
}
//创建2个线程 ====> 2个窗口
MyThreadTicket t1 = new MyThreadTicket();
MyThreadTicket t2 = new MyThreadTicket();
t1.setName("======窗口1=====>");
t2.setName("======窗口2=====>");
t1.start();
t2.start();
- 成员变量用staitc修饰,可以解决部分重复票问题。但是存在负数票问题。
- synchronized锁对象用this,表示对象线程对象不唯一。
同步代码块和同步方法区别
- 同步代码块可以锁住指定代码;同步方法可以锁住所有代码
- 同步代码块可以指定锁对象;同步方法不能指定锁对象
核心代码
//当前系统的总票数(张)
private Integer ticketCount = 100;
@Override
public void run() {
while (true){
if ("窗口1".equals(Thread.currentThread().getName())){
//执行同步方法
boolean result = synchronizedMethod();
if (result){
break;
}
}
if ("窗口2".equals(Thread.currentThread().getName())){
//执行同步代码块
synchronized (this){
if (ticketCount == 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//还有余票
ticketCount--;
System.out.println(Thread.currentThread().getName() + "正在卖票,余票" + ticketCount + "张");
}
}
}
}
}
private synchronized boolean synchronizedMethod() {
if (ticketCount == 0){
return true;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//还有余票
ticketCount--;
System.out.println(Thread.currentThread().getName() + "正在卖票,余票" + ticketCount + "张");
return false;
}
}
测试类
MyRunnableTicket mr = new MyRunnableTicket();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
同步静态方法的锁对象
- 类名.class // MyRunnableTicket.class
Lock锁
//当前系统的总票数(张)
private Integer ticketCount = 100;
//创建自动锁对象
//private Object obj = new Object();
//创建Lock锁对象
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//同步代码块:给我们操作共享数据的代码锁上啦
//synchronized (obj) {
try {
//加lock锁
lock.lock();
if (ticketCount == 0) {
//卖完啦
//线程结束
break;
} else {
Thread.sleep(100);
//还有余票
ticketCount--;
System.out.println(Thread.currentThread().getName() + "正在卖票,余票" + ticketCount + "张");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
//}
}
}
死锁
- 多个线程互相持有对方所需要的资源,导致这些线程处于等待,无法执行。
* 锁不能嵌套使用
Object obj1 = new Object();
Object obj2 = new Object();
new Thread(() -> {
while (true) {
synchronized (obj1) {
synchronized (obj2) {
System.out.println("======彤彤001正在通行====");
}
}
}
}).start();
new Thread(() -> {
while (true){
synchronized (obj2){
synchronized (obj1){
System.out.println("========彤彤002正在通行=======");
}
}
}
}).start();
8 生产者-消费者模式
- 生产者抢到CPU的执行权:
* 首先判断有没有资源
* 如果有等待消费者用,如果没有资源就生产
* 把生成好的资源放到容器中
* 叫醒消费者来用- 消费者抢到CPU的执行权:
* 判断容器里有没有资源
* 如果没有资源就休眠等待
* 如果有就用
* 叫醒等待的生产者来继续生产
* 资源数量 减一
核心代码
共享资源(sharedRes)
//定义桌子的标记
//true 表示桌子上有资源 允许消费者执行
//false 表示桌子上没有资源 允许生产者执行
public static boolean flag = false;
//定义资源的总数量 count
public static Integer count = 10;
//锁对象 消费者 和 生产者用同一把锁
public static final Object obj = new Object();
生产者
@Override
public void run() {
while (true){
synchronized (sharedRes.obj){ //锁对象 和 消费者用同一把锁
if (sharedRes.count == 0){
//有资源 不生产
break;
}else {
//生产者继续做
if (!sharedRes.flag){
//需要生产
System.out.println("=====001正在给彤彤做=======");
sharedRes.flag = true;
sharedRes.obj.notifyAll();
}else {
//等待消费者
try {
sharedRes.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
消费者
@Override
public void run() {
/*
* 方法:
* 1. while()
* 2. synchronized 锁对象
*/
while (true){
synchronized (sharedRes.obj){ //锁对象 和 生产者用同一把锁
if (sharedRes.count == 0){
//没有资源啦 结束线程
break;
}else {
if (sharedRes.flag){
//有资源
System.out.println("=====彤彤正在吃========");
sharedRes.flag = false;
sharedRes.obj.notifyAll();
sharedRes.count--;
}else {
//没有资源,等待生产者
try {
sharedRes.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
测试类
Consumers consumers = new Consumers();
Producters producters = new Producters();
consumers.start();
producters.start();
生产者–消费者代码改写
共享资源(sharedRes)
//定义桌子的标记
//true 表示桌子上有资源 允许消费者执行
//false 表示桌子上没有资源 允许生产者执行
//public static boolean flag = false;
private boolean flag;
//定义资源的总数量 count
//public static Integer count = 10;
//private Integer count =10; //默认值 count = 0
private Integer count;
//锁对象 消费者 和 生产者用同一把锁
//public static final Object obj = new Object();
private final Object obj = new Object();
//构造方法、get/set方法、toString()方法自己生成
消费者
private sharedRes sharedRes;
public Consumers(sharedRes sharedRes) {
this.sharedRes = sharedRes;
}
@Override
public void run() {
/*
* 方法:
* 1. while()
* 2. synchronized 锁对象
*/
while (true){
synchronized (sharedRes.getObj()){ //锁对象 和 生产者用同一把锁
System.out.println("===彤彤是消费者1111=======");
if (sharedRes.getCount() == 0){ //sharedRes 对象的值 0 0.getCount() NullPointerException
System.out.println("===彤彤是消费者222=======");
//没有资源啦 结束线程
break;
}else {
System.out.println("===彤彤是消费者3333=======");
if (sharedRes.isFlag()){
//有资源
System.out.println("=====彤彤正在吃========");
sharedRes.setFlag(false);
sharedRes.getObj().notifyAll();
sharedRes.setCount(sharedRes.getCount() - 1);
}else {
//没有资源,等待生产者
try {
sharedRes.getObj().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
生产者
private sharedRes sharedRes;
public Producters(sharedRes sharedRes) {
this.sharedRes = sharedRes;
}
@Override
public void run() {
while (true){
synchronized (sharedRes.getObj()){ //锁对象 和 消费者用同一把锁
System.out.println("======彤彤生产者1111=======");
if (sharedRes.getCount() == 0){
System.out.println("======彤彤生产者2222=======");
//有资源 不生产
break;
}else {
System.out.println("======彤彤生产者3333=======");
//生产者继续做
if (!sharedRes.isFlag()){
//需要生产
System.out.println("=====001正在给彤彤做=======");
sharedRes.setFlag(true);
sharedRes.getObj().notifyAll();
}else {
//等待消费者
try {
sharedRes.getObj().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
测试类
//创建共享资源对象
sharedRes sharedRes = new sharedRes();
//创建生产者和消费者线程
Consumers consumers = new Consumers(sharedRes);
Producters producters = new Producters(sharedRes);
//开启线程
consumers.start();
producters.start();
效果图
以上内容是JavaSE的多线程初次相识,记录了我的学习路径,重点需要理解其中的概念和最后的生产者消费者模式。对此如果更好的理解欢迎在评论区留言!如果你在看就点击下方“在看”,点个“赞”,你会变好看!