多线程详解

多线程

1.线程,进程,多线程

  • 多线程:多条执行路径,主线程与子线程并行交替执行(普通方法只有主线程一条路径)。
  • 程序:指令和数据的有序集合,本身没有任何运行的含义,是一个静态的概念。
  • 进程:在操作系统中运行的程序就是进程,即执行程序的一次执行过程,是一个动态的概念。
  • 一个进程可以有多个线程,比如视频中同时听声音,看图像,看弹幕等。
  • 线程是CPU调度和执行的基本单位。

注意:
很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,
cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

核心概念

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

2.线程创建

三种方式:

  1. 继承Thread类(重点)
  2. 实现Runnable接口(重点)
  3. 实现Callable接口(了解)

方法1:Thread类

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

//调用多线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread1 extends Thread{
    @Override
    public void run() {    //多线程方法体
        for (int i = 0; i < 50; i++) {
            System.out.println("我在学习多线程");
        }
    }

    //main方法,主线程
    public static void main(String[] args) {
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();

        //调用start方法开启线程
        testThread1.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("我在看代码");
        }
    }
}

运行结果其实是 我在学习多线程 和 我在看代码 交替执行,因为一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉,实质上是交替执行。

问题:利用多线程同时下载多张照片
package demo01;

import org.apache.commons.io.FileUtils;

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

//利用多线程同时下载图片
public class TestThread2 extends Thread{

    private String url;   //图片网络链接
    private String name;  //保存文件名

    //有参构造
    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        WebDoanloader webDoanloader = new WebDoanloader();
        webDoanloader.doanloader(url,name);
    }

    //定义一个下载器(一个类里面写下载方法)
    class WebDoanloader{
        //下载方法
        public void doanloader(String url,String name){
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
                System.out.println("下载名为:"+name);
            } catch (IOException e) {
                System.out.println("文件下载异常");
            }
        }
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=sG3Ey32h&id=849A1B58327DE85B2DA9F594D962FDB0A7F2A8C2&thid=OIP.sG3Ey32h9wi9SNVwOoYTugHaNK&mediaurl=https%3a%2f%2fpic3.zhimg.com%2fv2-e97611f0a61c75662afaf15ed03002ce_r.jpg&exph=1920&expw=1080&q=%e5%b0%8f%e5%a7%90%e5%a7%90%e5%9b%be%e7%89%87&simid=608006123438289425&FORM=IRPRST&ck=93253D905D95339FEA3A64CD76DEFC04&selectedIndex=10", "1.jpg");
        TestThread2 t2= new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=r9%2bCBVXH&id=E4F0F2392385789627B34CB97C7E157B053073F7&thid=OIP.r9-CBVXH8kmTv1t21TUL-QHaQD&mediaurl=https%3a%2f%2fpic1.zhimg.com%2fv2-afdf820555c7f24993bf5b76d5350bf9_r.jpg%3fsource%3d1940ef5c&exph=2340&expw=1080&q=%e5%b0%8f%e5%a7%90%e5%a7%90%e5%9b%be%e7%89%87&simid=608005169961369771&FORM=IRPRST&ck=B8176D685EECEB8A4F2FAD8D9DC6178C&selectedIndex=23", "2.jpg");
        TestThread2 t3 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=fjh5iqpq&id=4E711ABF8F13BEE4E8E3DB1669AE8490C80FDB3B&thid=OIP.fjh5iqpqLE8pHaGfce3VFwHaNK&mediaurl=https%3a%2f%2fpic4.zhimg.com%2fv2-9e0efd1f631f806afa0aa97b16a3a010_r.jpg%3fsource%3d1940ef5c&exph=1920&expw=1080&q=%e5%b0%8f%e5%a7%90%e5%a7%90%e5%9b%be%e7%89%87&simid=608017642549226249&FORM=IRPRST&ck=36CDED3ABB8E0572351E5B3CC1EDACE3&selectedIndex=19", "3.jpg");

        t1.start();
        t2.start();
        t3.start();

        //交替运行,每次的结果都不一样
    }
}
package demo01;

//创建线程
public class Download extends Thread {

