Java多线程

  • 一、多线程介绍
  • 概念
  • 并发
  • 什么是主线程及子线程
  • 二、线程的创建
  • 通过继承Thread类实现多线程
  • 通过Runnable接口实现多线程
  • 线程的执行流程
  • 线程的生命周期
  • 线程的五个状态
  • 三、线程的使用
  • 终止线程
  • 暂停线程执行
  • 线程联合
  • Thread类中其他常用方法
  • 四、线程的优先级
  • 五、守护线程
  • 六、线程同步
  • 实现线程同步
  • 七、死锁及解决方案
  • 死锁解决


一、多线程介绍

概念

在知道多线程之前,我们需要知道什么是线程,当我们运行一个程序时,我们运行程序的可执行文件后,程序就被加载到了cpu中,这时候计算机就产生了一个进程。那我们知道进程后,就好理解线程了,线程也叫轻量级进程,它是由进程产生的,一个进程可以创建多个线程,所以我们叫它多线程。那多线程先对与进程有什么区别呢?主要区别就是以下几点,1.线程比进程消耗的资源少。2.线程的通信是在同一地址空间上进行,所以通信快。3.资源是共享的。

大概就是这个样子

并发

并发就是在同一时间同时做多件事,如果只有一个cpu那么计算机就会采取时间片轮转法来运行。

什么是主线程及子线程

主线程就是最先被启动的程序,即main对应的线程,他是程序开始就被执行的。而在主线程创建并启动的线程,一般就被称为子线程。

二、线程的创建

通过继承Thread类实现多线程

具体的步骤是:

1.继承Thread类定义线程类

2.重写类中的run()方法,这个方法也叫线程体

3.实例化线程并通过start()方法启动线程

具体流程就是这样,但是,纸上得来终觉浅,绝知此事要躬行,所以我们来使用idea浅浅测试一下吧!

简单的测试线程


运行结果


由此我们可以看出主线程不一定是最后执行完的,当然子线程也不一定就是先执行完的,这些我们后面会提到

通过Runnable接口实现多线程

这个也是创建多线程的一种方式,话不多说,直接上图

通过Runnble接口实现


这是运行结果

看来运行之后,有小伙伴想知道具体的线程执行流程和生命周期吗?小编当然也准备了

线程的执行流程

线程的执行流程

线程的生命周期

线程的生命周期


我们人这一生也就是幼年、青年、壮年、老年这几个状态,线程的一生也就是这五个状态,不同的是,我们人只能是幼年,青年,壮年,老年这样。线程就不一样了,他可以在几个状态之间反复横跳。

线程的五个状态

1.新生状态
用new关键字建立线程对象后,就是新生状态,他有自己的内存空间,当调用start方法后就进入就绪状态。
2.就绪状态
就绪,就是已经准备好运行了,他处于线程就绪队列,当系统给他cpu之后,线程进入运行状态,并调用自己的run方法。就绪状态并不只有新建线程时会有,还有3种情况会进入就绪状态
a.阻塞线程:阻塞接触,进入就绪状态
b.运行线程:调用yield方法,进入就绪状态
c.运行线程:jvm将cpu资源从本线程切换带其他线程。
这就是我刚刚说的会反复横跳的原因,当然并不只这一个地方会反复横跳,等说到的时候我们再具体说
3.运行状态
线程执行run方法中的代码,知道调用其他方法而终止或等待某资源而阻塞或完成任务死亡。如果在时间片内没有执行完,就会被系统换下来到就绪队列中,成为就绪状态。也有可能由于某些导致阻塞的事件而进入阻塞状态
4.阻塞状态
阻塞指的是暂停一个线程的执行以等待某个条件发生(或资源就绪)。有4中原因会导致阻塞
a.执行sleep方法
b.执行wait方法
c.线程运行时,某个操作进入阻塞状态,比如IO流操作(read()/write()方法本身就是阻塞的方法),当引起阻塞的原因消失后,进入就绪状态,并不是直接继续运行。
d.join()线程联合:当某个线程执行结束后,才能继续执行,使用join()方法。
5.死亡状态
死亡是最后一个阶段,不能反复横跳了,导致死亡有两个原因
a.正常运行完run()中的代码
b.执行stop()或destroy()方法,不推荐,已经被jdk废弃
所以只有新生和死亡状态不能反复横跳,其他状态都是可以的。

