java基础之多线程详细掌握

  • 线程的简介
    进程(process)
    线程(thread)
    调度模式
    内存开销
    普通方法调用和多线程调用
  • 线程实现的三种方式
    继承Thread类
    实现Runnable接口
    实现Calleble 接口
    初识并发问题
  • 线程状态,线程同步
    线程的五大状态
    线程方法
    线程停止
    线程休眠
    线程礼让
    线程强制执行
    观测线程状态
    线程优先级
    守护线程
  • 线程通信问题
    -生产者与消费者模式
    管程法
    红绿灯法
  • 高级主题
    线程池
    静态代理模式
    Lamda表达式

线程的简介

例如边吃饭边玩手机
生活中有太多这样同时做多件事情的例子,看起来是多个任务都在做,其本质上我们的大脑在同一时间依旧只做了一件事情
多个线程组成一个进程,QQ,应用,播放器,等
1、进程(process)
一个启动的应用程序就是进程,执行程序的过程,启动后就分配了内存 和 CPU
2、线程(thread)
进程中的一段顺序执行流,它是cpu执行和调度的最基本的单元
只是程序计数器用一点点内存,其他资源共享进程资源
3、调度模式
分时调度
抢占式调度
4、内存开销
进程:创建和销毁内存消耗比较大
线程:创建和销毁内存消耗比较小
同一个进程当中的所有线程共享次进程的所有资源

5、普通方法调用和多线程调用
普通方法调用
主线程下来,调用run方法,方法执行完,再往下走,
在这里插入图片描述
主线程走主线程,子线程走子线程

总结第一章核心概念:
  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • main()称之为主线程,为系统入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排,调度器与操作系统紧密相连,不可控;
  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制;
  • 线程对带来额外的开销,如cpu调度时间,并发控制开销;
  • 每个线程在自己的工作内存交互,内存控制不会造成数据不一致;

线程实现的三种方式

创建maven项目方便点

1、继承Thread类

覆盖run方法。run方法里面就是线程要执行的顺序流。
书写顺序:
自定义线程类继承Thread类
重写run()方法,编写线程的执行体
创建线程对象,调用start()方法启动线程

//创建线程的方式一:继承Thread类,重写run*()方法,调用start开启线程
public class Threadtest1 extends Thread{
    @Override
    //run()方法线程体
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码-----"+i);
        }
    }
    //main线程,主线程
    public static void main(String[] args) {
        //创建一个线程对象
        Threadtest1 threadtest1 = new Threadtest1();
        //调用start方法开启线程
        threadtest1.start();
        for (int j = 0; j < 20; j++) {
            System.out.println("我在学习多线程--"+j);
        }
    }
}

总结:线程不一定立即执行,CPU安排调度。
异步执行,没有顺序。

写一个案例练习1:

maven下载依赖

<dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

代码:


import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//练习Rread,实现多线程同步下载图片
public class Treadtest2 extends Thread{
    private String url;//文件地址
    private String name;//保存的文件名

    public Treadtest2(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名字是"+name);

    }
    public static void main(String[] args) {
        Treadtest2 treadtest1 = new Treadtest2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4179487881,3847867256&fm=11&gp=0.jpg","1.jpg");
        Treadtest2 treadtest2 = new Treadtest2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260498&di=1751f388a8f222d966efe98c0b0e5d88&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201308%2F05%2F20130805105309_5E2zE.jpeg","2.jpeg");
        Treadtest2 treadtest3 = new Treadtest2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260493&di=d45ac86c1fafba25175847b96e0184d6&imgtype=0&src=http%3A%2F%2F00.minipic.eastday.com%2F20170331%2F20170331113141_98c8105e504ff71e68d59a6eaa30bd7e_5.jpeg","3.jpeg");
        treadtest1.start();
        treadtest2.start();
        treadtest3.start();
    }
}
//下载类
class WebDownloader{
    //下载方法
    public void downloader(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));//url变成文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }

    }
}

结果:
在这里插入图片描述

2、实现Runnable接口,

覆盖run方法。run方法里面就是线程要执行的顺序流。
将实现实现Runnable接口的类对象放入线程对象中
注意:Tread类也实现了Runable接口