    private String url;   //  文件网络地址(URL)
    private String name;  //   保存的文件名

    //  有参构造器,用来给URL和 name赋值
    public Download(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        //在重写的run()方法中,调用下载器
        new WebDownload().download(url,name);
    }

    public static void main(String[] args) {
        new Download("https://cn.bing.com/images/search?q=%E5%9B%BE%E7%89%87&FORM=IQFRBA&id=49E031AAC7715C25D8E03215367A7B4A50E14354","1").start();
        new Download("https://cn.bing.com/images/search?q=%E5%9B%BE%E7%89%87&FORM=IQFRBA&id=1C8280D2D75B8653FE4F87817387F57189B5AA41","2").start();
        new Download("https://cn.bing.com/images/search?q=%E5%9B%BE%E7%89%87&FORM=IQFRBA&id=1C8280D2D75B8653FE4F94F7C32E1B68E30CC400","3").start();
    }
}






/*

package demo01;

import org.apache.commons.io.FileUtils;

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

//自定义一个下载器
public class WebDownload {
    public void download(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
            System.out.println("文件"+name+"下载完成");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("文件下载出错!");
        } finally {
        }
    }
}

 */

方法2:Runnable接口

  1. 自定义线程类实现Runnable接口
  2. 重写run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动对象
  4. 出现的问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱

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

package demo02;

//创建线程第二种方法:实现runnable接口
public class TestRunnable implements Runnable{
    @Override
    public void run() {    //run方法线程体
        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习Runnable接口");
        }
    }

    public static void main(String[] args) {
        //创建一个线程对象
        TestRunnable testRunnable = new TestRunnable();

        //开启线程对象来start()开启线程,代理
        Thread thread = new Thread(testRunnable);
        thread.start();

        //上面两句可以合并为一句
        //new Thread(testRunnable). start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码");
        }
    }
}
例子:买火车票
package demo02;

//模拟买火车票
public class TestRunnableExample1 implements Runnable{

    private int ticketNums = 10;   //定义票数

