百木园-与人分享,
就是让自己快乐。

多线程详解

1. 线程简介

程序:程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念

进程:执行程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。由它自身的产生、存在和消亡的过程

线程是由进程创建的,是进程的一个实体。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位

很多多线程是模拟出来的,真正多线程实现并行,是有多个CPU,即多核。一个CPU统一时间点只能执行一个代码,因为切换快,所以就有同时执行的错觉。 宏观并行,微观串行

public static void main(String[] args) {
    Runtime runtime = Runtime.getRuntime();
    //获取当前电脑的CPU数量
    int i = runtime.availableProcessors();
    System.out.println(i);
}

2. 线程实现(重点)

2.1 实现方式一:继承Thread类

实现步骤:

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
package com.ThreadStudy;

/**
 * @ClassName ThreadDemo01
 * @Description TODO 创建线程方式一:继承Thread类
 * @Author Mark
 * @Date 2022/6/23 13:40
 * @Version 1.0
 */
//创建线程方式一:继承Thread类,重写run方法,调用start开始线程
public class ThreadDemo01 {
    public static void main(String[] args) {
        Cat cat = new Cat();    //new Cat() .var/ctrl+alt+v/alt+enter导入前半部分
        cat.start();//启动线程
        //当main线程启动一个子线程 Thread-0 主线程不会阻塞,会继续执行

        /*
        * 源码:
        * public synchronized void start() {
        *     start0();
        * }
        *
        * private native void start0();//start0是本地方法,是JVM调用,底层由C/C++实现的
        * //真正实现多线程效果由start0()实现
        * */
    }
}

//Thread实现了Runnable接口的run方法
    /*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    */
class Cat extends Thread {//Cat继承于Thread类,该类可当作线程使用
    int times = 0;

    @Override
    public void run() {//重写run方法,写入自己的业务代码
        while (true) {
            //每隔一秒输出一次“喵喵”
            System.out.println(\"喵喵 \" + (++times)+\" 线程名:\"+Thread.currentThread().getName());
            //sleep:使线程休眠 ctrl+alt+t写入try-catch,或者鼠标指向要抛出异常的方法,ctrl+alt+enter
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 80)
                break;//当times=8,线程停止
        }
    }
}

进程启动--->创建main线程--->在主线程中创建一个新的子线程Thread-0

可以使用控制台jconsole打开Java监视和管理控制台查看线程执行情况,当所有的线程结束后,进程才会结束(当主线程结束,主线程的子线程还未结束,进程并不会关闭,而是等待子线程结束后关闭

案例:多线程下载图片

package com.ThreadStudy;

import org.apache.commons.io.FileUtils;

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


/**
 * @ClassName ThreadDemo02
 * @Description TODO 练习Thread,实现多线程同步下载图片
 * @Author Mark
 * @Date 2022/6/23 19:09
 * @Version 1.0
 */
public class ThreadDemo02 extends Thread {
    private String url;//网络地址
    private String name;//保存的文件名

    public ThreadDemo02(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) {
        ThreadDemo02 thread1 = new ThreadDemo02(\"https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg\", \"fpx.jpeg\");
        ThreadDemo02 thread2 = new ThreadDemo02(\"https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png\", \"mgn.jpeg\");
        ThreadDemo02 thread3 = new ThreadDemo02(\"https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png\", \"edg.png\");

        thread1.start();
        thread2.start();
        thread3.start();

        //下载了的文件名为:mgn.jpeg     --thread2
        //下载了的文件名为:fpx.jpeg     --thread1
        //下载了的文件名为:edg.png      --thread3
    }
}

//下载器
class WebDownloader {
    //下载方法
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(\"myThread\\\\src\\\\com\\\\\" + name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println(\"IO异常,downloader方法出现异常\");
        }
    }
}

2.2 实现方式二:实现Runnable接口

java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类创建线程显然不可行了,因此我们通过实现Runnable接口来创建线程

实现步骤:

  • 定义MyRunnable类实现Runnable接口

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

    ThreadDemo03 threadDemo03=new ThreadDemo03();

    new Thread(threadDemo03).start();

推荐使用Runnable对象,因为Java单继承的局限性

package com.ThreadStudy;

/**
 * @ClassName TheardDemo03
 * @Description TODO 创建线程方式二:实现Runnable接口
 * @Author Mark
 * @Date 2022/6/23 20:54
 * @Version 1.0
 */
public class ThreadDemo03 {
    public static void main(String[] args) {
//        Dog dog = new Dog();
//        //dog.start;    dog不能调用start
//        //创建Thread对象,把dog对象(实现Runnable的对象)放入Thread
//        new Thread(dog).start();
//        //这里底层使用了设计模式:代理模式
        Tiger tiger = new Tiger();
        Proxy proxy = new Proxy(tiger);
        proxy.start();


    }
}

class Animal{

}

class Tiger extends Animal implements Runnable{
    @Override
    public void run() {
        System.out.println(\"嗷嗷\");
    }
}

//线程代理类,模拟一个极简的Thread类
class Proxy implements Runnable { //这里可以把Proxy类当作Thread
    private Runnable target = null;//属性、类型是Runnable


    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定
        }
    }

    public Proxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();//⭐⭐⭐⭐⭐
    }

    private void start0() {
        run();
    }
}

class Dog implements Runnable {
    int times = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println(\"汪汪\" + (++times) + \" 线程名:\" + Thread.currentThread().getName());

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 8) {
                break;
            }
        }
    }
}

