java多线程-初探(一)

啥是多线程?跟进程又是啥关系?

 

比方说:我去洗手,洗完手去吃饭。

进程(Processor)

洗手跟吃饭是两个进程。

线程(Thread)

在洗手的进程里,我同时听歌,还唱歌。那这里洗手是一个进程,听歌跟唱歌是两个线程。

在吃饭的进程里,我同时听歌,还唱歌。那这里吃饭是一个进程,听歌跟唱歌是两个线程。

吃饭跟洗手两个进程之间的线程互不干扰,同个进程之间的线程存在锁、等待、唤醒等等等机制。

 

多线程:同一个进程内做多件事,每件事是一个线程

创建线程有两种方式:继承Thread,实现Runnable接口

合理的使用多线程,可以提高程序的效率,如果无限制的开启多线程,会导致程序的效率降低。

多线程运行原理

任何程序运行都需要CPU执行。但是CPU在某个时间点上,它只能处理某一条指令。

但它可以在很短的时间内在多个线程(指令)之间高速的切换。导致我们误认为多个程序在同时运行。

 

这里创建一个普通java项目。

 

单线程示例

以吃饭为例,只有听完歌才能唱歌,无法边听边唱。

 

class Main

public class Main {

    /**
     * 吃饭
     * @param args
     */
    public static void main(String[] args) {
        Song song = new Song();
        while (!song.listenIsZero())
            song.doThing("listen") ;
        while (!song.singIsZero())
            song.doThing("sing") ;
        System.out.println("执行完成!");
    }
}

class Song

public class Song {

    // 待唱的歌曲数量
    private int singNum = 5 ;

    // 待听的歌曲数量
    private int listenNum = 5 ;

    public boolean singIsZero(){
        return singNum == 0 ;
    }

    public boolean listenIsZero(){
        return listenNum == 0 ;
    }

    public void doThing(String thing){
        switch (thing){
            case "sing" : doSing(); break;
            case "listen" : doListen(); break;
        }
    }