    @Override
    public void run() {
        while (true){
            if (ticketNums <= 0) break;

            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {
        //开启一份资源,多个代理
        TestRunnableExample1 testRunnableExample1 = new TestRunnableExample1();
        new Thread(testRunnableExample1,"张三").start();
        new Thread(testRunnableExample1,"李四").start();
        new Thread(testRunnableExample1,"王五").start();
    }

    //出现的问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
}
例子2:龟兔赛跑

思路:

1.首先来个赛道距离,然后要离重点越来越近

2.判断比赛是否结束

3.打印出胜利者

4.龟兔赛跑开始

5.故事中是因为兔子睡觉乌龟赢,所以要模拟兔子睡觉

6.最终,乌龟赢了

package demo02;

//模拟龟兔赛跑,兔子睡觉,乌龟赢
public class Race implements Runnable{

    private String winner;   //定义一个胜利者
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            boolean flag = gameOver(i);
            if (flag) break;

            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");

            //模拟兔子睡觉
            if (Thread.currentThread().getName().equals("兔子") && i == 50){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //判断比赛是否完成
    public boolean gameOver(int steps){
        if (winner != null){
            return true;
        }else if (steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("胜利者是"+winner);
            return true;
        }else {
            return false;
        }
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

方法3:Callable接口

  1. 实现Callable接口,需要返回值类型

  2. 重写call方法,需要抛出异常

  3. 创建目标对象

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

  5. 提交执行:Future result1 = ser.submit(11);

  6. 获取结果:boolean r1 = result1.get()

  7. 关闭服务:ser.shutdownNow();

package demo02;

import java.util.concurrent.*;

/**
 * 方式三:实现Callable接口
 * 1.可以返回值
 * 2.可以抛出异常
 */

public class TestCallable implements Callable {

    private String url;
    private String name;

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

    @Override
    public Boolean call() throws Exception {
        new WebDownloader().downloader(url,name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "1.png");
        TestCallable t2 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "2.png");
        TestCallable t3 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "3.png");

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

        //提交执行
        Future<Boolean> s1 = ser.submit(t1);
        Future<Boolean> s2 = ser.submit(t2);
        Future<Boolean> s3 = ser.submit(t3);

        //获取结果
        Boolean b1 = s1.get();
        Boolean b2 = s2.get();
        Boolean b3 = s3.get();
        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);

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

静态代理(多线程的底层逻辑)

package staticproxy;

/*
静态代理:
1.真实对象与代理对象要实现同一接口
2.代理对象创建代理真实角色
好处:
1.代理对象可以做很多真实对象做不了的事情,比如布置结婚场景
2.真实对象专注做自己的事情,结婚
 */

public class StaticProxy {
    public static void main(String[] args) {

        /*You you = new You();
        you.HappyMarry();*/ //原本方式,下面交给代理

        //线程类代理,实际调用了Runnable接口中的run方法
        
        new Thread(()-> System.out.println("我爱你")).start();

        new WeddingCompany(new You()).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();
    }

    public void before(){
        System.out.println("布置现场");
    }

    public void after(){
        System.out.println("收尾款");
    }
}

Lamda表达式

  • λ 希腊字母表中排序第十一位的字母,英语名称为 Lamda
  • 避免匿名内部类定义过多
  • 其实质属于函数式编程的概念
  • 去掉了一堆没有意义的代码,只留下核心逻辑
new Thread (()->System.out.println(“多线程学习。。。。”)).start();

函数式接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口.

public interface Runnable{
    public abstract void run();
}

对于函数式接口,我们可以通过Lamda表达式来创建该接口的对象.

package lambda;

public class Test01 {
    public static void main(String[] args) {
        //2.用lambda简化,-->函数式接口
        ILike iLike = () -> {
            System.out.println("Lambda表达式");
        };
        iLike.ilike();
    }
}

//1.定义一个函数式接口
interface ILike {
    void ilike();
}

Lamda表达式进化之路:

package lambda;

/*
推导Lambda表达式
 */
public class Test02 {

    //3.静态内部类
    static class Demo2 implements Lambda {
        @Override
        public void study() {
            System.out.println("我在学习Lambda表达式2");
        }
    }

    public static void main(String[] args) {
        //实现类调用
        Lambda demo = new Demo1();
        demo.study();  //我在学习Lambda表达式1

        //静态内部类调用
        demo = new Demo2();
        demo.study();  //我在学习Lambda表达式2

        //4.局部内部类实现
        class Demo3 implements Lambda {
            @Override
            public void study() {
                System.out.println("我在学习Lambda表达式3");
            }
        }

        //局部内部类掉用
        demo = new Demo3();
        demo.study();  //我在学习Lambda表达式3

        //5.匿名内部类:没有类的名称,必须借助接口(new 接口)或者父类
        demo = new Lambda() {
            @Override
            public void study() {
                System.out.println("我在学习Lambda表达式4");
            }
        };

        //匿名内部类调用
        demo.study();  //我在学习Lambda表达式4

        //6.用Lambda表达式
        demo = ()-> {
            System.out.println("我在学习Lambda表达式5");
        };

        //Lambda调用
        demo.study();   //我在学习Lambda表达式5
    }
}

//1.定义一个函数式接口
interface Lambda {
    void study();
}

//2.函数式接口的实现类
class Demo1 implements Lambda {
    @Override
    public void study() {
        System.out.println("我在学习Lambda表达式1");
    }
}

Lamda表达式继续简化:

package lambda;

public class Test03 {
    public static void main(String[] args) {
        //1.Lambda表达式普通形式
        UserDao1 userDao1 = () -> {
            System.out.println("UserDao1");
        };
        userDao1.add();

        //2.方法体只有一行代码时可以去掉花括号{}
        UserDao2 userDao2 = () -> System.out.println("UserDao2");
        userDao2.add();

        //3.当有返回值时不能去掉花括号{}
        UserDao3 userDao3 = () -> {
            return 0;
        };
        //有返回值要接收并输出才能看见
        System.out.println(userDao3.add());

        //4.有一个参数时,参数类型可以去掉,参数类型外边的括号也能去掉()
        UserDao4 userDao4 = a -> {
            System.out.print("UserDao4,a=");
            return a;
        };
        System.out.println(userDao4.add(10));

        //5.有多个参数时,参数类型可以去掉,但需要全部去掉,或者全部不去,但是参数外边的括号不能去掉()
        UserDao5 userDao5 = (a,b) -> {
            System.out.print("UserDao5,a+b=");
            return a + b;
        };
        System.out.println(userDao5.add(1, 2));
    }
}

//定义一个函数式接口
interface UserDao1 {
    void add();
}

interface UserDao2 {
    void add();
}

interface UserDao3 {
    int add();
}

interface UserDao4 {
    int add(int a);
}

interface UserDao5 {
    int add(int a, int b);
}

3.线程状态及方法

五大状态

线程五大状态:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

线程停止

在这里插入图片描述

package threadstop;

public class TestStop01 implements Runnable {
    //1.设置一个标识位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 1;
        while (flag){
            System.out.println("兔跑了第" + i++ +"步");
        }
    }

    //2.设置公开的方法,转换标识符,让线程停止
    private void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop01 testStop01 = new TestStop01();
        new Thread(testStop01).start();
        for (int i = 1; i <= 100; i++) {
            System.out.println("乌龟跑了第"+i+"步");
            //3.调用stop()方法让线程停止
            if (i == 80){
                testStop01.stop();
                System.out.println("兔该停下了");
            }
        }
    }
}

线程休眠(sleep)

  • sleep(时间) 指定当前线程阻塞的毫秒数;
  • sleep时间到达后线程就进入就绪状态;
  • sleep存在异常InterruptedException;
  • sleep可以模拟网络延时,倒计时等。(故意设置延时收优化钱💴,不道德,比如某盘)
  • 每一个对象都有一个锁🔒,sleep不会释放锁。
package threadsleep;

//模拟网络延时,买火车票
//放大问题的发生性
public class TestSleep1 implements Runnable{

    //定义票的总数
    private int tickets = 10;

    //线程体
    @Override
    public void run(){
        while (true) {
            if (tickets <= 0) break;

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

            System.out.println(Thread.currentThread().getName()+"拿到了第" + tickets-- + "张票");
        }
    }

    public static void main(String[] args) {
        //一份资源,三个代理
        Runnable testSleep1 = new TestSleep1();
        new Thread(testSleep1,"张三").start();
        new Thread(testSleep1,"李四").start();
        new Thread(testSleep1,"王五").start();
    }
}
package threadsleep;

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

//模拟倒计时
public class TestSleep2 {
    public static void main(String[] args) {

        //主线程中调用倒数计时方法
        try {
            new TestSleep2().countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //打印系统当前时间
        Date date = new Date(System.currentTimeMillis());   //获取当前时间

        //模拟时钟
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date = new Date(System.currentTimeMillis());  //更新当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }



    }

    //倒数计时方法
    public void countDown() throws InterruptedException {

        //倒数从低级秒开始
        int num = 10;

        while (true){
            if (num < 0) break;
            Thread.sleep(1000);
            System.out.println(num--);
        }
    }
}
package threadsleep;

public class TestSleep3 {
    public static void main(String[] args) {
        //在新创建的线程中运行
        new Thread(new CountDown()).start();
    }
}

//模拟倒计时类
class CountDown implements Runnable{
    private int num = 10;
    @Override
    public void run() {
        while (true){
            if (num < 0)  break;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
        }
    }
}
package threadsleep;

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

public class ThreadSleep4 {
    public static void main(String[] args) {

        //new Clock().run();    //在主线程运行
        new Thread(new Clock()).start();   //在新创建的线程运行
    }
}

class Clock implements Runnable{

    //打印系统当前时间
    Date date = new Date(System.currentTimeMillis());  //获取当前时间

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date = new Date(System.currentTimeMillis()); //更新时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}

线程礼让(Yield)

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转化为就绪状态
  • 让CPU重新调度,但礼让不一定成功,看CPU心情
package threadjoin;

//join()方法,线程插队,强制执行,阻塞其他线程,有重要的线程就可以使用线程插队让这个主要的线程先执行。尽量少用
public class TestJoin1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("VIP线程" + i);
        }
    }

    //主线程
    public static void main(String[] args) {
        Runnable join = new TestJoin1();
        Thread thread = new Thread(join);
        thread.start();   //开启线程

        for (int i = 0; i <= 200; i++) {
            if (i == 150){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("普通" + i);
        }
    }
}
package threadjoin;

public class TestJoin2 {
    //主线程
    public static void main(String[] args) {
        //启动VIP线程
        Thread vip = new Thread(new Vip());
        vip.start();
        try {
            vip.join();    //把主要线程用join()方法,让其先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //启动普通线程
        Thread a = new Thread(new A());
        a.start();

        //主线程任务
        for (int i = 0; i < 200; i++) {
            System.out.println("主线程");
        }
    }
}

//重要线程
class Vip implements Runnable{
    @Override
    //VIP线程任务
    public void run() {
        System.out.println("VIP线程====================");
    }
}

//普通线程
class A implements Runnable{
    @Override
    //普通线程任务
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("普通线程");
        }
    }
}

观测线程状态

线程五大状态:新生状态,就绪状态,运行状态,阻塞状态,死亡状态

