线程创建,线程通信,线程安全

谁借莪1个温暖的怀抱¢ 2023-10-10 15:53 95阅读 0赞

单线程

线程是一个内部程序的一条执行路径。

我们之前执行的main路程就是一条执行线程

程序中只有一条执行的线程就是单线程的。多线程,反之亦然

多线程创建方式

多线程的创建的方式:

    • 继承自Thread
    • 实现Runnable接口
    • Jdk 5.0 增加了:实现Callable接口
    1. 定义一个子类继承自Thread,重写run() 方法,定义线程任务
    2. 创建子类对象
    3. 调用对象的start()方法,启动线程(启动后还是执行run()方法的代码)

为什么不直接调用run()方法要调用start()方法?

直接调用run方法会变成普通方法,此时相当于还是单线程

只有调用start()方法才是启动一个新的线程

不能把主线程放在子线程前面这样相当于一个单线程的效果,这样会优先执行主线程

实现接口

继承实现多线程

实现线程步骤

  1. 在run()方法中编写线程任务,继承线程类
  2. 在测试类中先创建线程任务类的对象,将该对象给线程对象
  3. 创建线程对象
  4. 调用start() 启动线程

    package com.pan.crizab;
    //缺点较多,不便于扩展接口任务对象的功能
    public class ThreadExtend extends Thread {

    1. public void run(){
    2. System.out.println("通过继承的方式实现线程的任务");
    3. }

    }
    class Main{

    1. public static void main(String[] args) {
    2. //1.创建线程任务对象
    3. ThreadExtend threadExtend = new ThreadExtend();
    4. //2.创建线程对象
    5. Thread thread1 = new Thread(threadExtend);
    6. thread1.start();
    7. }

    }

缺点:不便于扩展,不能处理结果集

实现runnable接口方式

实现线程步骤

  1. 实现接口Runnable,在run()方法中编写线程任务
  2. 在测试类中先创建线程任务类的对象,将该对象给线程对象
  3. 创建线程对象
  4. 调用start() 启动线程

    package com.pan.crizab;
    //便于扩展,但是不能处理返回要求
    public class RunnableImp extends Thread implements Runnable{

    1. @Override
    2. public void run() {
    3. System.out.println("通过实现runnable接口实现线程任务");
    4. }

    }

    class Main1{

    1. public static void main(String[] args) {
    2. //1.多态式创建接口实现类线程任务对象
    3. Runnable runnable1 = new RunnableImp();
    4. //2.创建线程,将线程任务对象传入线程
    5. Thread thread1 = new Thread(runnable1);
    6. //3.启动线程
    7. thread1.start();
    8. }

    }

缺点:不能处理线程任务的返回值,没有结果返回

优点:扩展性强,在实现接口的同时可以继承其他的类,完成一些方法的重写

实现Callable函数式接口

Callable类有

实现线程步骤

  1. 实现callable接口,在run()方法中编写线程任务
  2. 将线程任务对象给FutureTask对象进行修饰包装

    1. FutureTask类是继承自runnable类,也就是其本质还是Runnable接口只是实现了返回结果的处理
  3. 在测试类中先创建线程任务类的对象,将给对象给线程对象
  4. 创建线程对象
  5. 调用start() 启动线程
  6. 使用futureTask对象调用get获取结果返回

    package com.pan.crizab;

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;

    public class CallableImp {

    1. public static void main(String[] args) {
    2. //创建线程任务对象
    3. Callable callable = new CallableIm(100);
    4. //封装该对象,使之可以在线程对象中入参,以便后面调用get()方法获取返回值
    5. FutureTask<Object> f1 = new FutureTask<Object>(callable);
    6. Thread t1 = new Thread(f1);
    7. t1.start();
    8. try {
    9. int s = (Integer) f1.get();
    10. System.out.println("f1执行后的结果"+s);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. } catch (ExecutionException e) {
    14. e.printStackTrace();
    15. }
    16. //创建线程任务对象
    17. Callable callable2 = new CallableIm(100);
    18. //封装该对象,使之可以在线程对象中入参,以便后面调用get()方法获取返回值
    19. FutureTask<Object> f2 = new FutureTask<Object>(callable);
    20. Thread t2 = new Thread(f2);
    21. t2.start();
    22. try {
    23. int s = (Integer) f2.get();
    24. System.out.println("f2执行后的结果"+s);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. } catch (ExecutionException e) {
    28. e.printStackTrace();
    29. }
    30. }

    }

  1. class CallableIm implements Callable<Object>{
  2. private int n;
  3. public CallableIm(int n) {
  4. this.n = n;
  5. }
  6. @Override
  7. public Object call() throws Exception {
  8. int sum = 0;
  9. for(int i = 0;i<n;i++){
  10. sum += i;
  11. }
  12. System.out.println("线程执行任务");
  13. return sum;
  14. }
  15. }

缺点:代码量过多

优点:便于扩展,便于处理结果的返回值,

线程常用API

区分各个线程:setName(String name),getName();

sleep();

wait()

notifyAll();

在许多的线程中,创建当前的线程对象

线程安全问题

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

