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

线程安全

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

线程安全问题出现的问题?

1、存在多个线程在同时执行

2、同时访问一个共享资源

3、存在修改该共享资源

模拟线程安全问题

java 复制代码
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只锁定特定的账户实例,不同账户间的操作不会相互阻塞

锁对象的使用规范

java 复制代码
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、锁的获取机制:

对于同一个实例对象,同一时间内只能有一个线程能够获得该实例的锁并执行同步方法

java 复制代码
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个)里解锁

java 复制代码
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个参数:前四个简单,后三个固定

java 复制代码
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创建线程池

最后一个方法属于底层,重点是第一个

java 复制代码
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任务

java 复制代码
        //目标:创建线程池对象
        //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)亲自来执行新的线程

java 复制代码
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任务

java 复制代码
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是并行和并发同时执行的

并发/并行小结

相关推荐
Anastasiozzzz7 分钟前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人8 分钟前
通过脚本推送Docker镜像
java·docker·容器
铁蛋AI编程实战25 分钟前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
晚霞的不甘36 分钟前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
SunnyDays101138 分钟前
使用 Java 冻结 Excel 行和列:完整指南
java·冻结excel行和列
摇滚侠1 小时前
在 SpringBoot 项目中,开发工具使用 IDEA,.idea 目录下的文件需要提交吗
java·spring boot·intellij-idea
云姜.1 小时前
java多态
java·开发语言·c++
李堇1 小时前
android滚动列表VerticalRollingTextView
android·java
泉-java1 小时前
第56条:为所有导出的API元素编写文档注释 《Effective Java》
java·开发语言
zfoo-framework2 小时前
帧同步和状态同步
java