Java多线程

Java多线程

什么是进程?

电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。在这里插入图片描述

什么是线程?

进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

  • 进程是程序执行的一次过程,它是一个动态的概念。是系统资源分配的单位。
  • 通常在一个进制中可以包含多个线程,线程是CPU调度和执行的单位
  • 多线程是模拟出来的,在一个CPU下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以有同时执行的错觉
  • main()被称为主线程,为系统入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器调度,调度器与操作系统紧密相关的,先后顺序是不能人为干预。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如cpu调度时间,并发开销控制
  • 每个线程在自己的工作内存交互,内存控制不会造成数据不一致

线程的创建

Thread. Runnable .Callabele

三种创建方式

  • Thread class 继承 Thread 类

  • Runnable 接口 实现Runnable 接口

  • Callable 接口 实现Callable 接口

  • 自定义线程类继承 Thread类

  • 重写Run()方法,编写线程执行体

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

package Thread;

//线程开启,不一定立即执行,由cpu调度
//创建线程方式一 : 继承Thread类,重写run()方法,调用 start 开启 线程
public class TestThread extends Thread {
    @Override
    public void run() {
       //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("你好呀"+i);
        }
    }

    public static void main(String[] args) {
        //开启 Thread线程
        TestThread testThread = new TestThread();
        //调用start方法
        testThread.start();
        //main 线程,主线程
        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习多线程--"+i);
        }
    }
}

案例:下载图片

package Thread;

import org.apache.commons.io.FileUtils;

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

//练习Thread, 实现多线程同步下载图片
public class TestThread extends Thread {

    private String url;//网络图片地址
    private String name;//网络图片保存地址

    public TestThread(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) {
        TestThread testThread1 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "1.jpg");
        TestThread testThread2 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "2.jpg");
        TestThread testThread3 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "3.jpg");

        testThread1.start();
        testThread2.start();
        testThread3.start();
    }
}
//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO 异常,dwonloader方法出现问题");
        }
    }

}

实现Runnable

  • 定义MyTunnable类实现Runnable接口
  • 实现Run()方法,编写线程执行体
  • 创建线程执行体,调用start()方法启动
package Thread;

public class TestThread3 implements Runnable{
    @Override
    public void run() {
        //run方法体
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码---"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口实现类对象
        TestThread3 testThread3 = new TestThread3();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        //把 runnable的接口对象 丢进去
 //     Thread thread = new Thread(testThread3);
        //threa 调用 start
 //     thread.start();

        new Thread(testThread3).start();
        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习!!"+i);
        }

    }
}

建议使用 Runnable

避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用

//一份资源
StratThread station = new StartThread();
//多个代理
new Thread(Startion,"小明").start();
new Thread(Startion,"小红").start();
new Thread(Startion,"小强").start();

并发问题

多个线程同时操作 同一个对象 有一个问题 : 发生了数据不安全 数据紊乱

抢火车票的例子

package Thread;

import static java.lang.Thread.sleep;

//多个线程同时操作 同一个对象  有一个问题  : 发生了数据不安全 数据紊乱
//抢火车票模拟
public class TesrThread4 implements Runnable{


    //票书
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            //制造延时
            try {
                sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
        }
    }

    public static void main(String[] args) {
        TesrThread4 ticket = new TesrThread4();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛党").start();
    }

}

输出结果:
小明拿到了第10张票
老师拿到了第9张票
黄牛党拿到了第10张票
小明拿到了第8张票
黄牛党拿到了第7张票
老师拿到了第8张票
小明拿到了第6张票
黄牛党拿到了第4张票
老师拿到了第5张票
老师拿到了第3张票
小明拿到了第1张票
黄牛党拿到了第2张票

案例 龟兔赛跑

  • 需要赛道 , 越跑距离终点越近

  • 判断比赛是否结束

  • 打印出胜利者

  • 龟兔赛跑开始

  • 乌龟赢,兔子需要睡觉

  • 乌龟赢了比赛

package Thread;

public class Race implements Runnable{

    //胜利者
    private static 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+"步");
        }
    }

    //判断 是否 完成比赛
    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;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }

}

输出结果:
兔子-->跑了68步
兔子-->跑了69步
兔子-->跑了70步
兔子-->跑了71步
兔子-->跑了72步
兔子-->跑了73步
兔子-->跑了74步
乌龟-->跑了91步
兔子-->跑了75步
兔子-->跑了76步
兔子-->跑了77步
兔子-->跑了78步
兔子-->跑了79步
兔子-->跑了80步
乌龟-->跑了92步
乌龟-->跑了93步
乌龟-->跑了94步
乌龟-->跑了95步
乌龟-->跑了96步
乌龟-->跑了97步
乌龟-->跑了98步
乌龟-->跑了99步
Winner is 乌龟
兔子-->跑了81步

静态代理

举办 婚礼

  • 人 婚礼的主体
  • 婚庆公司 代理承办婚礼
  • 婚礼 去实现婚礼
package Thread;

//静态代理模式总结
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色

//好吃 :
//代理对象可也做很多代理对象做不了的事情
//真实对象做自己的事情
public class StaticProxy {
    public static void main(String[] args) {
        WeddingComany weddingComany = new WeddingComany(new You());
        weddingComany.HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}

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

//代理角色 承办结婚的机构
class WeddingComany implements Marry{

    //真实目标角色
    private Marry target;

    public WeddingComany(Marry You) {
        this.target = You;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//这就是真实对象。
        after();

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

    public void after(){
        System.out.println("打扫干净");
    }


}
输出结果:
布置现场
我要结婚了,超开心
打扫干净 

Lamda 表达式

函数式接口定义

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

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

无参数

package Thread;
//推导Lambda 表达式
public class Lambda {