详解:

银行取钱:

小红与小白拥有一个共同账户,余额10万元

小红和小白同时取钱10万元

线程并发执行,此时都判断都为可以取出,则同时取出10万元

出现余额为-10万余元的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVZn04CW-1672539577713)(D:\HONOR Magic-link\Screenshot\capture_20221230210642785.bmp)]

解决办法:也就是加锁,对资源区加锁

synchronized

  • 代码块形式

    其中 this 就是账户类的对象,用于指定访问时的线程

    1. synchronized (this){
    2. //代码
    3. }
  • 方法修饰形式

    1. //同步方法锁
    2. public synchronized void drawMoney(double money){
    3. //获取用户的Id()
    4. String name = Thread.currentThread().getName();
    5. //lock.lock();
    6. if(this.money >= money){
    7. System.out.println(""+name+"来取钱,取出"+money );
    8. this.money -= money;
    9. Thread.sleep(5000);
    10. System.out.println("余额:"+ this.money);
    11. }
    12. else{
    13. System.out.println(name+"来取钱"+money);
    14. System.out.println("余额不足");
    15. }
    16. //lock.unlock();
    17. }

Lock:

//防止中途出现异常,迟迟不释放资源区

所以当出现异常时,也要执行完 unlock();

lock();

unlock();

  1. finally{
  2. lock1.unlock();
  3. }

账户类

  1. package com.pan.items;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class Account {
  5. private String carId;
  6. private Double money;
  7. //创建Lock锁的实现类对象
  8. private final Lock lock = new ReentrantLock();
  9. public Account(String carId, Double money) {
  10. this.carId = carId;
  11. this.money = money;
  12. }
  13. //同步方法锁
  14. public synchronized void drawMoney(double money){
  15. //获取用户的Id()
  16. String name = Thread.currentThread().getName();
  17. // lock.lock();
  18. //存钱
  19. if(this.money >= money){
  20. System.out.println(""+name+"来取钱,取出"+money );
  21. this.money -= money;
  22. System.out.println("余额:"+ this.money);
  23. }
  24. else{
  25. System.out.println(name+"来取钱"+money);
  26. System.out.println("余额不足");
  27. }
  28. //lock.unlock();
  29. }
  30. public String getCarId() {
  31. return carId;
  32. }
  33. public void setCarId(String carId) {
  34. this.carId = carId;
  35. }java
  36. public Double getMoney() {
  37. return money;
  38. }
  39. public void setMoney(Double money) {
  40. this.money = money;
  41. }
  42. }

线程任务类:

  1. package com.pan.items;
  2. public class RunnableDemo extends Thread implements Runnable {
  3. private Account account;
  4. public RunnableDemo(String name, Account account) {
  5. super(name);
  6. this.account = account;
  7. }
  8. @Override
  9. public void run() {
  10. account.drawMoney(100000.0);
  11. }
  12. }

执行测试类:

  1. package com.pan.items;
  2. public class Main1 {
  3. public static void main(String[] args) {
  4. Account account = new Account("123ID",100000.0);
  5. new RunnableDemo("小红",account).start();
  6. new RunnableDemo("小白",account).start();
  7. }
  8. }

结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6FsgKuk-1672539577716)(C:\Users\abundan\AppData\Roaming\Typora\typora-user-images\image-20221230210537496.png)]

线程通信

详解

线程通信就是各个线程之间共享信息,操作同一个资源区,各个线程可以接收来自不同线程所共享的数据,根据数据信息抢占资源或者暂停资源的夺取。

eg:

五个人共享一个账户,账户:资源区。五个人:五个线程

三人负责存钱,两人负责取钱

但同一时间只能有一个人可以操作该账户,所以抢到资源后就要停止他们的争夺,就会用到通信的API

wait(),notifyAll(),

如下代码实现

  1. public synchronized void drawMoney(double money) throws Exception {
  2. //获取用户的Id()
  3. String name = Thread.currentThread().getName();
  4. // lock.lock();
  5. if(this.money >= money){
  6. System.out.println(""+name+"来取钱,取出"+money );
  7. this.money -= money;
  8. System.out.println("余额:"+ this.money);
  9. this.notifyAll();
  10. this.wait();
  11. }
  12. else{
  13. System.out.println(name+"来取钱"+money);
  14. System.out.println("余额不足,通知存钱");
  15. this.notifyAll();
  16. this.wait();
  17. }
  18. //lock.unlock();
  19. }
  20. public synchronized void saveMoney(double v) throws Exception {
  21. String name = Thread.currentThread().getName();
  22. if (this.money == 0){
  23. System.out.println(name+"来存钱同时通知其他线程取钱,存钱数量:"+v);
  24. this.money += v;
  25. Thread.sleep(5000);
  26. this.notifyAll();
  27. this.wait();
  28. }else {
  29. System.out.println(name+"查询=====================还有钱,余额:"+money);
  30. this.notifyAll();
  31. this.wait();
  32. }
  33. }

发表评论

表情:
评论列表 (有 0 条评论,95人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线(3)- 线通信

    线程之间的通信: 多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。 等待唤醒机...