java多线程基础

网页左边,向下滑有目录索引,可以根据标题跳转到你想看的内容

一、线程与进程,线程的实现方式

什么是线程?

聊线程我们需要先知道什么是进程(进程包含线程,一个进程可以有多个线程,进程结束线程一定结束,线程结束,进程不一定结束)
进程:是当前正在执行的一个程序,比如我们同时运行了qq和微信,这两个程序就是两个独立的进程

  • 进程是静态的执行过程
  • 占用特定的内存空间
  • 不同进程间相互独立,由cpu、内存地址code 和 内存空间data组成
  • 一个单核cpu同一时刻只能处理一个进程,如果进程过多,cpu负担会很重,所以现在有很多4核,8核cpu来同时处理多个进程

cpu如何处理进程

  • 同一时刻单核cpu只能处理一个进程,简单来说,一个进程有3个状态(只是简单来说),就绪-运行-等待(阻塞)
  • 进程准备好了除cpu外所有资源,进入就绪状态
  • 这时当cpu空闲就会处理这个进程,此时进程为运行状态
  • 当此进程突然需要某些资源,比如io设备(这个设备很慢,和cpu速度完全不是一个量级),那么此时cpu不会等待这个进程获取它要的资源,转而处理其它就绪进程
  • 此时此进程进入等待状态,当它获取到了它需要的资源,这时又只差cpu了,就重新进入就绪状态

线程:线程是单一的可连续处理的可控流程,可以称为是轻量级进程,一个进程可以有多个线程

  • 一个进程可以有多个并行的线程
  • 相同进程的所有线程共享相同的内存单元、内存地址空间、可以共享资源(变量和对象)
  • 相同进程的所有线程从同一个堆(不懂请百度’堆栈’的概念)中分配对象和通信,还可以数据交换,同步操作
  • 不需要额外通信机制,通信简便,数据传输快。(因为某一进程中的所有线程通信是在同一地址空间进行的)
  • 这也是为什么现在出现4核8线程cpu,8核16线程cpu的原因

线程执行状态或者称为生命周期(注意是java的线程,不是进程,以下两张图片取自马士兵教育的课件)
在这里插入图片描述
在这里插入图片描述

1、实现线程的方式1

在这里插入图片描述

package com.company;

/**
 * 实现线程的第一种方法,通过类实现
 * 1、继承Thread类
 * 2、实现run()方法
 * 3、创建类对象,调用线程方法,比如start()启动线程
 */
public class ThreadTest extends Thread{ //1、继承Thread类

    //2、实现run方法
    @Override
    public void run() {
        for (int i = 0;i <= 10;i++){
            Thread.currentThread().setName("ThreadTest类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
            System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
        }
    }
}

在这里插入图片描述

package com.company;

/**
 * 实现线程的第一种方法,通过类实现
 * 1、继承Thread类
 * 2、实现run()方法
 * 3、创建类对象,调用线程方法,比如start()启动线程
 */
public class Main extends Thread{ //1、继承Thread类

    //2、实现run方法
    @Override
    public void run() {
        for (int i = 0;i <= 10;i++){
            Thread.currentThread().setName("Main类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
            System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
        }
    }

    public static void main(String[] args) {

        //3、创建类对象,可省略,因为这里main方法就在这个类,其它情况不能省略
        ThreadTest threadTest = new ThreadTest();//创建第一个线程类对象
	    Main main = new Main();//创建第二个

        //4、调用Thread类继承来的方法,启动线程
        threadTest.start();//启动第一个线程
        main.start();//启动第二个线程
    }

}

2、实现线程的方式2

在这里插入图片描述

package com.company;

/**
 * 实现线程的第二种方法,通过继承Runnable接口(因为Thread类就是实现Runnable接口实现的)
 *      此方法适用于需要使用线程,还需要继承其它类的时候(因为一个类同时只能继承一个类,但可以同时实现多个接口)
 * 1、实现Runnable接口(与方法1的不同点)
 * 2、实现run()方法
 * 3、创建类对象,然后创建Thread类对象,将刚实现了Runnable接口的类放在构造方法中,实现线程(与方法1的不同点),最后通过Thread类对象调用start()方法启动线程
 */
public class ThreadTest implements Runnable{ //1、实现Runnable接口