    public void doSing(){
        try {
            // 唱歌需要时间,加时间能体现多线程的效果
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("唱了一首歌,剩余:" + singNum-- +"首未唱");
        if (singIsZero()) System.out.println("唱完了!");
    }

    public void doListen(){
        try {
            // 听歌需要时间,加时间能体现多线程的效果
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("听了一首歌,剩余:" + listenNum-- +"首未听");
        if (listenIsZero()) System.out.println("听完了!");
    }

}

执行结果

 

多线程示例(继承Thread类)

可以在吃饭的进程里,边听歌边唱歌。同时进行。

创建一个类负责听歌或者唱歌,继承Thread,重写run函数。

建立两个线程,都start之后可以观察到两个线程同时调用doThing函数。

 

class Main

public class Main {

    /**
     * 吃饭
     * @param args
     */
    public static void main(String[] args) {
        // 单线程示例
        /*Song song = new Song();
        while (!song.listenIsZero())
            song.doThing("listen") ;
        while (!song.singIsZero())
            song.doThing("sing") ;*/

        // 多线程示例
        SongThread listen = new SongThread("listen", new Song());
        SongThread sing = new SongThread("sing", new Song());
        // 启动两个线程
        listen.start();
        sing.start();
        System.out.println("执行完成!");
    }
}

class SongThread

public class SongThread extends Thread{

    private Song song ;

    private String thing ;

    public SongThread(String thing, Song song){
        this.song = song;
        this.thing = thing;
    }

    public void run(){
        switch (thing){
            case "listen" : while (!song.listenIsZero()) song.doListen(); break;
            case "sing" : while (!song.singIsZero()) song.doSing(); break;
        }
    }

}

执行结果

 

为什么要继承Thread类?

线程本身是操作系统中存在的一个需要CPU去执行的任务(指令)。

而我们希望我们的代码可以被多个线程同时运行。

我们的代码就需要和线程产生关系,而在Java中负责和线程交互的类是Thread。

为什么要重写Run方法?

继承Thread是让我们的类和线程有关系,但是我们类中书写的代码需要最终被线程执行,这时书写的代码必须书写在线程可以运行的指令。

当有了线程对象之后,调用start方法,JVM会自动的调用当前线程中的run方法。这时我们需要把让线程执行的自己的代码书写在run方法中,线程开启之后,就会自动的把我们的代码也运行了。

开启线程的目的:让线程来运行我们自己指定的代码,而这些被线程运行的代码称为线程开启之后需要执行的任务。

为什么要调用start方法?

当我们创建了线程对象(Thread或Thread的子类),只有调用了start方法,才能在栈内存中划分出一个新的栈区来运行当前这个线程的任务。如果不调用start方法,仅仅是代表有了线程,但是线程并不会独立的去运行。

调用了start方法,在start方法中JVM会自动的调用run方法,run方法会被加载到当前新的栈区中运行,这样会导致栈内存中有主线程,还有我们调用start之后开启的新线程,这样cpu就会在多个线程之间进行切换。

 

多线程示例(实现Runnable接口)

创建一个类实现Runnable接口,然后new Thread的时候将实现类传参之后start。

Thread与Runnable的关系:Thread类实现了Runnable接口。

 

class Main

public class Main {

    /**
     * 吃饭
     * @param args
     */
    public static void main(String[] args) {
        // 单线程示例
        /*Song song = new Song();
        while (!song.listenIsZero())
            song.doThing("listen") ;
        while (!song.singIsZero())
            song.doThing("sing") ;*/

        /*// 多线程示例 Thread
        SongThread listen = new SongThread("listen", new Song());
        SongThread sing = new SongThread("sing", new Song());
        // 启动两个线程
        listen.start();
        sing.start();
        System.out.println("执行完成!");*/

        // 多线程示例 实现Runnable接口
        SongRunnable listen = new SongRunnable("listen", new Song());
        SongRunnable sing = new SongRunnable("sing", new Song());
        // 启动两个线程
        new Thread(listen).start();
        new Thread(sing).start();
    }
}

 

class SongRunnable

public class SongRunnable implements Runnable{

    private Song song ;

    private String thing ;

    public SongRunnable(String thing, Song song){
        this.song = song;
        this.thing = thing;
    }

    public void run(){
        switch (thing){
            case "listen" : while (!song.listenIsZero()) song.doListen(); break;
            case "sing" : while (!song.singIsZero()) song.doSing(); break;
        }
    }

}

 

执行结果

 

Thread跟Runnable到底更推荐用哪个?

实现上的区别:Thread需要类继承。Runnable需要类实现该接口。

所以话题就转到了:继承跟实现都可以实现的功能,选继承还是实现?

继承的局限性:

在Java中每个类的直接父类只能有一个,不能多继承。

而JDK中提供的Thread方式,让某个类需要去继承Thread,才能创建线程并开启线程。

当一个类继承了Thread,就无法再去继承其他的类。

首先一个类它应该有自己所属的体系。如果一个子类中需要有多线程执行的代码,采用继承的方式会导致这个子类脱离原有的继承体系。然后去继承Thread,这种方式肯定不可取。

接口的好处:

一个类可以去继承某个父类的同时,还能实现多个接口。

实现了Runnable接口的实现类本身与线程(Thread)并无关系,无法直接开启线程。

当需要启动线程时,将Runnable实现类交给Thread对象,这样就有线程关系了,start启动。

 

java多线程-初探(二)

 

实际业务应用场景举例

1.后台需要定时给数据库100w个用户发送提醒邮件。

 启动两个线程分别给50w个用户发邮件。

2.不影响用户情况下记录用户操作日志(异步)

 启动一个线程去单独执行记录日志的操作。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,您的问题是关于Java多线程的存钱取钱问题。这是一个经典的多线程同步问题,可以通过使用synchronized关键字来实现线程安全。下面是一个简单的示例代码: ```java public class BankAccount { private int balance; public BankAccount(int balance) { this.balance = balance; } public synchronized void deposit(int amount) { balance += amount; System.out.println("Deposit successful. Balance: " + balance); } public synchronized void withdraw(int amount) { if (balance < amount) { System.out.println("Withdraw failed. Insufficient balance."); return; } balance -= amount; System.out.println("Withdraw successful. Balance: " + balance); } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount(1000); // Create two threads to simulate deposit and withdraw Thread depositThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.deposit(100); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread withdrawThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.withdraw(200); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); depositThread.start(); withdrawThread.start(); } } ``` 在这个示例代码中,我们创建了一个银行账户类BankAccount,并在其中实现了deposit和withdraw方法,并使用synchronized关键字来保证线程安全。 在main方法中,我们创建了两个线程来模拟存款和取款操作,每个线程执行5次操作。我们使用Thread.sleep方法来模拟每个操作之间的间隔,以便更好地观察多线程操作的结果。 当多个线程同时访问BankAccount对象的deposit和withdraw方法时,synchronized关键字可以确保每个方法只能被一个线程访问,从而避免了竞争条件和数据不一致的问题。 希望这个示例代码能够回答您的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

954L

帮帮孩子把~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值