Java多线程

目录

前言:

Java中的多线程

JVM是一个进程

线程的生命周期

1、新建

2、就绪

3、运行

4、中断

5、死亡

多线程实现的API

一、继承 Thread

步骤

注意

 二、实现 Runnable接口

步骤

分情况使用

三、实现 Callable接口

步骤

注意 

多线程引发的问题

注意

线程安全性问题发生的主要原因

一、可见性问题

二、原子性问题

三、有序性问题

问题的处理

线程安全性问题的解决

关键字 volatile

 关键字 synchronized 

同步方法

同步块

小练习

线程同步--同步方法

 线程同步--同步块

问题

进程与线程的区别?

线程中断状态的原因?

为什么线程安全的类效率更低?


前言:

        早期的(命令行)操作系统不能一次性执行多个任务,引入“进程”的概念后,实现了多任务。

        我们可以把进程看作一个应用程序,运行一个应用程序时,操作系统会开启一个独立的进程,给这个进程划分独立的内存地址空间。除了我们开启的应用程序以外,操作系统自己的后台程序也都是一个个进程(包括病毒程序),在进程内部再划分多任务就是线程,它可以让一个程序同时做多件事

        多进程在执行的时候,并不是绝对的同时执行,只是因为CPU在多个进程之间来回的快速切换,速度很快,感觉不到停顿罢了。


Java中的多线程

JVM是一个进程

        启动一个Java应用程序,就是启动了一个JVM。应用程序是在JVM内部运行的。JVM是一个进程,我们书写的代码又是一个泡在JVM中的一个线程。

        在JVM当中除了我们书写的main线程以外,同时还跑了多个其他线程。比如:GC线程(它是JVM中的一个线程,作用是回收我们在程序中的产生的垃圾对象)

        Java的多线程开发,可以让我们在main线程中再次开启多个子线程,从而执行不同的动作。所以程序是由main方法开始而开始,但是main方法结束不一定程序结束,因为可能还有子线程没有结束。

        所有的线程一旦启动,都是平行关系,CPU会平等的在它们之间来回切换,我们可以调整比例但无法绝对控制。

        在多线程的情况下,程序执行的效果具备一定的随机性,同一段代码每次执行的效果可能不一样,因为CPU和硬件的自身算法所带来的,甚至不同的机器也会不一样。


线程的生命周期

1、新建

new Thread() 对象

        向JVM申请要建立一个新的线程,JVM就会做一些资源的申请和分配动作。

2、就绪

Thread.start()

        建好后,启动线程,也要先执行一段底层代码,让这个线程运行起来,然后才能执行我们在代码书写的执行任务。因此,从线程启动开始,到它真正执行我们书写的线程内部代码之前,这个阶段叫做 就绪。就绪状态是在start()开始,到他调用run()之前。

3、运行

        运行状态就是run()方法的执行周期,从run()开始到run()结束。可以把run()方法看成是这个子线程的main方法。

4、中断

        在run()的执行过程中,这个代码可能没有得到CPU的执行,但是run又没有结束,那么这个时候这个线程没有死,在等待,等待CPU再次继续执行它。

5、死亡

        run()方法结束后,Thread对象会自动调用它销毁线程,回收资源的底层代码实现。(这个过程我们无法控制)


多线程实现的API

一、继承 Thread

步骤

        1.继承 Thread

        2.重写run方法

        3.调用start方法

注意

  • 子线程被开启以后,主线程没有停,依然会和子线程抢夺CPU,继续往下执行
  • 一个线程可以产生多个线程对象,每个线程对象启动就是一个子线程,执行的是同样的代码
  • setName()给线程取名 
  • setPriority()  给线程设置优先级(共10级,默认都是5级),优先级高的,受到CPU的选择几率高。

 二、实现 Runnable接口

        Java的继承是单继承,在某些场景我们需要让线程拥有某个业务父类,就没办法同时继承Thread类了。且Runnable很容易实现资源共享

步骤

        1、让类实现接口

        2、产生该 实现类的对象 然后作为参数传递到真正的Thread身上

        3、调用Thread对象的start()方法

分情况使用

一、外部单独调用start方法

public class Main  {
    public static void main(String[] args) {
       Thread thread1=new Thread(new A());
       Thread thread2=new Thread(new B());
       thread1.start();
       thread2.start();
    }
}
public class A implements Runnable {
    @Override
    public void run() {
      线程需要做的事
    }
}
public class B implements Runnable{
    @Override
    public void run() {
        线程需要做的事
    }
}