    //2、实现run方法
    @Override
    public void run() {
        for (int i = 0;i <= 10;i++){
            Thread.currentThread().setName("ThreadTest类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
            System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
        }
    }
}

在这里插入图片描述

package com.company;

/**
 * 实现线程的第二种方法,通过继承Runnable接口(因为Thread类就是实现Runnable接口实现的)
 *      此方法适用于需要使用线程,还需要继承其它类的时候(因为一个类同时只能继承一个类,但可以同时实现多个接口)
 * 1、实现Runnable接口(与方法1的不同点)
 * 2、实现run()方法
 * 3、创建类对象,然后创建Thread类对象,将刚实现了Runnable接口的类放在构造方法中,实现线程(与方法1的不同点),最后通过Thread类对象调用start()方法启动线程
 */
public class Main extends Thread{ //1、继承Thread类

    //2、实现run方法
    @Override
    public void run() {
        for (int i = 0;i <= 10;i++){
            Thread.currentThread().setName("Main类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
            System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
        }
    }

    public static void main(String[] args) {

        //3、创建类对象,可省略,因为这里main方法就在这个类,其它情况不能省略
	    Main main = new Main();//创建第二个

        //4、调用Thread类继承来的方法,启动线程
        main.start();//启动线程

        /**方法2不同的地方*/
        //创建刚刚实现了Runnable接口的类
        ThreadTest threadTest = new ThreadTest();

        //创建Thread类对象,将刚实现了Runnable接口的类放在构造方法中,实现线程
        Thread thread = new Thread(threadTest);

        //通过Thread类对象调用start()方法
        thread.start();
    }
}

3、经典卖票问题

首先,使用继承Thread类的方式实现线程会出现共享资源不同步问题,当然可以添加static关键字解决
在这里插入图片描述
继承Runnable接口实现不会出现资源不同步的问题
在这里插入图片描述

可以看到,上面的结果依旧不是我们想要的,这里就涉及到正真的线程同步解决方案了,学习之前我们需要先学习代理设计模式

4、代理设计模式

因为篇幅原因,我将这部分内容放在别的博客中了。请参考https://blog.csdn.net/grd_java/article/details/109690730

5、线程操作相关方法

在这里插入图片描述在这里插入图片描述

从Object类继承来的方法
wait():暂停当前线程,进入阻塞。这只是片面的解释。因为wait方法是所有类共享的方法,所以一般我们暂停的是当前共享资源(共享对象),导致线程无法强制资源,被迫进入阻塞
notify():唤醒暂停的线程,和wait一样,唤醒的其实是某个共享对象
package com.company;

/**
 * 调用动态代理
 */
public class Main{