  • Thread.State方法

线程状态, 线程可以处于以下状态之一:

  • NEW 尚未启动的线程处于此状态。
  • RUNNABLE 在Java虚拟机中执行的线程处于此状态。
  • BLOCKED被阻塞等待监视器锁定的线程处于此状态。
  • WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED已退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

package threadstate;

//观察线程状态
public class TestState {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            int num = 5;
            //线程等待五秒
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("================");
            }
        );

        Thread.State state = thread.getState();  //获取当前线程状态
        System.out.println(state);    //打印当前线程状态

        thread.start();   //启动该线程
        state = thread.getState();   //获取当前线程状态
        System.out.println(state);   //打印当前线程状态

        //state != Thread.State.TERMINATED  表示线程 不等于  线程停止(线程不停止)
        while (state != Thread.State.TERMINATED){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            state = thread.getState();
            System.out.println(state);    //每隔一秒打印一次线程状态
        }
    }
}


//打印结果
/*

NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
================
TERMINATED

 */

优先级

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2IQ1OxQ9-1649992930038)(F:\image\截屏\20210508195913713.png)]

package threadpriority;

//CPU不一定按优先级调度,但是优先级大的线程权重大
public class TestPriority {
    public static void main(String[] args) {
        //main()线程(主线程)  默认优先级为  5
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());

