JAVA基础 day13 多线程

一、多线程

1.1多线程的创建方法?

1.1.1方式一:继承Thread类

java 复制代码
//创建线程的方式之一:继承Thread类
public class demo1 {
    //main方法本身是由一条主线程推进,这里创建了myThread后,已经是多线程了
    public static void main(String[] args) {
        //3.创建一个线程对象,他才是真正代表一个线程
        MyThread myThread = new MyThread();
        //4.调用start方法,启动该线程
        myThread.start();

        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + "主线程"+i);
        }


    }
}
//1.定义一个子类来继承thread类,就可以成为一个线程
class MyThread extends Thread{
    //2.重写继承自thread的run方法,run方法是你要使用该线程完成的功能
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + "子线程" + i);
            System.out.println("\n");
        }
    }

}

注意事项

主线程若放在启动子线程之前, 是会先跑完主线程任务,再启动子线程,相当于没有实现多线程。

1.1.2 方式二:实现Runnable接口

java 复制代码
//创建线程的第二种方法
public class BuildMethod_2 {
    public static void main(String[] args) {
        //3.创建线程任务类对象,代表线程任务
        myThread_Build r = new myThread_Build();
        //4.把线程任务对象,交给一个线程对象处理
        Thread t = new Thread(r);
        //5.启动线程
        t.start();

        for(int i = 0; i < 10; i++){
            System.out.println("主线程" + i);
        }
    }
}
//1.定义一个类,来实现runnable接口
class myThread_Build implements Runnable {
    //2.重写run方法,创建线程任务
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程" + i);
        }
    }
}

匿名内部类写法:

java 复制代码
//使用runnable接口的匿名内部类 + lambda 来简化线程创建
public class Runable_lmbda {
    public static void main(String[] args) {
        new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程" + ":" + i)
                }
            }).start();
    }
}

1.1.3 方式三:实现Callable接口

前两种方式都有一个问题:重写后的run方法,无法返回直接返回结果

由此引出第三种方式:实现callable接口+FutureTask类,可以返回线程执行完后的结果

java 复制代码
//实现callable + futureTask类创建线程
public class Method3_Callable {
    public static void main(String[] args) {
        //3.创建一个实现类对象,来代表线程任务
        Callable my_call = new MyCallable(200);
        //4.把callable的任务对象,包装成FutureTask对象
        FutureTask<String> my_future = new FutureTask<String>(my_call);
        /*
        未来任务对象的作用:
        a.本质是一个runnable线程任务对象,可以交给thread线程任务对象处理
        b.可以获取线程运行后的结果
         */
        //5.把FutureTask对象作为参数,传给thread线程对象
        Thread my_thread = new Thread(my_future);
        //6.使用线程的start方法,启动线程
        my_thread.start();

        //3.创建一个实现类对象,来代表线程任务
        Callable my_call2 = new MyCallable(100);
        //4.把callable的任务对象,包装成FutureTask对象
        FutureTask<String> my_future2 = new FutureTask<String>(my_call2);
        Thread my_thread2 = new Thread(my_future2);
        my_thread2.start();

        //获取线程运行后的结果:使用FutureTask的get方法
        try {
            //如果主运行到这里,发现子线程任务还没跑完,他会让出cpu,等着子线程执行完
            System.out.println(my_future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            System.out.println(my_future2.get());
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}
//1.定义一个实现类,实现callable接口 这是一个泛型接口,实现的时候要声明返回值类型
class MyCallable  implements Callable<String> {

    private final int num;
    public MyCallable(int num) {
        this.num = num;
    }

    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
        }
        return "子线程计算1到"+ "num" + "的结果是" + sum;


    }
}

1.2 多线程的常用方法

2.线程安全

多个线程 ,同时操作 一个共享资源,就可能出现线程安全问题

线程安全出现的三个前提:如上

2.1模拟线程安全问题

java 复制代码
public class Test {
    public static void main(String[] args) {
        //1.设计一个账户类,并用于创建小红和小明的共同账户对象,并存入10w
        Account account1 = new Account(1000,"CARD-1");
        //2.设计线程类:用于创建两个线程,同时去取card1里的钱
        new DrawThread("小明",account1).start();
        new DrawThread("小红",account1).start();

    }
}

账户类:

java 复制代码
//设计一个账户类,用于创建小红和小明的共同账户对象,并存入10w
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
    private int money; //余额
    private String ID; // 卡号

