• Java基础(四)


    前言:本博客主要涉及java编程中的线程、多线程、生成者消费者模型、死锁。

    目录

    线程多线程

    线程同步

    synchronized

    Lock锁

    线程通信

    生产者消费者模型

    线程池

    使用线程池处理Runnable任务

    使用线程池处理Callable任务

    Excutors

    悲观锁

    乐观锁

    并发VS并行

    线程的生命周期


    线程多线程

    创建一个简单的线程

    获取执行当前代码的线程名:Thread.currentThread().getName()

    开启线程:对象名.start()

    守护线程 优先级

    1. //1.创建一个继承于Thread类的子类
    2. class MyThread extends Thread {
    3. //2.重写Thread类的run()
    4. @Override
    5. public void run() {
    6. for (int i = 0; i < 100; i++) {
    7. System.out.println(Thread.currentThread().getName() + ":" + i);
    8. }
    9. }
    10. }
    11. public class ThreadTest {
    12. public static void main(String[] args) {
    13. MyThread t1 = new MyThread();
    14. t1.start();
    15. }
    16. }
    1. package learn12;
    2. class MyRunnable implements Runnable {
    3. @Override
    4. public void run() {
    5. for (int i = 1; i <= 5; i++) {
    6. System.out.println("子线程输出:" + i);
    7. }
    8. }
    9. }
    10. public class MyThreadTest2 {
    11. public static void main(String[] args) {
    12. Runnable target = new MyRunnable();
    13. new Thread(target).start();
    14. for (int i = 1; i <= 5; i++) {
    15. System.out.println("主线程main输出:" + i);
    16. }
    17. }
    18. }

    线程安全

    例子:多个人同时去一个账户里取钱,被取出后,不能再取出钱。但多个线程执行时,在访问时,账户里有钱的,但实际上已经被其他用户取走,此时再取钱就会引发数据不安全的问题。

    使用多线程时,每个线程都对数据进行修改,如何来保证数据的安全性?

    1. package learn12;
    2. public class Account {
    3. private String cardId;
    4. private double money;
    5. public Account() {
    6. }
    7. public Account(String cardId, double money) {
    8. this.cardId = cardId;
    9. this.money = money;
    10. }
    11. public String getCardId() {
    12. return cardId;
    13. }
    14. public void setCardId(String cardId) {
    15. this.cardId = cardId;
    16. }
    17. public double getMoney() {
    18. return money;
    19. }
    20. public void setMoney(double money) {
    21. this.money = money;
    22. }
    23. public void drawMoney(double money) {
    24. String name = Thread.currentThread().getName();
    25. if (this.money >= money) {
    26. System.out.println(name + "来取钱" + money + "成功!");
    27. this.money -= money;
    28. System.out.println(name + "来取钱,此时剩余:" + this.money);
    29. } else {
    30. System.out.println(name + "来取钱,但余额不足");
    31. }
    32. }
    33. }
    1. package learn12;
    2. public class DrawThread extends Thread {
    3. private Account acc;
    4. public DrawThread(Account acc, String name) {
    5. super(name);
    6. this.acc = acc;
    7. }
    8. @Override
    9. public void run() {
    10. acc.drawMoney(100000);
    11. }
    12. }
    1. package learn12;
    2. public class ThreadTest {
    3. public static void main(String[] args) {
    4. Account acc = new Account("ICBC-100", 100000);
    5. new DrawThread(acc, "小明").start();
    6. new DrawThread(acc, "小红").start();
    7. new DrawThread(acc, "小李").start();
    8. }
    9. }

    线程同步

    使用了加锁的机制

    synchronized

    用到了同步控制关键字synchronized

    用其修饰成员方法,被修饰的方法,在同一时间,只能被一个线程执行

    1. //同步方法
    2. public synchronized void drawMoney(double money) {
    3. String name = Thread.currentThread().getName();
    4. //同步代码块
    5. synchronized (this) {
    6. if (this.money >= money) {
    7. System.out.println(name + "来取钱" + money + "成功!");
    8. this.money -= money;
    9. System.out.println(name + "来取钱,此时剩余:" + this.money);
    10. } else {
    11. System.out.println(name + "来取钱,但余额不足");
    12. }
    13. }
    14. }

    Lock锁

    注意:解锁要放在finally里,以便程序出错时,可以解锁。

    1. package learn12;
    2. import java.util.concurrent.locks.Lock;
    3. import java.util.concurrent.locks.ReentrantLock;
    4. public class Account {
    5. private String cardId;
    6. private double money;
    7. //创建Lock锁对象
    8. private final Lock lk = new ReentrantLock();
    9. public Account() {
    10. }
    11. public Account(String cardId, double money) {
    12. this.cardId = cardId;
    13. this.money = money;
    14. }
    15. public String getCardId() {
    16. return cardId;
    17. }
    18. public void setCardId(String cardId) {
    19. this.cardId = cardId;
    20. }
    21. public double getMoney() {
    22. return money;
    23. }
    24. public void setMoney(double money) {
    25. this.money = money;
    26. }
    27. public void drawMoney(double money) {
    28. String name = Thread.currentThread().getName();
    29. //加锁
    30. lk.lock();
    31. try {
    32. if (this.money >= money) {
    33. System.out.println(name + "来取钱" + money + "成功!");
    34. this.money -= money;
    35. System.out.println(name + "来取钱,此时剩余:" + this.money);
    36. } else {
    37. System.out.println(name + "来取钱,但余额不足");
    38. }
    39. } catch (Exception e) {
    40. e.printStackTrace();
    41. } finally {
    42. //解锁
    43. lk.unlock();
    44. }
    45. }
    46. }

    线程通信

    生产者线程负责生产数据

    消费者线程负责消费生产者产生的数据

    生产者消费者模型

    1. package learn12;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. public class Desk {
    5. private List list = new ArrayList<>();
    6. public synchronized void put() {
    7. try {
    8. String name = Thread.currentThread().getName();
    9. if (list.size() == 0) {
    10. list.add(name + "做的肉包子");
    11. System.out.println(name + "做了一个肉包子");
    12. Thread.sleep(2000);
    13. //唤醒别人 等待自己
    14. this.notifyAll();
    15. this.wait();
    16. } else {
    17. //唤醒别人 等待自己
    18. this.notifyAll();
    19. this.wait();
    20. }
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. public synchronized void get() {
    26. try {
    27. String name = Thread.currentThread().getName();
    28. if (list.size() == 1) {
    29. System.out.println(name + "吃了:" + list.get(0));
    30. list.clear();
    31. Thread.sleep(1000);
    32. //唤醒别人 等待自己
    33. this.notifyAll();
    34. this.wait();
    35. } else {
    36. //唤醒别人 等待自己
    37. this.notifyAll();
    38. this.wait();
    39. }
    40. } catch (InterruptedException e) {
    41. e.printStackTrace();
    42. }
    43. }
    44. }
    1. package learn12;
    2. public class ThreadTest4 {
    3. public static void main(String[] args) {
    4. Desk desk = new Desk();
    5. //生产者线程
    6. new Thread(() -> {
    7. while (true) {
    8. desk.put();
    9. }
    10. }, "厨师1").start();
    11. new Thread(() -> {
    12. while (true) {
    13. desk.put();
    14. }
    15. }, "厨师2").start();
    16. new Thread(() -> {
    17. while (true) {
    18. desk.put();
    19. }
    20. }, "厨师3").start();
    21. //消费者线程
    22. new Thread(() -> {
    23. while (true) {
    24. desk.get();
    25. }
    26. }, "吃货1").start();
    27. new Thread(() -> {
    28. while (true) {
    29. desk.get();
    30. }
    31. }, "吃货2").start();
    32. }
    33. }

    线程池

    线程池就是一个可以复用线程的技术。

    使用线程池的必要性:

    用户发起一个请求,后台就需要创建一个新线程。不使用线程池,会产生大量的线程,会损害系统的性能。

    线程池可以固定线程和执行任务的数量,可以避免系统瘫痪和线程耗尽的风险。

    创建线程池

    临时线程什么时候创建?

    新任务提交时,发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时会创建临时线程。

    什么时候可以拒绝新任务?

    核心线程和临时线程都在忙,任务队列都满了,新的任务过来时才会开始拒绝任务。

    使用线程池处理Runnable任务

    1. package learn12;
    2. public class MyRunnableLearn implements Runnable {
    3. @Override
    4. public void run() {
    5. System.out.println(Thread.currentThread().getName() + "==>输出666");
    6. try {
    7. Thread.sleep(Integer.MAX_VALUE);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. }
    1. package learn12;
    2. import java.util.concurrent.*;
    3. public class ThreadPoolTest {
    4. public static void main(String[] args) {
    5. //创建线程池对象
    6. // new ThreadPoolExecutor(
    7. // int corePoolSize,
    8. // int maximumPoolSize,
    9. // long keepAliveTime,
    10. // TimeUnit unit,
    11. // BlockingQueue workQueue,
    12. // ThreadFactory threadFactory,
    13. // RejectedExecutionHandler handler
    14. // )
    15. ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
    16. TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
    17. new ThreadPoolExecutor.AbortPolicy());
    18. Runnable target = new MyRunnableLearn();
    19. pool.execute(target);
    20. pool.execute(target);
    21. pool.execute(target);
    22. pool.execute(target);
    23. pool.execute(target);
    24. pool.execute(target);
    25. pool.execute(target);
    26. pool.execute(target);
    27. pool.execute(target);
    28. //都满了 拒绝新任务
    29. pool.execute(target);
    30. // pool.shutdownNow();
    31. //pool.shutdown();
    32. }
    33. }

    使用线程池处理Callable任务

    1. package learn12;
    2. import java.util.concurrent.Callable;
    3. public class MyCallable implements Callable {
    4. private int n;
    5. public MyCallable(int n) {
    6. this.n = n;
    7. }
    8. @Override
    9. public String call() throws Exception {
    10. int sum = 0;
    11. for (int i = 1; i <= n; i++) {
    12. sum += i;
    13. }
    14. return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;
    15. }
    16. }
    1. package learn12;
    2. import java.util.concurrent.*;
    3. public class ThreadPoolTest2 {
    4. public static void main(String[] args) throws Exception {
    5. ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
    6. TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
    7. new ThreadPoolExecutor.CallerRunsPolicy());
    8. //使用线程池处理Callbable任务
    9. Future f1 = pool.submit(new MyCallable(100));
    10. Future f2 = pool.submit(new MyCallable(200));
    11. Future f3 = pool.submit(new MyCallable(300));
    12. //复用前面的线程
    13. Future f4 = pool.submit(new MyCallable(400));
    14. System.out.println(f1.get());
    15. System.out.println(f2.get());
    16. System.out.println(f3.get());
    17. System.out.println(f4.get());
    18. }
    19. }

     

    Excutors

    大型并发系统环境中使用Excutors如果不注意可能会出现系统风险。

    核心线程数

    计算密集型任务:核心线程数量 = CPU核数+1

    IO密集型任务:核心线程数量 = CPU核数*2

    1. package learn12;
    2. import java.util.concurrent.*;
    3. public class ThreadPoolTest2 {
    4. public static void main(String[] args) throws Exception {
    5. //通过Executors创建一个线程池对象
    6. ExecutorService pool = Executors.newFixedThreadPool(3);
    7. Executors.newSingleThreadExecutor();
    8. //使用线程池处理Callbable任务
    9. Future f1 = pool.submit(new MyCallable(100));
    10. Future f2 = pool.submit(new MyCallable(200));
    11. Future f3 = pool.submit(new MyCallable(300));
    12. //复用前面的线程
    13. Future f4 = pool.submit(new MyCallable(400));
    14. System.out.println(f1.get());
    15. System.out.println(f2.get());
    16. System.out.println(f3.get());
    17. System.out.println(f4.get());
    18. }
    19. }

    悲观锁

    一开始就加锁,每次只能一个线程进入访问完毕后,再解锁,线程安全,性能较差。

    1. package learn12;
    2. public class MyRunnable implements Runnable {
    3. public int count;
    4. @Override
    5. public void run() {
    6. for (int i = 0; i < 100; i++) {
    7. System.out.println(this);
    8. synchronized (this) {
    9. System.out.println("count ===>" + (++count));
    10. }
    11. }
    12. }
    13. }

     

    1. package learn12;
    2. public class Test {
    3. public static void main(String[] args) {
    4. Runnable target = new MyRunnable();
    5. for (int i = 1; i <= 100; i++) {
    6. new Thread(target).start();
    7. }
    8. }
    9. }

    乐观锁

    一开始不上锁,等要出现线程安全问题时,等要出现线程安全问题时,线程安全,性能较好。

    如何实现乐观锁

    private AtomicInteger count = new AtomicInteger();
    
    1. package learn12;
    2. import java.util.concurrent.atomic.AtomicInteger;
    3. public class MyRunnable implements Runnable {
    4. private AtomicInteger count = new AtomicInteger();
    5. @Override
    6. public void run() {
    7. for (int i = 0; i < 100; i++) {
    8. System.out.println(this);
    9. synchronized (this) {
    10. System.out.println("count ===>" + count.incrementAndGet());
    11. }
    12. }
    13. }
    14. }

    并发VS并行

    python进阶学习也涉及到这里的知识点。

    Python进阶(二)-CSDN博客

    进程:正在运行的程序

    线程:一个进程中可以运行多个线程

    并发:线程是由CPU调度执行的,但CPU能同时处理的线程数量有限,为了保证全部线程都能执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,好像是线程在同时执行,这就是并发。

    并行:同一时间点,任务同时地运行,比如一台电脑,有8个CPU,每个CPU的每个核心都可以独立地执行一个任务,在同一时间点,可同时执行8个任务,这时任务是同时执行,并行地运行任务。

    线程的生命周期

    New:新建

    Runnable:可运行

    Timinated:被终止

    Timed Waiting:计时等待

    Waiting:无线等待

    Blocked:阻塞

  • 相关阅读:
    使用 Qt for Android 获取并利用手机传感器数据(1)开发环境省心搭建
    配置请求头Content-Type
    使用Seata实现分布式事务
    用LeNet做CIFAR10图像分类,并用streamlit搭建成web
    安装ROS
    Leetcode44: 通配符匹配
    231 基于matlab的北斗信号数据解析
    git ssh建立连接
    flutter run长时间卡在Running Gradle task “assembleDebug“问题解决
    ChatGPT终于接上视觉能力!
  • 原文地址:https://blog.csdn.net/MRJJ_9/article/details/132053276