        Runnable priority = new MyPriority();

        Thread t1 = new Thread(priority,"t1");
        Thread t2 = new Thread(priority,"t2");
        Thread t3 = new Thread(priority,"t3");
        Thread t4 = new Thread(priority,"t4");
        Thread t5 = new Thread(priority,"t5");
        Thread t6 = new Thread(priority,"t6");

        t1.start();  // 不设置优先级,默认优先级为5

        t2.setPriority(10);   //设置优先级为10
        t2.start();

        t3.setPriority(1);    //设置优先级为1
        t3.start();

        t4.setPriority(8);    //设置优先级为8
        t4.start();

        t5.setPriority(3);    //设置优先级为3
        t5.start();

        //优先级等级只有[1,10],否则异常
        //t6.setPriority(0);
        //t6.start();
    }
}

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}

守护线程

  • 线程分为用户线程守护线程(daemon)
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待…
package threaddaemon;

public class TestDaemon {
    public static void main(String[] args) {
        //God为守护线程,用户现场结束守护线程也就结束了
        Thread thread = new Thread(new God());
        thread.setDaemon(true);   //设置为守护线程,默认是false(即用户线程)
        thread.start();   //守护线程启动
        //守护线程关闭需要一定的时间

        new Thread(new You()).start();   //用户线程启动

    }
}


class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝守护着你");
        }
    }
}

class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 365; i++) {
            System.out.println("你开心的度过了一年");
        }
        System.out.println("=======一年结束了======");
    }
}