//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class Testrunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("当前执行的线程是---"+Thread.currentThread().getName()+"--------"+i);
        }
    }
    public static void main(String[] args) {
        //创建runnable接口实现类对象
        Testrunnable testrunnable = new Testrunnable();
        //创建线程对象,通过线程对象来开启线程,代理
        Thread thread = new Thread(testrunnable,"线程1");
        Thread thread2 = new Thread(testrunnable,"线程2");
        thread.start();
        thread2.start();
        //new Thread(testrunnable).start  匿名对象方式
        
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码----"+i);
        }
    }
}

自行练习将案例一改为runnable接口方式
小结:
继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程,子类对象.start()
  • 不建议使用:避免OOP单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用,避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
写一个案例练习2(初识并发):
//多个线程同时操作同一个对象
// 发现问题,多个线程操作同一个资源的情况下,线程不安全,数据紊乱
//可参考多线程同步锁我写的那篇,也可以继续往下看
public class Testrunnable2 implements Runnable {
    //票数
    private int ticketNums=10;
    @Override
    public void run() {
        while (true){
            if (ticketNums<=0) {
                break;
            }
            //模拟延迟
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");//每拿一张递减

        }
    }
    public static void main(String[] args) {
        Testrunnable2 testrunnable2 = new Testrunnable2();
        new Thread(testrunnable2,"小明").start();
        new Thread(testrunnable2,"小红").start();
        new Thread(testrunnable2,"小芳").start();
    }
}
案例3:龟兔赛跑

步骤:
1,首先来个赛道,然后距离终点越来越近
2,判断比赛是否结束
3,打印出胜利者
4,龟兔赛跑开始
5,股市是乌龟赢了,兔子需要睡觉
6,乌龟赢得比赛

public class Race implements Runnable {
    //胜利者
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //让兔子睡觉
            if (Thread.currentThread().getName().equals("兔子")&& i%10==0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag=gameOver(i);
            //比赛结束停止程序
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps){
        if (winner!=null) {
            return true;
        }{
            if (steps>=100) {
                winner=Thread.currentThread().getName();
                System.out.println("winner is"+winner);
                return true;
            }
        }
        return false;
    }
    public static void main(String[] args) {
        Race race = new Race();
        Thread thread = new Thread(race,"兔子");
        Thread thread2 = new Thread(race,"污龟");
        thread.start();
        thread2.start();
    }
}
3、实现Calleble 接口(前期先了解)

概要:
1,实现Callable接口,需要返回值类型
2,重写call方法,需要抛出异常
3,创建目标对象
4,创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
5,提交执行:Futureresult1=ser.submit(t1);
6,获取结果:boolean r1=result1.get()
7,关闭服务ser.shutdownNow();

修改下载图片的案例

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class CallAble implements Callable<Boolean> {

    private String url;//文件地址
    private String name;//保存的文件名

    public CallAble(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名字是"+name);
return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallAble callAble1 = new CallAble("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4179487881,3847867256&fm=11&gp=0.jpg","1.jpg");
        CallAble callAble2 = new CallAble("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260498&di=1751f388a8f222d966efe98c0b0e5d88&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201308%2F05%2F20130805105309_5E2zE.jpeg","2.jpeg");
        CallAble callAble3 = new CallAble("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260493&di=d45ac86c1fafba25175847b96e0184d6&imgtype=0&src=http%3A%2F%2F00.minipic.eastday.com%2F20170331%2F20170331113141_98c8105e504ff71e68d59a6eaa30bd7e_5.jpeg","3.jpeg");


        //创建执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> result1=ser.submit(callAble1);
        Future<Boolean> result2=ser.submit(callAble2);
        Future<Boolean> result3=ser.submit(callAble3);
        //获取结果
        boolean rs1=result1.get();
        boolean rs2=result2.get();
        boolean rs3=result3.get();
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        //关闭服务
         ser.shutdownNow();
//        treadtest1.start();
//        CallAble.start();
//        treadtest3.start();
    }
}
//下载类
class WebDownloader{
    //下载方法
    public void downloader(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));//url变成文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }

    }
}

线程状态,线程同步

转连接:线程的五大状态与线程方法加案例:

转链接:线程同步与同步锁详解加案例

线程通信问题

生产者消费者模式

是个问题不是设计模式
应用场景:生产者和消费者问题
◆假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费.
◆如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止.
◆如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.
在这里插入图片描述
分析问题:
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件.
◆对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
◆对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费.