代理是为了不改变原来代码的基础上增强代码

静态代理:

package com.ThreadStudy;

/**
 * @ClassName StaticProxy
 * @Description TODO 静态代理
 * @Author Mark
 * @Date 2022/6/24 16:10
 * @Version 1.0
 */
public class StaticProxy {
    public static void main(String[] args) {
        //代理是为了不改变原来代码的基础上增强代码
        //在原来只能”嘿嘿嘿“的基础上增加了”准备婚礼“和”付尾款“
//        WeddingCompany weddingCompany=new WeddingCompany(new You());//WeddingCompany代理You
//        weddingCompany.Marry();
        You you=new You();

        new Thread(()-> System.out.println(\"我爱你\")).start();

        new WeddingCompany(you).Marry();
    }

}

//功能接口
interface  Marry{
    //结婚方法
    void Marry();
}

//You:真实对象,实现结婚接口
class You implements Marry{
    @Override
    public void Marry() {
        System.out.println(\"嘿嘿嘿\");
    }
}

//WeddingCompany:代理角色,帮助You
class WeddingCompany implements Marry{

    //帮助对象
    private Marry target;

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

    @Override
    public void Marry() {
        before();
        this.target.Marry();
        after();
    }

    private void after() {
        System.out.println(\"付尾款\");
    }

    private void before() {
        System.out.println(\"准备婚礼\");
    }
}

方式一和方式二对比

继承Thread类 实现Runnable接口
子类继承Thread类具备多线程能力 实现接口Runnable具有多线程能力
启动线程:子类对象.start() 启动线程:线程对象(目标对象).start()
不建议使用:避免OOP单继承局限性 建议使用:避免单继承,灵活方便,方便同一个对象被多个线程使用

案例:售票

通过继承Thread实现:

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();

    }
}

class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享ticketNum

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println(\"售票结束\");
                break;
            }

            //休眠50ms
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(\"窗口 \" + Thread.currentThread().getName() + \"售出一张票\" +
                    \" 剩余票数:\" + (--ticketNum));
        }
    }
}

通过实现Runnable接口实现

public class SellTicket02 {
    public static void main(String[] args) {
        SellTicketWindow sellTicketWindow=new SellTicketWindow();
        new Thread(sellTicketWindow).start();
        new Thread(sellTicketWindow).start();
        new Thread(sellTicketWindow).start();
    }
}

class SellTicketWindow implements Runnable {
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println(\"end...\");
                break;
            }

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(\"窗口 \" + Thread.currentThread().getName() + \"售出一张票\" +
                    \" 剩余票数:\" + (--ticketNum));
        }
    }
}

出现问题:票数超卖

窗口 Thread-0售出一张票 剩余票数:-1

每个线程都会进行if判断,第一个线程if判断出ticketNum>0,还没有执行--ticketNum的时候,第二个线程已经开始了if判断。

案例:龟兔赛跑

  • 首先创建赛道,即实现类ThreadDemo05
  • 判断比赛是否结束
  • 打印出胜利者
  • 龟兔赛跑开始
  • 兔子要睡觉:模拟睡觉
  • 最终乌龟赢得比赛