二、直接在线程类的构造方法中调用start方法

  • 线程类对象一旦产生立马启动运行
public class Main  {
    public static void main(String[] args) {
       new A();
       new B();
    }
}

public class A implements Runnable {
    public A() {
        Thread thread=new Thread(this);
        thread.start();
    }
    @Override
    public void run() {
       线程需要做的事
    }
}

public class B implements Runnable{
    public B() {
        Thread thread=new Thread(this);
        thread.start();
    }
    @Override
    public void run() {
       线程需要做的事
    }
}

三、匿名内部类

  • 只用一次
public class Main  {  
  new Thread(new A()){
           @Override
           public void run() {
              线程需要做的事
           }
       }.start();
}

1、本质上,线程必须使用Thread这个类,必须有它的对象和调用它的start()方法;

2、Thread类在没有传入Runnable接口的实现类对象时,是执行自己的run方法;传入了Runnable接口的实现类对象,就执行这个实现类的run方法;

三、实现 Callable接口

它可以让主线程获取子线程状态

        前两种的run方法返回类型都是void,它们调用的start也是void。这样的主线程是得不到子线程的结果的,如果子线程报了异常,主线程也得不到。

步骤

        1、实现Callable接口,重写call方法。call方法中书写线程要做的事,它可以返回一个唯一结果,且这个结果是返回给主线程的;

        【Callable<> 泛型就是定义要返回的数据类型】

        2、把这个实现类的对象  传递给FutureTask类的构造方法中

        【FutureTask的泛型应该与Callable的返回类型保持一致】

        3、把FutureTask的对象传递给Thread类的构造方法。然后调用Thread对象的start方法

        4、调用FutureTask对象的get方法获取到子线程call方法执行结束以后的返回值


public class Main  {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       FutureTask<String> ft=new FutureTask(new A());
       Thread td=new Thread(ft);
       td.start();
       String result=ft.get();
       System.out.println(result);
    }
}

public class A implements Callable<String> {
    @Override
    public String call() throws Exception {
        书写线程需要做的事
        String str="需要返回的结果";
        return str;
    }
}

注意 

        1.当主线程已经调用FutureTask对象的get方法获取返回结果了,而子线程还没有执行完,那么这个时候主线程会进入阻塞状态,等待子线程全部执行结束,拿到返回值之后再往下执行

        2.如果没有使用Callable,而是使用另外两个,也想得到主线程在子线程执行结束后再执行,可以调用子线程对象的join方法,达到此效果。

多线程引发的问题

        多线程,让应用程序的丰富度也增加了,但是它会有线程安全性问题

注意

        1、单线程一定没有线程安全性问题

        2、多线程不一定有线程安全性问题(线程安全性问题是一种特殊场景)当多个线程操作统一资源时,才有可能发生。


线程安全性问题发生的主要原因

一、可见性问题

        程序是在内存中执行的,声明的数据是在内存中,存放的数据在硬盘上,执行的数据是在CPU当中,越靠近CPU的硬件,执行速度越快

在Java的内存模型上,其实CPU也有存放数据的缓存

二、原子性问题

        必须让一个线程中的多段代码被单独执行,不能被其他线程的代码插入,让这段代码成为独立的不可分隔的原子步骤

三、有序性问题

        我们书写代码的顺序,不一定是计算机执行指令的顺序,硬件方的指令可能对此进行优化


问题的处理

        1、让一个线程对这个资源的改变,能够及时通知另外一个资源的改变,让它不要一直去读它的缓存数据

        2、让整段代码或一句代码被拆分成的多个指令不被打断


线程安全性问题的解决

关键字 volatile

  • 不稳定的

解决 可见性问题,它可以修饰变量(在多线程中要操作的那个同一资源变量

public class ThreadA extends Thread{
    private volatile boolean isRun = true;//修饰变量,要操作的 同一资源变量
    public void setRun(boolean run) {
        isRun = run;
    }
    @Override
    public void run() {
        System.out.println("线程1开始执行.........");
        while(isRun){//isRun为false时,跳出循环,才执行下面输出语句
        }
        System.out.println("线程1结束执行");
    }
}

public class ThreadB extends Thread{
    private ThreadA threadA;
    public ThreadB(ThreadA threadA){
        this.threadA = threadA;
    }
    @Override
    public void run() {
        System.out.println("线程2启动执行........");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread1.setRun(false);//改变资源变量
        System.out.println("线程2结束执行......");
    }
}

public class Test {
    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2(t1);

        t1.start();
        t2.start();
    }
}

 关键字 synchronized 

  • 同步

        一段代码在执行的过程中,只能由一个线程完成。其他线程,进入等待状态。当这个线程把这段代码执行完成了,其他线程才可以进入。这段代码只能是一个线程一个线程的来,不存在被插入的可能性。保证了它的原子性和有序性