    //3 静态内部类
    static class Like2 implements Ilike{
        @Override
        public void lambda() {
            System.out.println("I like Lambda2");
        }
    }

    public static void main(String[] args) {
        Ilike like = new Like();
        like.lambda();

        //静态内部类
        like = new Like2();
        like.lambda();

        //4 局部内部类
        class Like3 implements Ilike{
            @Override
            public void lambda() {
                System.out.println("I like Lambda3");
            }
        }

        like = new Like3();
        like.lambda();

        //5匿名内部类,没有 类的名称 ,必须借助 接口 或者 父类   
        like = new Ilike() {
            @Override
            public void lambda() {
                System.out.println("I like Lambda4");
            }
        };
        like.lambda();

        //6 用 lamdba 简化  只有一个 接口 和 一个 方法 所有 中 lambda 默认 找到方法
        like = ()-> {System.out.println("I like Lambda5");};
        like.lambda();

    }

}
//1定义一个函数式接口  只有一个接口
interface Ilike{
    void lambda();
}

//2实现 接口 实现 他的方法
class Like implements Ilike{
    @Override
    public void lambda() {
        System.out.println("I like Lambda");
    }
}
输出
I like Lambda
I like Lambda2
I like Lambda3
I like Lambda4
I like Lambda5

有参数

package Thread;

public class Lambda_CanShu {

    public static void main(String[] args) {

        ILove love = (int a)-> {
                System.out.println("i love"+a);
            };

        //1Lambda 表达式简化 去掉 int 多个参数 也可以都去掉 但是必须加上括号。
        love = (int a)-> {
            System.out.println("i love"+a);
        };
        //2Lambda简化    去掉括号 
        love =a -> {
            System.out.println("i love"+a);
        };
        //3 Lambda 简化  去掉花括号
        love = a-> System.out.println("i Love u ");
        love.love(1);
    }
}
//接口必须为函数式接口   : 接口只有一个方法
interface ILove{
    void love(int a);
}

线程的五大状态

停止进程

  • 不推荐使用JDK停供stop(),destory() 方法【已废弃】
  • 推荐线程自己停止下来
  • 建议使用一个标志位终止变量 当Flag = false 则终止程序运行
package Thread;
//测试 stop
//1 建议 线程正常 停止, 利用次数,不建议死循环
//2 建议使用标志位  设置 一个 标志位
//3 不要使用 stop 或者 destory 等 过时或者 JDK不建议使用的方法

public class TestStop implements Runnable{

    // 1.设置一个标志位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run----Thread"+ i++);
        }
    }

    // 2. 设置一个公开的方法停止进程,转换标志位
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();

        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main" + i);
            if(i == 500){
                //调用stop()方法
                testStop.stop();
                System.out.println("该线程停止");
                }

        }
    }
}

线程休眠

  • sleep(毫秒)

  • 存在 InterruptException 中断异常

  • sleep时间到达后 线程进入就绪状态

  • sleep可以模拟网络延时 倒计时等

  • 每个对象都有一个锁,sleep不会释放锁

  • 放大问题的发生性

package Thread;

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

//模拟网络 延时  : 放大问题的发生性
public class TestSleep {
    public static void main(String[] args) throws InterruptedException {
        //tenDown();倒计时
        //打印当前时间
        Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间

        while (true){
            Thread.sleep(1000);
             startTime = new Date(System.currentTimeMillis());//刷新当前时间
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));

        }
    }

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

}

Join

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
  • 少用 join 会让线程堵塞
package Thread;

import javax.sound.midi.Soundbank;

//测试Join方法  想象成 插队
public class Testjoin implements Runnable{
    public static void main(String[] args) throws InterruptedException {

        //启动我们的线程
        Testjoin testjoin = new Testjoin();
        Thread thread = new Thread(testjoin);
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            if(i==200){
                thread.join();//插队
            }
            System.out.println("main"+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程VIP来了"+i);
        }
    }
}

观测线程状态

  • new
    • 新生
  • runnable
    • 就绪(虚拟机)
  • blocked
    • 阻塞
  • waitting
    • 等待另一个线程的特定动作
  • Timed_watting
    • 等待另一个线程执行完
  • terminated
    • 已退出的线程处于此状态 不能被再次 start()
观测线程状态
package Thread;

//观测线程状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(500);//睡觉5秒 线程一直等待
                } 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);// Run 跑起来了

        //只要线程不中止就一直输出状态
        while (state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();//更新线程的状态
            System.out.println(state);//输出状态

        }
        
        thread.start();//会报错 ,停止后的线程 不能继续开始 

    }

}

输出结果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING


///////
TERMINATED

线程优先级

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

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

    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 1;
    • Thread.NORM_PRIORITY = 1;
  • 使用以下方法改变或获取优先级

    • getPriority()
    • setPriority()
package Thread;

//测试线程优先级
public class TestPriority extends Thread{
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        //先设置优先级再启动
        //t1 给个默认优先级
        t1.start();
        t2.setPriority(1);
        t2.start();
        t3.setPriority(8);
        t3.start();
        t4.setPriority(9);
        t4.start();
        
    }
}

class MyPriority implements Runnable{
    @Override
    public void run() {
        //线程名字+线程优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}


输出
    main-->5
Thread-0-->5
Thread-1-->1
Thread-3-->9
Thread-2-->8

守护(deamon)线程

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

posted @ 2020-12-25 01:06  AronJudge  阅读(135)  评论(0)    收藏  举报