public class ThreadDemo05 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(1);
                } 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;
        } else if (steps >= 100) {
            winner = Thread.currentThread().getName();
            System.out.println(\"winner is\" + winner);
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        ThreadDemo05 threadDemo05 = new ThreadDemo05();

        new Thread(threadDemo05, \"小兔子\").start();
        new Thread(threadDemo05, \"老乌龟\").start();
    }
}

2.3 实现方式三:实现Callable接口(了解即可)

实现步骤:

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行Future<Boolean r1> result1 = ser.submit(t1);
  • 获取结果Boolean r1 = result1.get()
  • 关闭服务ser.shutdownNow();
package com.ThreadCallable;

import org.apache.commons.io.FileUtils;

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

/**
 * @ClassName ThreadCallableDemo
 * @Description TODO 线程创建方式三:实现Callable接口
 * @Author Mark
 * @Date 2022/6/23 22:06
 * @Version 1.0
 */
//①实现Callable接口,需要返回值类型
public class ThreadCallableDemo implements Callable {
    private String url;//网络地址
    private String name;//保存的文件名

    public ThreadCallableDemo(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //②重写call方法,下载图片线程的执行体
    @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 {
        //③创建目标对象
        ThreadCallableDemo thread1 = new ThreadCallableDemo(\"https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg\", \"fpx.jpeg\");
        ThreadCallableDemo thread2 = new ThreadCallableDemo(\"https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png\", \"mgn.jpeg\");
        ThreadCallableDemo thread3 = new ThreadCallableDemo(\"https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png\", \"edg.png\");

        //④创建执行服务:
        ExecutorService ser= Executors.newFixedThreadPool(1);

        //⑤提交执行:
        Future<Boolean> result1= ser.submit(thread1);
        Future<Boolean> result2= ser.submit(thread2);
        Future<Boolean> result3= ser.submit(thread3);

        //⑥获取结果:
        boolean rs1=result1.get();
        boolean rs2=result2.get();
        boolean rs3=result3.get();

        //⑦关闭服务:
        ser.shutdown();
    }
}

//下载器
class WebDownloader {
    //下载方法
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(\"myThread\\\\src\\\\com\\\\\" + name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println(\"IO异常,downloader方法出现异常\");
        }
    }
}

2.5 Lamda表达式

Java1.8中更新后出现

目的:简化代码

  • 避免匿名内部类定义过多

  • 可以让代码看起来简洁

  • 去掉了没有意义的代码,只留下核心逻辑

    ​eg:new Thread(()->System.out.println(\"多线程\")).start();

实质是属于函数式编程的概念只有函数式接口才能使用Lambda表达式

  • 函数式接口的定义:

    • 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口

      public interface Runnable{
          public abstract void run();
      }
      
  • 对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象

    推导lambda表达式:

package com.lambda;

/**
 * @ClassName LambadDemo01
 * @Description TODO 推导Lambda表达式
 * @Author Mark
 * @Date 2022/6/24 17:15
 * @Version 1.0
 */
public class LambdaDemo01 {

    //2. 静态内部类
    static class Love1 implements ILove {
        @Override
        public void lambda() {
            System.out.println(\"I Love CC Twice\");
        }
    }

    public static void main(String[] args) {

        ILove love = new Love();//创建接口对象=new 实现类

        //1.2 实现类方法调用
        love.lambda();//实现类.方法

        //2.1 静态内部类创建对象调用方法
        love = new Love1();
        love.lambda();

        //3. 局部内部类
        class Love2 implements ILove {
            @Override
            public void lambda() {
                System.out.println(\"I Love CC Three Times\");
            }
        }

        //3.1 局部内部类创建对象调用方法
        love = new Love2();
        love.lambda();

        //4. 匿名内部类:没有类的名称,必须借助接口或者父类
        love = new ILove() {
            @Override
            public void lambda() {
                System.out.println(\"I Love CC Four Times\");
            }
        };
        love.lambda();

        //5. 用lambda简化
        love = () -> {
            System.out.println(\"I Love CC Three thousand\");
        };
        love.lambda();

    }
}

//定义函数式接口
interface ILove {
    void lambda();//默认抽象方法
}

//1.1 实现类
class Love implements ILove {
    @Override
    public void lambda() {
        System.out.println(\"I Love  CC\");
    }
}

lambda简化:

package com.lambda;

/**
 * @ClassName LambdaDemo02
 * @Description TODO
 * @Author Mark
 * @Date 2022/6/24 17:39
 * @Version 1.0
 */
public class LambdaDemo02 {
    public static void main(String[] args) {
//        ILike iLike = (int a)->{
//            System.out.println(\"I Like You \" + a + \" Times\");
//        };
        //简化1,去掉参数类型
//        ILike iLike1=(a)->{
//            System.out.println(\"I Like You \" + a + \" Times\");
//        };

        //简化2,简化括号(只有一个参数时或多个参数类型不同时)
//        ILike iLike2= a->{
//            System.out.println(\"I Like You \" + a + \" Times\");
//        };

        //简化3,简化花括号(方法中只有一条语句时)
        ILike iLike3= a-> System.out.println(\"I Like You \" + a + \" Times\");

//        iLike.like(3000);
//        iLike1.like(3000);
//        iLike2.like(3000);
          iLike3.like(3000);
    }
}

interface ILike {
    void like(int a);
}

3. 线程状态

image

image

线程方法:

方法 说明
void setName(String name) 将此线程的名称更改为等于参数 name
void getName() 返回此线程的名称
void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
void run() 调用该Runnable对象的run方法
void setPriority(int newPeiority) 更改此线程的优先级
void getPriority() 返回此线程的优先级
void join() 等待这个线程死亡。
static void sleep(long millis) 导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停
void interrupt() 中断这个线程
boolean isAilve() 测试这个线程是否活着
static void yield() 暂停当前正在执行的线程对象,并执行其他线程

3.1 停止线程

  • 不推荐使用JDK提供的stop()destory()方法(@Deprecated(since=\"1.2\"):已废弃)

  • 推荐线程自己停止下来:将线程体写到循环内,利用次数终止循环,不建议死循环

  • 建议用一个标志位进行终止变量。当flag=false,终止线程运行:通知方式

    public class StopThread {
        public static void main(String[] args) throws InterruptedException {
            ThreadExit_ threadExit_ = new ThreadExit_();
    
            new Thread(threadExit_).start();
    
            Thread.sleep(10000);
            threadExit_.setFlag(false);
    
        }
    }
    
    class ThreadExit_ implements Runnable {
        private int count = 0;
    
        private boolean flag=true;
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
        @Override
        public void run() {
            while (flag) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + \" 运行中...\" + (++count));
            }
        }
    }
    

3.2 线程中断

class T implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+\" 吃包子...........\"+i);
        }

        try {
            System.out.println(Thread.currentThread().getName()+\" 休眠中...........\");
            Thread.sleep(20002);
        } catch (InterruptedException e) {
            //该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
            //InterruptedException是捕获到一个中断异常
            System.out.println(Thread.currentThread().getName()+\" 被interrupt了\");
        }
    }
}

3.3 线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException;
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延迟、倒计时等
  • 每个对象都有一个锁,sleep不会释放锁
package com.ThreadStudy;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @ClassName SleepThread2
 * @Description TODO 倒计时+打印系统时间
 * @Author Mark
 * @Date 2022/6/24 21:16
 * @Version 1.0
 */
public class SleepThread2 {
    public static void main(String[] args) {
//        try {
//            timeDown();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        Date startTime =new Date(System.currentTimeMillis());
        while(true){
            try {
                System.out.println(new SimpleDateFormat(\"HH:mm:ss\").format(startTime));
                startTime =new Date(System.currentTimeMillis());//更新当前时间
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    //计时器
//    public static void timeDown() throws InterruptedException {
//        int num=10;
//        while(true){
//            Thread.sleep(1000);
//            System.out.println(num--);
//            if (num<0){
//                break;
//            }
//        }
//    }
}

3.4 线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

  • 线程的优先级用数字表示,范围从1~10

    • Thread.MIN_PRIORITY=1

    • Thread.MAX_PRIORITY=10

    • Thread.NORM_PRIORITY=5

      /**
       * The minimum priority that a thread can have.
       */
      public static final int MIN_PRIORITY = 1;
      
      /**
       * The default priority that is assigned to a thread.
       */
      public static final int NORM_PRIORITY = 5;
      
      /**
       * The maximum priority that a thread can have.
       */
      public static final int MAX_PRIORITY = 10;
      
  • 使用以下方法改变或获取优先级

    • getPriority()
    • setPriority(int xx)
package com.ThreadStudy;

/**
 * @ClassName PriorityThread
 * @Description TODO
 * @Author Mark
 * @Date 2022/6/27 15:57
 * @Version 1.0
 */
public class PriorityThread {
    public static void main(String[] args) throws InterruptedException {
        Eat eat = new Eat();

        Thread thread = new Thread(eat);

        thread.setName(\"小明\");
        thread.setPriority(Thread.MIN_PRIORITY);
        thread.start();

        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(\"hi\"+i);
        }

        thread.interrupt();//当执行到这里时就会中断thread的休眠

    }
}

class Eat implements Runnable {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + \" 吃包子...........\" + i);
            }

            try {
                System.out.println(Thread.currentThread().getName() + \" 休眠中...........\");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                //该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
                //InterruptedException是捕获到一个中断异常
                System.out.println(Thread.currentThread().getName() + \" 被interrupt了\");
            }
        }
    }
}



3.5 线程礼让

  • 礼让线程yield(),让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,但礼让的时间不确定,所以礼让不一定成功
package com.ThreadStudy;

/**
 * @ClassName YieldThread
 * @Description TODO 主线程输出20次hello,子线程输出20次hi,主线程输出5次后子线程插队
 * @Author Mark
 * @Date 2022/6/24 21:45
 * @Version 1.0
 */
public class ThreadDemo06 {
    public static void main(String[] args) throws InterruptedException {

        ThreadSon threadSon = new ThreadSon();

        Thread thread = new Thread(threadSon);

        thread.start();

        for (int i = 1; i <= 20; i++) {
            Thread.sleep(500);
            System.out.println(\"hi\");
            if (i == 5) {
                thread.join();
            }
        }
    }
}

class ThreadSon implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(\"hello\");
        }
    }
}

3.6 线程强制执行

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
package com.ThreadStudy;

/**
 * @ClassName JoinDemo
 * @Description TODO Join方法,想象为插队
 * @Author Mark
 * @Date 2022/6/24 22:00
 * @Version 1.0
 */
public class JoinDemo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(\"线程VIP: \"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        JoinDemo joinDemo=new JoinDemo();
        Thread thread=new Thread(joinDemo);


        for (int i = 0; i < 300; i++) {
            if (i==200){
                thread.start();
                thread.join();
            }
            System.out.println(\"主线程: \"+i);
        }

    }
}

4. 守护线程

  • 用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  • 常见的守护线程:垃圾回收机制
package com.ThreadStudy;

/**
 * @ClassName DaemonThread
 * @Description TODO 将一个线程设置为守护线程,当主线程结束后,子线程也结束
 * @Author Mark
 * @Date 2022/7/3 18:10
 * @Version 1.0
 */
public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        myDaemonThread myDaemonThread = new myDaemonThread();
        Thread thread = new Thread(myDaemonThread);
        //如果希望当主线程结束后,子线程可以自动结束,只需将子线程设为守护线程
        thread.setDaemon(true);
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(\"呜呜呜...\");
            Thread.sleep(1000);
        }

    }
}

class myDaemonThread implements Runnable{
    @Override
    public void run() {
        for(;;){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(\"哈哈哈\");
        }
    }
}

5. 线程的生命周期

线程状态:Thread.State 枚举了线程可能处于以下状态之一

  • New:尚未启动的线程处于此状态
  • RUNNABLE:在Java虚拟机中执行的线程出于此状态
    • Ready:线程进入就绪状态,可以被执行
    • Running:线程开始执行,进入运行状态
  • BLOCKED:被阻塞等待监视器锁定的线程处于此状态
  • WAITING:正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED:已退出的线程处于此状态

image

package com.ThreadStudy;

/**
 * @ClassName ThreadState
 * @Description TODO 观察测试线程的状态
 * @Author Mark
 * @Date 2022/6/27 15:38
 * @Version 1.0
 */
public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        System.out.println(thread.getName() + \" 状态 \" + thread.getState());
        thread.start();

        while (Thread.State.TERMINATED != thread.getState()) {
            System.out.println(thread.getName() + \" 状态 \" + thread.getState());
            Thread.sleep(500);
        }

        System.out.println(thread.getName() + \" 状态 \" + thread.getState());

    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println(\"hi\" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

运行结果:

Thread-0 状态 NEW
Thread-0 状态 RUNNABLE
hi0
Thread-0 状态 TIMED_WAITING
hi1
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi2
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi3
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi4
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi5
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi6
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi7
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi8
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi9
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TERMINATED

5. 线程同步(重点)

线程同步机制

  • 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问计数,保证数据在任何时刻,最多有一个线程访问,以保证数据完整性

  • 也可以理解为:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进程操作,直到该线程完成操作,其他线程才能对该内存地址进程操作。

  • 同步具体方法:Synchronized

    • 同步代码块:

      synchronized(对象){//得到对象的锁,才能操作同步代码
          //需要被同步代码
      }
      
    • synchronized还可以放在方法声明中,表示整个方法为同步方法

      public synchronized void fun(String name){
          //需要被同步的代码
      }
      

案例:售票系统升级

package com.Synchronized;

/**
 * @ClassName SellTicket02
 * @Description TODO
 * @Author Mark
 * @Date 2022/7/3 12:31
 * @Version 1.0
 */
public class SellTicket02 {
    public static void main(String[] args) {
        SellTicketWindow sellTicketWindow = new SellTicketWindow();
        new Thread(sellTicketWindow).start();
        new Thread(sellTicketWindow).start();
        new Thread(sellTicketWindow).start();
    }
}

//使用同步方法synchronized实现线程同步
class SellTicketWindow implements Runnable {

    private boolean flag = true;
    private int ticketNum = 100;
	//同步方法,在同一时刻只能有一个线程来执行run方法
    //这时锁在this对象
    //也可以在代码块上写synchronized,同步代码块,互斥锁还是在this对象
  	//synchronized(this){}
    public synchronized void fun() {
        if (ticketNum <= 0) {
            System.out.println(\"end...\");
            flag = false;
            return;
        }

        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(\"窗口 \" + Thread.currentThread().getName() + \"售出一张票\" +
                \" 剩余票数:\" + (--ticketNum));
    }

    @Override
    public void run() {
        while (flag) {
            fun();
        }
    }
}

6. 互斥锁

  • 在Java中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  • 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
  • 多个线程的锁对象必须是同一个
  • 同步的局限性:导致程序的执行效率降低
  • 静态同步方法:静态同步方法的锁是当前类的字节码文件(类名.class)
  • 非静态同步方法:非静态同步方法的锁是this
  • 同步代码块:同步代码块可以使用自定义的Object对象,也可以使用this或者当前类的字节码文件(类名.class)

7. 线程死锁

  • 基本介绍

​ 多个线程都占用了对方的锁的资源,但不肯相让,导致了死锁,一定要避免死锁的发生

  • 实际案例:

    妈妈:你先完成作业,才能打游戏

    儿子:我先打游戏,才能完成作业

    package com.Synchronized;
    
    /**
     * @ClassName DeadLockDemo
     * @Description TODO
     * @Author Mark
     * @Date 2022/7/8 20:09
     * @Version 1.0
     */
    public class DeadLockDemo {
        public static void main(String[] args) {
            DeadLock deadLock1 = new DeadLock(true);
            DeadLock deadLock2 = new DeadLock(false);
            new Thread(deadLock1,\"A线程\").start();
            new Thread(deadLock2,\"B线程\").start();
        }
    }
    
    class DeadLock implements Runnable {
        static Object o1 = new Object();//保证多线程共享一个对象,使用static
        static Object o2 = new Object();
        boolean flag;
    
        public DeadLock(boolean flag) {
            this.flag = flag;
        }
    
        @Override
        public void run() {
            if (flag) {
                synchronized (o1) {//对象互斥锁,下面是同步代码
                    System.out.println(Thread.currentThread().getName() + \"进入1\");
                    synchronized (o2) {
                        System.out.println(Thread.currentThread().getName() + \"进入2\");
                    }
                }
            } else {
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + \"进入3\");
                    synchronized (o1) {
                        System.out.println(Thread.currentThread().getName() + \"进入4\");
                    }
                }
            }
        }
    }
    

8.释放锁

释放锁的操作:

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

不会释放锁的操作:

  • 线程执行同步代码或同步方法时,程序调用了Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
    • 应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

来源:https://www.cnblogs.com/hackertyper/p/16459588.html
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » 多线程详解

相关推荐

  • 暂无文章