三、线程的使用

终止线程

之前就说jdk提供的stop()和destroy()方法被废弃,所以通常我们自己定义一个变量,当这个变量变为false时,终止线程运行。

![stop方法](https://img-

blog.csdnimg.cn/bddb395b9d14434dbed66f6c87e6432d.png)

运行结果

暂停线程执行

有两个方法可以暂停线程执行sleep()和yield()方法

区别是:sleep()方法可以让正在运行的线程进入阻塞,当时间到了进入就绪状态,而yield()方法则是直接让出cpu使用权,进入就绪状态

sleep


运行结果


实际上运行yield方法无法保证一定达到让步,因为,让步的线程可能被线程调度程序再次选中

yield

运行结果

线程联合

当前线程邀请调用方法的线程优先执行,在调用方法的线程执行结束之前,当前线程不能再次执行。线程A在运行期间,可以调用B的join方法,让A和B联合。这样A就必须等B执行完毕才能继续执行。

举个栗子:儿子买烟

儿子



运行结果

Thread类中其他常用方法

Thread.currentThread.getName():获取线程名字,也可以通过setName设置线程名
isAlive():判断是否处于活动状态,线程处于运行或者就绪状态就认为是存活。

四、线程的优先级

每一个线程都是有优先级的,我们可以为每一个线程定义优先级,但是并不能保证高优先级会在低优先级线程前执行。线程优先级用数字1-10表示,一个线程的缺省优先级是5。注意:不是优先级高就先执行,是执行的概率高。
获得线程优先级的方法是getPriority()
设置线程优先级的方法是setPriority()

五、守护线程

在Java中有两类线程,一类是用户线程,另一类是守护线程(垃圾回收线程就是典型的守护线程)。守护线程会随着用户线程的死亡而死亡。



六、线程同步

当多个线程访问同一个对象,并且还想修改这个对象,这时候我们就需要线程同步,线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的使用完,下一个线程再使用。
举个栗子:当两个人去同一个账户取钱,两个人同时取钱,第一个人取完,账户信息还没有更新,第二个人又取了,这就导致账户只有一百块钱,但是两个人一共取出了两百块钱,使用线程同步就是让第一个人取钱时不许其他人用这个账户,等第一个人用完,第二个人才能使用。

实现线程同步

语法:
synchronized(锁对象){同步代码}
我们就用上面提到的例子来写这个代码

class Account {
    private String account;
    private double balance;

    public Account(String account, double balance) {
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account() {
    }
}
//取款线程
class DrawThread extends Thread{
    private Account account;
    private double drawMoney;
    public DrawThread(String name,Account account,double drawMoney){
        super(name);
        this.account=account;
        this.drawMoney=drawMoney;
    }

    @Override
    public void run() {

            if (this.drawMoney<=this.account.getBalance()){
                System.out.println(this.getName()+"取钱成功"+this.drawMoney);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.account.setBalance(this.account.getBalance()-this.drawMoney);
                System.out.println("余额为"+this.account.getBalance());
            }else {
                System.out.println("余额不足");
            }
        


    }
}
public class DrawMoneyThread {
    public static void main(String[] args) {
        Account account = new Account("1234", 1000);
        new DrawThread("老公",account,800).start();
        new DrawThread("老婆",account,800).start();
    }
}


当我们不加synchronized时就会出现这个情况当我们加上之后

@Override
    public void run() {
        synchronized (this.account){
            if (this.drawMoney<=this.account.getBalance()){
                System.out.println(this.getName()+"取钱成功"+this.drawMoney);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.account.setBalance(this.account.getBalance()-this.drawMoney);
                System.out.println("余额为"+this.account.getBalance());
            }else {
                System.out.println("余额不足");
            }
        }

七、死锁及解决方案

死锁指的是:多个线程各自占有一些资源,并且相互等待其他线程占有的资源才能进行,导致都在等对方释放资源而都停止执行。
举个栗子
一条河上只有一座独木桥,两个人分别在对岸都想过去,但是都不愿意让对方先过去,于是都过不去,两个人相互等待,把这两个人看作线程,就是死锁。

死锁解决

同一个代码块不要用两个对象锁