    public void drawMoney(int NumToDraw) {
        //拿到是谁在取钱
        String name = Thread.currentThread().getName();
        //判断余额是否足够
        if(this.money >= NumToDraw) {
            System.out.println("余额足够,取钱成功!");
            this.money -= NumToDraw;
            System.out.println(name + "来取钱,成功!" + "余额为" + this.money);
        }else{
            System.out.println("余额不足");
        }
    }

}

线程类:

java 复制代码
public class DrawThread extends Thread {
    private Account account; // 用来记住线程对象要处理的账户对象
    public DrawThread(String name , Account account) {
        super(name); //使用父类的有参构造器
        this.account = account;
    }
    //重写run方法,负责取钱
    @Override
    public void run() {
        account.drawMoney(1000);

    }
}

输出:

余额足够,取钱成功!

余额足够,取钱成功!

小明来取钱,成功!余额为0

小红来取钱,成功!余额为-1000

2.2线程同步

有下面几种方式上锁:

2.2.1同步代码块

java 复制代码
public class Account {
    private int money; //余额
    private String ID; // 卡号

    public void drawMoney(int NumToDraw) {
        //拿到是谁在取钱
        String name = Thread.currentThread().getName();
        //判断余额是否足够:这段代码要上锁,快捷键ctrl + alt + t
        synchronized ("ronnie") {
            if(this.money >= NumToDraw) {
                System.out.println("余额足够,取钱成功!");
                this.money -= NumToDraw;
                System.out.println(name + "来取钱,成功!" + "余额为" + this.money);
            }else{
                System.out.println("余额不足");
            }
        }
    }

}

问题:锁对象随便选择一个唯一对象 有没有问题?(java中,字符串是唯一 的,所以传入一个字符串给作为锁对象,对于线程来说就是唯一对象,可以成功给程序上锁

问题在于:锁的范围太大,只要上锁,有且只有一个对象可以运行,如果是两个不同账户,都想调用取钱方法,由于被上锁了,即便两个账户不是共享的资源,也会被上锁,而导致无法访问。

因此可以使用this作为锁。

小结:建议使用共享资源作为锁对象,对于实例方法,使用this作为锁;对于静态方法,使用字节码(类名.class)作为锁

2.2.2同步方法

java 复制代码
    //只需要加上synchronized关键字,即可保证线程安全
    public synchronized void drawMoney(int NumToDraw) {
        //拿到是谁在取钱
        String name = Thread.currentThread().getName();
        //判断余额是否足够
        if(this.money >= NumToDraw) {
            System.out.println("余额足够,取钱成功!");
            this.money -= NumToDraw;
            System.out.println(name + "来取钱,成功!" + "余额为" + this.money);
        }else{
            System.out.println("余额不足");

2.2.3lock锁

java 复制代码
public class Account {
    private int money; //余额
    private String ID; // 卡号
    private final Lock lock = new ReentrantLock(); // 不定义成静态是因为我们是给每个account上锁,
                                            // 如果是static,相当于所以账户共用同一个锁
    //加上final关键字,把锁保护起来,不允许他在被更改

    public void drawMoney(int NumToDraw) {
        //拿到是谁在取钱
        String name = Thread.currentThread().getName();
        //判断余额是否足够
        lock.lock();//在需要上锁的代码块前,调用lock方法
        if(this.money >= NumToDraw) {
            System.out.println("余额足够,取钱成功!");
            this.money -= NumToDraw;
            System.out.println(name + "来取钱,成功!" + "余额为" + this.money);
        }else{
            System.out.println("余额不足");
        }
        lock.unlock();//运行完毕后,解锁
    }

3.线程池

相关推荐
谈谈叭1 小时前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
lx学习1 小时前
Python学习26天
开发语言·python·学习
代码调试2 小时前
Springboot校园失物招领平台
java·spring boot
大今野2 小时前
python习题练习
开发语言·python
爱编程的鱼2 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
camellias_3 小时前
SpringBoot(二十三)SpringBoot集成JWT
java·spring boot·后端
tebukaopu1483 小时前
springboot如何获取控制层get和Post入参
java·spring boot·后端
昔我往昔3 小时前
SpringBoot 创建对象常见的几种方式
java·spring boot·后端
q567315233 小时前
用 PHP或Python加密字符串,用iOS解密
java·python·ios·缓存·php·命令模式
灭掉c与java3 小时前
第三章springboot数据访问
java·spring boot·后端