◆在生产者消费者问题中 ,仅有synchronized是不够的
◆synchronized 可阻止并发更新同一个共享资源,实现了同步
◆synchronized 不能用来实现不同线程之间的消息传递(通信)

所以java提供了几个方法解决线程之间的通信问题
在这里插入图片描述
注意:均为Object类的方法,都只能在同步方法或者同步代码块中只用,否则会抛出异常

解决方式一:
并发协作模型“生产者/消费者模式”–>管程法

◆生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
◆消费者:负责处理数据的模块(可能是方法,对象,线程,进程);< >
◆缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
解决方式二:
并发协作模型“生产者/消费者模式”–>信号灯法
做一个标识位,boolean类型,判断为真,就让其等待,判断为假就让他去通知,红灯停绿灯行。
管程法:

//测试生产者消费者模型,利用缓冲区解决-->管程法
public class TestPc {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Produter(container).start();
        new Consumer(container).start();
    }
}

//生产者
class Produter extends Thread{
    SynContainer container;
    public Produter(SynContainer container){
        this.container=container;
    }
    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");

        }
    }
}
//消费者
class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container=container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了--》"+container.pop().id+"只鸡");
//            container.push(new Chicken(i));
        }
    }
}

//产品鸡
class Chicken{
    int id;
    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{
    //需要一个容器大小
    Chicken[] chickens=new Chicken[10];

    //容量计数器
    int count=0;

    //生产者放入产品
    public synchronized void push(Chicken chicken){
        //如果容器满了,就需要等待消费者消费
        if (count==chickens.length) {
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要丢入产品
        chickens[count]=chicken;
        count++;
        //可以通知消费者消费了
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop(){
        //判断能否消费
        if (count==0) {
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken=chickens[count];
        //c吃完了,通知消费者生产
        this.notifyAll();
        return chicken;
    }
}

信号灯法:

//测试生产者消费者,信号灯法,标志位解决
public class TestPc2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Actor(tv).start();
        new Watch(tv).start();
    }
}
//生产者-->演员
class Actor extends Thread{
    TV tv;

    public Actor(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("快乐大本营");
            } else {
                this.tv.play("抖音:记录美好生活");
            }
        }
    }
}
//消费者-->观众
class Watch extends Thread{
    TV tv;

    public Watch(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();

        }
    }
}

//产品-->节目
class TV{
    //演员表演,观众等待
    //观众观看,演员等待
    String voice;//表演的节目
    boolean flag=true;

    //表演
    public synchronized void play(String voice){
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了"+voice);
        //通知观众观看
        this.notifyAll();//通知唤醒
        this.voice=voice;
        this.flag= !this.flag; //取反,是真就是假,是假就是真
    }

    //观看
    public synchronized void watch(){
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag= !this.flag;
    }

}

高级主题

线程池

◆背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
◆思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

◆好处:
◆提高响应速度(减少了创建新线程的时间)
◆降低资源消耗(重复利用线程池中线程,不需要每次都创建)
◆便于线程管理…
◆corePoolSize: 核心池的大小
◆maximumPoolSize:最大线程数
◆keepAliveTime: 线程没有任务时最多保持多长时间后会终止

使用线程池:
◆JDK 5.0起提供了线程池相关API: ExecutorService和Executors
◆ExecutorService:真正的线程池接口,常见子类: ThreadPoolExecutor
◆void execute(Runnable command) : 执行任务/命令,没有返回值,一般用来执行Runnable
◆ Future submit(Callable task): 执行任务,有返回值,一般用来执行Callable
◆void shutdown() :关闭连接池
◆Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池

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

public class TestPool implements Runnable {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        executorService.execute(new TestPool());
        executorService.execute(new TestPool());
        executorService.execute(new TestPool());
        executorService.execute(new TestPool());
        executorService.shutdown();
    }
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
            
        }
    }
静态代理模式(动态代理见博客其他部分)

概述:
你:真实角色
婚庆公司:代理你,处理结婚的事情
结婚:实现都实现结婚接口即可
演示静态代理对比Thread

//静态代理模式
//真实对象(目标对象)都要实现同一个接口
//代理对象要代理真实角色
//优势:
  //代理对象可以做真实对象做不了的事情
  //真实对象专注做自己的事情