    public static void main(String[] args) {//首先主函数就是一个线程,我们可以直接在main方法中测试方法

        Thread thread = Thread.currentThread();//获取当前线程对象

        String threadName = thread.getName();//获取当前线程名称

        System.out.println(threadName);//打印结果为main,因为main函数就是一个线程

        System.out.println(thread.getId());//获取线程id

        /*
               线程优先级,值越高,获取优先的概率越高,但是不一定就是它优先,仅仅概率更高一点罢了
               对比优先级低的线程,高优先线程有高一点的几率抢到cpu资源,但根据情况不同,也会抢不过低优先线程
         */
        thread.setPriority(10);//设置线程的优先级为10,一般大多数的系统中优先级是0-10,默认都会取中间值5,而有些系统是0-100

        System.out.println(thread.getPriority());//获取线程优先级

        System.out.println(thread.isAlive()?"线程存活":"线程死亡");//thread.isAlive()方法用来返回线程的状态,还在活动就返回True,否则false

        try {
            thread.sleep(1000);//休眠线程1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.isAlive()?"线程存活":"线程死亡");

        thread.yield();//暂停线程,让其他线程先执行,此时此线程重新回到就绪状态,开始抢cpu
        System.out.println(thread.isAlive()?"线程存活":"线程死亡");

        try {
            thread.join();//強行阻塞线程,一般此方法用在其他线程中,当调用这个方法的线程执行完了,才重新就绪这个被阻塞的线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.isAlive()?"线程存活":"线程死亡");

        thread.stop();//强行停止线程,已淘汰,不推荐使用,推荐使用yield
    }

}

在这里插入图片描述

二、线程同步,线程死锁

死锁:就是我让张三先交货,张三让我先给钱,我不肯先给钱,张三也不肯先给货,我俩各拿一份资源,谁的不肯松手,这就叫死锁。就是线程a拿一份资源,线程b拿一份资源,a想要b现在的资源,b也想要a现在的资源,两个都不肯让,就叫死锁。

1、线程安全,线程同步

上面我们卖票,出现了几个线程同时访问资源的问题,这时就需要用到线程同步处理方案

在这里插入图片描述
在这里插入图片描述

同步的条件
至少有两个线程
必须多个线程使用同一个资源
必须保证同步中只能有一个线程运行(需要同步的代码中,只能同时有一个线程,不能两个线程同时工作)

1、同步代码块

synchronized (共享对象,必须是object的子类){代码}

在这里插入图片描述

package com.company;

import org.apache.commons.io.filefilter.TrueFileFilter;

public class ThreadTest implements Runnable{

    private int ticket = 10;
    @Override
    public void run() {
        boolean b = true;
        synchronized (this){//同步代码块
            while(b){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"正在卖第--"+(ticket--)+"--张票");
                }else{
                    b = false;
                }
            }
        }
    }
}

2、同步方法(同步方法可以有效解决同步安全问题,并且不需要知道共享对象,通常将当前对象作为同步对象)

下面这张截图中忘了删掉同步代码块了,但是运行结果依然正常,详细代码请看我贴出来的代码
在这里插入图片描述

package com.company;

import org.apache.commons.io.filefilter.TrueFileFilter;

public class ThreadTest implements Runnable{

    private int ticket = 10;
    @Override
    public void run() {
        boolean b = true;
        while(b){
            sale(b);//调用同步方法
        }
    }

    public synchronized void sale(boolean b){//将需要同步的代码放在使用synchronized修饰的方法中
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第--"+(ticket--)+"--张票");
        }else{
            b = false;
        }
    }
}

3、解决线程独占资源的状况

仔细观察上面讲的同步的代码结果,你就会发现,所有的票都是同一个线程卖的,怎么解决呢?

在这里插入图片描述

在这里插入图片描述

2、线程的生产者与消费者问题

生产者不断生产,消费者不断取走生产者生产的产品(生产者不断生产东西到某个区域,消费者不断从此区域取走产品)

这里要重新提到从Object类继承来的方法,拿过来让大家注意一下
wait():暂停当前线程,进入阻塞。这只是片面的解释。因为wait方法是所有类共享的方法,所以一般我们暂停的是当前共享资源(共享对象),导致线程无法强制资源,被迫进入阻塞
notify():唤醒暂停的线程,和wait一样,唤醒的其实是某个共享对象

在这里插入图片描述

package com.company;

/**
 * 商品实体类
 */
public class Goods {
    private String brand;//品牌
    private String name;//商品名