4.线程同步

  • 并发:同一对象被多个线程同时操作(抢票例子)
  • 线程同步是一个等待机制,多个需要同时访问次对象的线程进入这个对象的等待池形成队列,等待前一个线程使用完毕,下一个线程才能使用。

形成条件:队列+锁

在这里插入图片描述

三大不安全案例

案例1.车站抢票:

package syn;

//模拟抢票,一个对象被多个线程操作,线程的并发问题   不安全
public class UnSafeBuyTickets {
    public static void main(String[] args) {    //主线程
        //一个对象被三个线程操作
        BuyTickets buyTickets = new BuyTickets();

        new Thread(buyTickets,"张三").start();
        new Thread(buyTickets,"李三四").start();
        new Thread(buyTickets,"可恶的黄牛党").start();
    }
}

//买票
class BuyTickets implements Runnable {
    //票数
    private int ticketnum = 10;

    //线程停止标志位
    private boolean flag = true;

    @Override
    public void run() {
        try {
            buyTickets();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void buyTickets() throws InterruptedException {
        //判断是否有票
        if (ticketnum <= 0) {
            System.out.println("没有票了");
            return;
        }

        //利用循环买票
        while (flag) {
            if (ticketnum <= 0) flag = false;    //线程停止标志位转换符
            Thread.sleep(100);    //线程睡眠,放大问题的发生下性
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketnum-- + "张票");
        }
    }
}


/*
多人同时拿到同一张票是因为每个线程在自己的工作内存交互,当多人同时看到一张票,就会出现张三,李四都抢到第10张票的结果
可恶的黄牛党拿到了第-1张票 是因为张三和黄牛党同时看到最后一张票,但是张三却先抢走了,系统内就剩下0张票,这时候黄牛党再拿一张票,系统内就会出现-1张票
 */

案例2.银行取钱

package syn;

//模拟银行取钱
/*
1.需要一个账户(属性有账户余额,账户名字)
2.需要一个银行(模拟取钱)
 */
public class UnSafeBank {
    public static void main(String[] args) {
        Account bankcard = new Account(50,"积蓄");

        Bank you = new Bank(bankcard,30,"你");
        Bank girlfriend = new Bank(bankcard,50,"女朋友");

        you.start();
        girlfriend.start();

    }
}

//定义一个账户
class Account {
    //账户余额
    int account_money;

    //账户名称
    String account_name;

    public Account(int account_money, String account_name) {
        this.account_money = account_money;
        this.account_name = account_name;
    }

}

//定义一个银行
class Bank extends Thread {
    //需要一个账户
    Account account;

    //取了多少钱
    int drawing_money;

    //手里现在有多少钱
    int now_money;

    public Bank(Account account, int drawing_money, String account_name) {
        super(account_name);
        this.account = account;
        this.drawing_money = drawing_money;
    }

    @Override
    public void run() {
        //判断钱够不够
        if (account.account_money - drawing_money < 0) {
            System.out.println(Thread.currentThread().getName() + "余额不足,取不了");
            return;
        }

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

        //账户余额
        account.account_money = account.account_money - drawing_money;

        //手里的钱
        now_money = now_money + drawing_money;

        System.out.println(account.account_name + "账户余额为:" + account.account_money);

        System.out.println(this.getName() + "取走了" + now_money);
    }
}

/*
问题:
出现账户余额为负数的情况
 */

案例3.不安全的集合

package syn;

import java.util.ArrayList;

//不安全的集合
public class UnSafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        
        System.out.println(list.size());
    }
}

同步方法

在这里插入图片描述

弊端:方法里需要修改的内容才需要锁,只读可以不用锁,锁太多会浪费资源

package syn.synchronizedmethods;

//模拟抢票,一个对象被多个线程操作,线程的并发问题   不安全
public class UnSafeBuyTickets {
    public static void main(String[] args) {    //主线程
        //一个对象被三个线程操作
        BuyTickets buyTickets = new BuyTickets();

        new Thread(buyTickets,"张三").start();
        new Thread(buyTickets,"李四").start();
        new Thread(buyTickets,"可恶的黄牛党").start();
    }
}