public class StaticProxy {
    public static void main(String[] args) {
       
        /***
        //加入线程,最后再看
        new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        }).start();
        //换做lamda表达式
        new Thread(() -> System.out.println("我爱你")).start();
        new WeddingCompany(new You()).HappyMarry();
        ***/
        
        //You you = new You();
        WeddingCompany weddingCompany=new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }
}
//定义接口
interface Marry{
    void HappyMarry();

}
//你,真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("你要结婚了");

    }
}

//婚庆公司,代理角色,帮助你
class WeddingCompany implements Marry{

    //代理谁--》目标对象(真实目标角色)
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//真实角色对象
        after();

    }

    private void after() {
        System.out.println("结婚之后,送入洞房");
    }

    private void before() {
        System.out.println("结婚之前,准备现场");
    }
}
Lambda表达式

概述:
1,避免匿名内部类定义过多(通常写个外部类new一个对象,因为只用一次,写成匿名内部类)。
2,让代码更简洁。
3,去掉没有意义的代码,只留核心逻辑。
4,理解函数式接口(Function Inrweface),是学习java8 lamda表达式关键。
5,函数式接口定义:任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口,对于函数式接口,可以通lamda表达式来创建该接口的对象。
正常表达方式:

public class TestLamda {
    public static void main(String[] args) {
        //创建一个接口对象,接口new一个实现类
        Ilike ilike=new Like();
        ilike.lambda();

    }
}
//定义一个函数式接口
interface Ilike{
    //一个抽象方法
    void lambda();
}
//实现类
class Like implements Ilike{

    @Override
    public void lambda() {
        System.out.println("我喜欢lambda");
    }
}

优化简化–>推导过程:

public class TestLamda {
    //2.静态内部类
    static class Like2 implements Ilike{
        @Override
        public void lambda() {
            System.out.println("我喜欢lambda2");
        }
    }

    public static void main(String[] args) {
        //创建一个接口对象,接口new一个实现类
        Ilike ilike=new Like();
        Ilike ilike2=new Like2();
        ilike.lambda();
        ilike2.lambda();

        //3.局部内部类
        class Like3 implements Ilike{
            @Override
            public void lambda() {
                System.out.println("我喜欢lambda3");
            }
        }
        Ilike ilike3=new Like3();
        ilike3.lambda();

        
        //4.匿名内部类:没有类的名称,必须借助接口或父类
        Ilike ilike4=new Ilike() {
            @Override
            public void lambda() {
                System.out.println("我喜欢lambda4");
            }
        };
        ilike4.lambda();

        //5.用lambda表达式
        Ilike ilike5=()->{
            System.out.println("我喜欢lambda5");
        };
        ilike5.lambda();


    }
}
//定义一个函数式接口
interface Ilike{
    //一个抽象方法
    void lambda();
}
//1.实现类
class Like implements Ilike{

    @Override
    public void lambda() {
        System.out.println("我喜欢lambda");
    }
}

结果:
在这里插入图片描述
简化lambda过程精要:

public class TestLamda2 {
    public static void main(String[] args) {
        ILove love=new Love();
        love.love(3);

        //匿名内部类
        ILove love2=new ILove() {
            @Override
            public void love(int a) {
                System.out.println("I LOVE YOU----"+a);
            }
        };
        love2.love(520);

        //lambda表达式
        ILove love3=(int a)->{
            System.out.println("I LOVE YOU----"+a);
        };
        love3.love(521);

        //lambda表达式简化1:参数类型
        ILove love4=(a)->{
            System.out.println("I LOVE YOU----"+a);
        };
        love4.love(522);

        //lambda表达式简化2:简化括号
        ILove love5=a->{
            System.out.println("I LOVE YOU----"+a);
        };
        love5.love(1314);

        //lambda表达式简化3:简化花括号
        ILove love6=a-> System.out.println("I LOVE YOU----"+a);

        love6.love(1314520);
    }
}
//总结:
//lambda表达式只能有一行代码的情况下才能简化,如果有多行,那么就用花括号包裹
//前提是接口为函数式接口,只能有一个抽象方法
//多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号

interface ILove{
    void love(int a);
}
class Love implements ILove{

    @Override
    public void love(int a) {
        System.out.println("I LOVE YOU----"+a);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值