同步方法

        把synchronized写在资源的某个方法的声明处(相当于修饰符的位置)。那么这个方法每次只能被一个线程调用,其他线程等待。执行完一个线程后,其他线程才能调用。

同步块

        把synchronized以代码块的形式,写在线程的调用处

synchronized(资源对象){
    资源对象.方法1();
    资源对象.方法2();
}

小练习

线程同步--同步方法

public class BigPang2 implements Runnable{
    private String name;
    private long loadTime;
    private Dumplings2 dps;
    private int num;

    public BigPang2() {

    }

    public BigPang2(String name, long loadTime, Dumplings2 dps) {
        Thread thread=new Thread(this);
        thread.start();
        this.name=name;
        this.loadTime=loadTime;
        this.dps=dps;

    }
    @Override
    public void run() {
        while (this.dps.eat()){
            num++;
            System.out.println(name+"吃了"+num+"个");
            try {
                Thread.sleep(loadTime);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

//资源类
public class Dumplings2 {
    private int dumplingNum=25;

    public synchronized boolean eat(){
    //保证两句代码不可分隔
        if(this.dumplingNum>0){
            this.dumplingNum--;
            return true;
        }else {
            return false;
        }
    }
}

public class Main2 {
    public static void main(String[] args) {
        Dumplings2 dumplings2 = new Dumplings2();
        new BigPang2("大胖",200,dumplings2);
        new SmallShou2("小瘦",300,dumplings2);
    }
}

 线程同步--同步块

public class SmallShou implements Runnable{
    private String name;
    private long loadTime;
    private Dumplings dps;

    public SmallShou() {

    }

    public SmallShou(String name, long loadTime, Dumplings dps) {
        Thread thread=new Thread(this);
        thread.start();
        this.name=name;
        this.loadTime=loadTime;
        this.dps=dps;
    }
    @Override
    public void run() {
        int num=0;
        while (true){
//保证 判断饺子数量的if语句 和 数量-1的代码 不会被插入,不可分隔
            synchronized (this.dps) {
                if(this.dps.getDumplingNum()>0) {
                    this.dps.setDumplingNum(this.dps.getDumplingNum() - 1);
                }else {
                    break;
                }
            }
            num++;
            System.out.println(this.name + "吃了第" + num + "个饺子 "
 + "还剩" + this.dps.getDumplingNum() + "个");
            try {
                Thread.sleep(loadTime);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("小瘦吃了"+num+"个");

    }
}


资源类
public class Dumplings {
    private int dumplingNum=25;

    public int getDumplingNum() {
        return dumplingNum;
    }

    public void setDumplingNum(int dumplingNum) {
        this.dumplingNum = dumplingNum;
    }
}

public class Main {
    public static void main(String[] args) {
        Dumplings dumplings = new Dumplings();
        new BigPang("大胖",200,dumplings);
        new SmallShou("小瘦",300,dumplings);
    }
}

问题

进程与线程的区别?

1、线程是在进程内部再次进行多任务的划分;

2、进程是独立内存地址空间的; 同一个进程内部的线程是共享内存地址空间的;

3、CPU在进程之间进行切换的成本高于在线程之间进行切换。


线程中断状态的原因?

1.CPU的切换

2.由于输入或输出导致的阻塞状态

3.让一个线程主动进入休眠状态,给它设置一个休眠的时长 Thread里的sleep()方法;

4.多个线程访问同一资源时,可以让A线程主动退出,进入等待;B线程进入执行资源,B线程执行结束以后,再唤醒A线程。

Object里的 wait()方法【等待】 和 notify()方法【唤醒第一个排队等待的】notifyAll()【唤醒所有】

它们只能写在同步方法或同步块中


为什么线程安全的类效率更低?

        它把并行的多个线程,变成了排队,依次执行的串行状态。多个线程在等待,每次只会唤醒其中一个。按等待的先后顺序唤醒的叫“公平锁”,synchronized是“非公平的锁”。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BroRiver

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值