//买票
class BuyTickets implements Runnable {
    //票数
    private int ticketnum = 10;

    //设置线程停止标志位
    private boolean flag = true;

    @Override
    public void run() {
        //循环调用买票方法,模拟进行买票
        while (flag){
            try {
                buyTickets();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //用systhronized关键字实现线程同步方法,锁的是this,解决了线程的并发问题
    //public systhronsized 返回值类型 methods(参数){}    为同步方法
    public synchronized void buyTickets() throws InterruptedException {
        //判断是否有票
        if (ticketnum <= 0) {
            System.out.println("没有票了");
            this.flag = false;   让标志位反转,使线程停止
            return;
        }

        //线程睡眠,模拟网络延迟
        Thread.sleep(100);

        //模拟买票
        System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketnum-- + "张票");
    }
}





/*
多人同时拿到同一张票是因为每个线程在自己的工作内存交互,当多人同时看到一张票,就会出现张三,李四都抢到第10张票的结果
可恶的黄牛党拿到了第-1张票 是因为张三和黄牛党同时看到最后一张票,但是张三却先抢走了,系统内就剩下0张票,这时候黄牛党再拿一张票,系统内就会出现-1张票
 */
package syn.synchronizedmethods;

//模拟银行取钱
/*
1.需要一个账户(属性有账户余额,账户名字)
2.需要一个银行(模拟取钱)
 */
public class UnSafeBank {
    public static void main(String[] args) {
        Account bankcard = new Account(100,"积蓄");

        Bank you = new Bank(bankcard,50,"你");
        Bank girlfriend = new Bank(bankcard,100,"女朋友");

        you.start();
        girlfriend.start();

    }
}

//定义一个账户
class Account {
    //账户余额
    int account_money;

    //账户名称
    String account_name;

    public Account(int account_money, String account_name) {
        this.account_money = account_money;
        this.account_name = account_name;
    }

}

//定义一个银行
class Bank extends Thread {
    //需要一个账户
    Account account;

    //取了多少钱
    int drawing_money;

    //手里现在有多少钱
    int now_money;

    public Bank(Account account, int drawing_money, String account_name) {
        super(account_name);
        this.account = account;
        this.drawing_money = drawing_money;
    }

    @Override
    public void run() {
        //synchronized(Obj){}    为同步块
        //锁的对象是要变化的量,需要增删改的对象
        synchronized (account){
            //判断钱够不够
            if (account.account_money - drawing_money < 0) {
                System.out.println(Thread.currentThread().getName() + "想取钱,钱不够了,取不了");
                return;
            }

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

            //账户余额
            account.account_money = account.account_money - drawing_money;

            //手里的钱
            now_money = now_money + drawing_money;

            System.out.println(account.account_name + "账户余额为:" + account.account_money);

            System.out.println(this.getName() + "手里有" + now_money);
        }

    }
}



/*
问题:
出现账户余额为负数的情况
 */
package syn.synchronizedmethods;

import java.util.ArrayList;

//不安全的集合
public class UnSafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                //变化的对象是list
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }


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

        //打印集合长度
        System.out.println(list.size());
    }
}

JUC安全的集合类型 CopyOnWriteArrayList

package syn.synchronizedmethods;

import java.util.concurrent.CopyOnWriteArrayList;

// 测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }

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

        System.out.println(list.size());
    }
}

死锁

  • 多个线程各自占用一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方资源释放,都停止的情形。某一个同步块同时拥有两个以上对象的锁时,就可能会死锁。
package syn.deadLock;

//死锁:多线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0, "灰姑娘");
        MakeUp g2 = new MakeUp(1, "白雪公主");

        g1.start();
        g2.start();
    }
}

//口红
class Lipstick {
}

//镜子
class Mirror {
}

//化妆
class MakeUp extends Thread {
    //需要的资源只有一份,用static来保证只有一份
    //需要一支口红
    static Lipstick lipstick = new Lipstick();
    //需要一面镜子
    static Mirror mirror = new Mirror();

    //选择
    int choice;
    //使用者名字
    String girlName;