    private boolean flag = false;//判断仓库中是否有商品,有就是true,没有就是false

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 线程安全同步,生产者生产的方法
     * @param brand
     * @param name
     */
    public synchronized void set(String brand,String name){
        //如果flag为true,说明有商品,让生产者线程等待商品被取走,并通知消费者来取
        if(flag){
            System.out.println("生产者查看了仓库,发现商品还未被取走,==========通知消费者来取");
            try {
                wait();//等待当前共享资源goods,相当于阻塞当前线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            Thread.sleep(1000);//睡眠1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.setBrand(brand);//设置品牌
        this.setName(name);//设置名称

        System.out.println("生产者生产了"+this.getBrand()+"----"+this.getName());

        /**
         * 生产者生产完商品,让flag设置为true表示仓库有商品了,并且使用notify重新唤醒生产者线程
         */
        flag = true;

        notify();//唤醒当前共享资源,让当前线程可以争取当前共享资源,就是唤醒线程的效果
    }

    public synchronized void get(){
        //如果flag为false,就是没有商品,让消费者赶紧生产,并且暂停当前线程
        if(!flag){
            System.out.println("消费者来取东西发现没有商品==========通知生产者生产");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.getBrand();
        this.getName();
        System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());

        //取走商品后,flag设置为false,表示仓库没有资源了,然后唤醒线程
        flag = false;
        notify();
    }
}

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

三、线程池

为什么需要线程池

  • 在实际使用中,线程非常占用系统资源,管理不当会导致一系列系统问题,许多并发框架中都会使用线程池来管理线程
  • 线程池,就是预先创建好一堆线程放在池里,有人需要就拿走,用完还回来,给其它人继续使用
  • 使用线程池可以重复利用已有线程继续执行任务,避免线程重复创建销毁,造成系统资源浪费,提高系统响应速度
  • 线程池可以合理管理线程,会根据系统承受能力调整可运行线程的数量和大小等

以下概念性图片均来自马士兵教育的课件ppt
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

1、CachedThreadPool,高速缓存线程池

在这里插入图片描述
在这里插入图片描述

package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main{

    public static void main(String[] args) {

        /**
         * Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
         */
        ExecutorService executorService = Executors.newCachedThreadPool();//创建高速缓存线程池

        for (int i = 0;i < 10;i++){
            executorService.execute(new ThreadTest());//添加线程
        }

        executorService.shutdown();//关闭线程池

    }
}

2、FixedThreadPool,固定数量线程池

在这里插入图片描述

package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main{

    public static void main(String[] args) {

        /**
         * Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
         */
        ExecutorService executorService = Executors.newFixedThreadPool(5);//创建固定线程池,就是线程有多少个是谁都固定的

        for (int i = 0;i < 10;i++){
            executorService.execute(new ThreadTest());//添加线程
        }

        executorService.shutdown();//关闭线程池

    }
}

3、SingleThreadExecutor,单例线程

在这里插入图片描述

package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main{

    public static void main(String[] args) {

        /**
         * Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
         */
        ExecutorService executorService = Executors.newSingleThreadExecutor();//创建单例线程池,就是只有一个线程

        for (int i = 0;i < 10;i++){
            executorService.execute(new ThreadTest());//添加线程
        }

        executorService.shutdown();//关闭线程池

    }
}

4、ScheduledThreadPool,可调度预定线程池

在这里插入图片描述

package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main{

    public static void main(String[] args) {

        /**
         * Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
         */
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);//创建最大容量为5的可调度线程池

        /**
         * schedule(Runnable接口,时间(比如5),单位(秒,分钟还是小时))
         */
        scheduledExecutorService.schedule(new Runnable() {//添加一个匿名函数线程,延迟3s执行
            @Override
            public void run() {
                System.out.println("延迟3秒");
            }
        },3, TimeUnit.SECONDS);

        scheduledExecutorService.schedule(new ThreadTest(),3,TimeUnit.SECONDS);//添加ThreadTest线程类延迟3秒
        
        /**
         * scheduleAtFixedRate(Runnable接口,延迟时间,每次执行间隔时间,单位)
         */
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟4秒执行,之后每1s执行一次"+"========="+(Thread.currentThread().getName()));
            }
        },4,1,TimeUnit.SECONDS);

//        scheduledExecutorService.shutdown();//注意,想要每秒执行就不能关闭线程池

    }
}

在这里插入图片描述

另外的单例模式,除了多个线程对象变成一个,其它没有变化,就不多介绍了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

殷丿grd_志鹏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值