    public MakeUp(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //化妆的方法
    //互相持有对方的锁,就是需要拿到对方的资源
    public void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror) {
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(1000);
                synchronized (lipstick) {
                    System.out.println(this.girlName + "获得口红的锁");
                }
            }
        }
    }
}

在这里插入图片描述

package syn.deadLock;

//死锁:多线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0, "灰姑娘");
        MakeUp g2 = new MakeUp(1, "白雪公主");

        g1.start();
        g2.start();
    }
}

//口红
class Lipstick {
}

//镜子
class Mirror {
}

//化妆
class MakeUp extends Thread {
    //需要的资源只有一份,用static来保证只有一份
    //需要一支口红
    static Lipstick lipstick = new Lipstick();
    //需要一面镜子
    static Mirror mirror = new Mirror();

    //选择
    int choice;
    //使用者名字
    String girlName;

    public MakeUp(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //化妆的方法
    //互相持有对方的锁,就是需要拿到对方的资源
    public void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            }
            //把镜子的锁放到外面就可以避免这种情况
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(1000);
            }
            //把镜子的锁放到外面就可以避免这种情况
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
            }

        }
    }
}

Lock(锁)

  • 从JDK5.0开始,Java提供了更强大的同步线程机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
  • Lock接口是控制躲个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock加锁,线程开始访问资源前必须先获得Lock对象。
  • ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。

在这里插入图片描述

package syn.lock;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock1 {
    public static void main(String[] args) {
        BuyTickets buyTickets = new BuyTickets();

        new Thread(buyTickets).start();
        new Thread(buyTickets).start();
        new Thread(buyTickets).start();
    }
}

class BuyTickets implements Runnable {

    //定义一个锁
    private final ReentrantLock lock = new ReentrantLock();

    int ticketnums = 10;

    @Override
    public void run() {
        while (true) {
            try {
                //上锁
                lock.lock();
                if (ticketnums <= 0) {
                    break;
                } else {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketnums--);
                }
            } finally {
                //开锁
                lock.unlock();
            }
        }
    }
}

在这里插入图片描述

5.线程通信

在这里插入图片描述

这是一个线程同步问题,生产这与消费者共享同一个资源,他们之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又要马上通知消费者消费。
  • 对于消费者,消费之后,要通知生产者生产新的产品。
  • synchronized可阻止并发更新同一个共享资源,而不能实现不同进程之间消息的传递。

在这里插入图片描述

管程法

package communication.monitor;

//生产者消费者问题:管程法
public class Test {
    public static void main(String[] args) {
        SynContainter synContainter = new SynContainter();

        new Producter(synContainter).start();
        new Consumer(synContainter).start();
    }
}

//生产者
class Producter extends Thread {
    SynContainter containter;

    public Producter(SynContainter containter) {
        this.containter = containter;
    }

    //生产生产
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            containter.push(new Chicken(i));
            System.out.println("生产了" + i + "只鸡");
        }
    }
}

//消费者
class Consumer extends Thread {
    SynContainter containter;

    public Consumer(SynContainter containter) {
        this.containter = containter;
    }

    //消费者消费
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("消费了-->" + containter.pop().id + "只鸡");
        }
    }
}


//产品
class Chicken {
    int id;   //产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainter {
    //定义一个容器
    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();
    }

    //消费者消费产品
    synchronized Chicken pop() {
        //判断是否消费
        //如果容器中没有产品,那么消费者就要等待,然后通知生产者生产
        if (count == 0) {
            try {
                //消费者等待生产者生产
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //否则就让消费者消费
        count--;
        Chicken chicken = chickens[count];


        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

信号灯法

package communication.monitor;

//生产者消费者问题:信号灯法
public class Test2 {
    public static void main(String[] args) {
        TV tv = new TV();

        new Actor(tv).start();
        new Person(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 Person extends Thread{
    TV tv;

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

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.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 = !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 (当线程没有任务,保持多长时间终止)

在这里插入图片描述

package communication.monitor;

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

生产者消费者问题:线程池
public class Test3 {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //Executors.newFixedThreadPool(参数:线程池大小)       创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.关闭连接
        service.shutdown();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值