java学习之旅(day.19)

多线程

线程简介

多任务:同时做多件事

进程(Process):在操作系统中运行的程序就是进程,如QQ,播放器,游戏。

线程(Thread):一个进程可以有多个线程,如视频中同时听声音,看弹幕,看图像。一个进程中至少有一个线程

多线程:多条线路同时跑起来执行一些东西

线程的创建

Thread、 Runnable、 Callable

线程的三种创建方式

  1. Thread class 继承Thread类(重点)

将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例,即:

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
package com.zhang.Thread.demo01;
//创建线程方式一:继承Thread类,重写run()方法,调用start方法开启线程
//注意:线程开启不一定立即执行,由CPU调度执行

public class TestThread01 extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码---"+i);

        }
    }

    public static void main(String[] args) {
        //main线程,主线程

        //创建一个线程对象
        TestThread01 testThread01=new TestThread01();
        //调用start()方法开启线程
        testThread01.start();//这样写run方法和下面的语句(主线程)是交替(同时)执行的,但若改为testThread01.run(),则按顺序执行
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程"+i);

        }
    }
}

多线程网图下载

package com.zhang.Thread.demo01;

import org.apache.commons.io.FileUtils;

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

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

    //写一个构造器//  用构造器丢入url和name
    public TestThread02(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) {
        TestThread02 t1=new TestThread02("https://t2.chei.com.cn/axvert/img/1849288675.jpg","1.jpg");
        TestThread02 t2=new TestThread02("https://t1.chei.com.cn/axvert/img/2878021138.jpg","2.jpg");
        TestThread02 t3=new TestThread02("https://t2.chei.com.cn/axvert/img/2856626941.jpg","3.jpj");
        t1.start();
        t2.start();
        t3.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异常,doenloader方法出现问题");
        }
    }
}
  1. Runnable接口 实现Runnable接口(重点)

    声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。即:

    • 定义MyRunnable类实现Runnable接口
    • 实现tun方法,编写线程执行体
    • 创建线程对象,调用start()方法启动线程
    package com.zhang.Thread.demo01;
    //创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
    public class TestThread03 implements Runnable{
        @Override
        public void run() {
            //run方法线程体
            for (int i = 0; i < 20; i++) {
                System.out.println("我在看代码---"+i);
    
            }
        }
    
        public static void main(String[] args) {
            //创建一个Runnable接口的实现类对象
            TestThread03 testThread03=new TestThread03();
    
            //创建线程对象,通过线程对象开启线程(代理)
            Thread thread=new Thread(testThread03);//创建一个对象,把runnable接口的实现类的一个对象丢进来
    
            thread.start();//调用线程的start方法 上两行可简写为:new Thread(testThread03).start();
            for (int i = 0; i < 20; i++) {
                System.out.println("我在学习多线程"+i);
    
            }
        }
    
    }
    
    package com.zhang.Thread.demo01;
    
    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    
    //练习Thread,实现多线程同步下载图片
    //把之前写的继承Thread改为实现Runnable接口
    public class TestThread02gai implements Runnable{
        private String url;//网络图片地址
        private String name;//保存的文件名
    
        //写一个构造器//  用构造器丢入url和name
        public TestThread02gai(String url,String name){
            this.url=url;
            this.name=name;
        }
    //下载图片线程的执行体
        @Override
        public void run() {
            WebDownloader1 webDownloader=new WebDownloader1();
            webDownloader.downloader(url,name);//此刻就下载完了
            //打印
            System.out.println("下载了的文件名为:"+name);
        }
    
        public static void main(String[] args) {
            TestThread02gai t1=new TestThread02gai("https://t2.chei.com.cn/axvert/img/1849288675.jpg","1.jpg");
            TestThread02gai t2=new TestThread02gai("https://t1.chei.com.cn/axvert/img/2878021138.jpg","2.jpg");
            TestThread02gai t3=new TestThread02gai("https://t2.chei.com.cn/axvert/img/2856626941.jpg","3.jpg");
            new Thread(t1).start();
            new Thread(t2).start();
            new Thread(t3).start();
        }
    }
    
    
    //下载器
    class WebDownloader1{
        //下载方法
        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异常,doenloader方法出现问题");
            }
        }
    }
    

    继承Thread和实现Runnable的区别

    • 继承Thread类

      • 子类继承Thread类具备多线程能力
      • 启动线程:子类对象.start()
      • 不建议使用这种方法,避免OOP单继承局限性
    • 实现Runnable接口

      • 实现接口Runnable具有多线程能力
      • 启动线程:传入目标对象+Thread对象.start()
      • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

初识并发问题

package com.zhang.Thread.demo01;
//多个线程同时操作一个对象
//买火车票的例子

//运行完毕后发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱(多个人抢到了同一张票)
public class TestThread04 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) {
        //需要一个对象,三个线程
        TestThread04 ticket=new TestThread04();//对象

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

龟兔赛跑案例

  1. 首先来个赛道距离,然后要离终点越来越近
  2. 判断比赛是否结束
  3. 打印出胜利者
  4. 龟兔赛跑开始
  5. 故事中乌龟获胜,兔子需要睡觉,所以模拟兔子睡觉
  6. 最终,乌龟获胜
package com.zhang.Thread.demo01;
//模拟龟兔赛跑
public class Race implements Runnable{

    private static String winner;//使用static保证只有一个winner?
    @Override
    public void run() {//跑步
        for (int i = 0; i <=100; i++) {
            //模拟兔子睡觉
            if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
                try {
                    Thread.sleep(20);
                } 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();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

  1. Callable接口 实现Callable接口(现阶段了解)

实现Callable接口

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

import com.zhang.Thread.demo01.TestThread02;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

//线程创建方式三:实现Callable接口
//callable的好处
//1.可以定义返回值
//2.可以抛出异常
public class TestCallable implements Callable<Boolean> {//返回值
    private String url;//网络图片地址
    private String name;//保存的文件名

    //写一个构造器//  用构造器丢入url和name
    public TestCallable(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 Exception{
        TestCallable t1=new TestCallable("https://t2.chei.com.cn/axvert/img/1849288675.jpg","1.jpg");
        TestCallable t2=new TestCallable("https://t1.chei.com.cn/axvert/img/2878021138.jpg","2.jpg");
        TestCallable t3=new TestCallable("https://t2.chei.com.cn/axvert/img/2856626941.jpg","3.jpg");

        // 创建执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3);//new了一个池子,可以放3个线程

        //提交执行
        Future<Boolean>r1=ser.submit(t1);
        Future<Boolean>r2=ser.submit(t2);
        Future<Boolean>r3=ser.submit(t3);
        //获取结果
        Boolean rs1=r1.get();//此处要抛出异常
        Boolean rs2=r2.get();//此处要抛出异常
        Boolean rs3=r3.get();//此处要抛出异常
        //打印下返回值
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        //关闭服务
        ser.shutdownNow();
        //创建了一个服务,通过服务提交线程,获得线程的返回值,最后关闭服务
    }

}

//下载器
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异常,doenloader方法出现问题");
        }
    }
}

Lamda表达式

为什么要使用Lamda表达式?

  1. 避免匿名内部类定义过多
  2. 可以让代码看起来更简洁
  3. 去掉了一堆没有意义的代码,只留下核心的逻辑
  4. 其实质属于函数式编程的概念

理解Functional Interface(函数式接口)是学习java8 lambda表达式的关键所在

函数式接口的定义

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

public interface Runnable{

public abstract void run();

}

  • 对于函数式接口,可以通过lambda表达式来创建该接口的对象
package com.zhang.Thread.demo03;
//lambda表达式
public class TestLambda {
    public static void main(String[] args) {
        //可以利用接口创建实现对象
        ILike like=new Like();//创建一个接口对象,new一个实现类
        like.lambda();
    }
}
//定义一个函数式接口,
interface ILike{
    void lambda();
}
//实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("I like lambda");

    }
}

优化

package com.zhang.Thread.demo03;
//lambda表达式
public class TestLambda {
    //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();//创建一个接口对象,new一个实现类
        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//new的时候自动生成的
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };//这是一行语句
        like.lambda();


//6.用lambda()简化,只要5中方法后面的部分,接口和方法名间的都不要,拿过来加箭头
        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");

    }
}
//优化:可以把类放到里面去

一遍不行就再来一遍

package com.zhang.Thread.demo03;

public class TestLambda2 {
    //1.静态内部类
   static class Love1 implements ILove {
        @Override
        public void love(int a) {
            System.out.println("I love you" + a);
        }
    }

    public static void main(String[] args) {
        ILove love=new Love1();
        love.love(2);

     //2.局部内部类
        class Love implements ILove{
            @Override
            public void love(int a) {
                System.out.println("I love you"+a);
            }
        }
        //3.匿名内部类
       ILove love1= new ILove() {
           @Override
           public void love(int a) {
               System.out.println("I love you"+a);
           }
       };
        love1.love(3);
        //4.lambda表达式简化
        ILove love2=(int a)-> {
            System.out.println("I love you"+a);
        };
        love2.love(4);

        //5.再简化1,去掉参数类型
        love=(a)-> {
            System.out.println("I love you"+a);
        };
        love.love(5);
        //6.再简化2  简化括号
        love=a-> {
            System.out.println("I love you"+a);
        };
        love.love(6);
        //再简化3   去掉花括号
        love=a->System.out.println("I love you"+a);
        love.love(7);
//总结下:
        //1.lambda表达式只有一行代码的情况下才能简化为一行(再简化3),如果由多行,就用代码块包裹(加花括号)
        //2.前提是接口是函数式接口
        //3.多个参数也可以去掉参数类型(再简化1),要去参数类型,每个参数的参数类型都要去,必须加括号,如:int a  int b,去掉参数类型后写为(a,b)

    }

}
//定义一个接口
interface ILove{
    //里面要有一个方法
    void love(int a);//这次方法给个参数

}
//实现类
class Love implements ILove{
    @Override
    public void love(int a) {
        System.out.println("I love you"+a);
    }
}
//继续向上一个代码那样优化

静态代理模式

package com.zhang.Thread.demo03;
//静态代理模式总结
//1.真实(目标)对象和代理对象都要实现同一个接口
//2.代理对象要代理真实角色

//好处:
//1.代理对象可以做很多真实对象做不了的事情
//2.真实对象专注于做自己的事情就好了
//结婚实现
public class StaticProxy {
    public static void main(String[] args) {
        //最后加个开启线程
        new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println("爱吗");
            }
        }).start();

        //或者用lambda表达式来简化,因为runnable接口只有一个run方法
        new Thread(()-> System.out.println("爱吗")).start();//Thread类相当于WeddingCompany,是一个代理,代理中间的真实对象,真实对象是runable接口,调用一个start方法

       //代理,用婚庆公司去结婚
        /*//原来这样调
        You you=new You();
        you.HappyMarry();
         */
        //现在用代理来调
        //下面两行代码可以简写成这样
        new WeddingCompany(new You()).HappyMarry();
       //WeddingCompany weddingCompany=new WeddingCompany(new You());//这里的参数为结婚的真实对象 //WeddingCompany通过构造方法传递一个目标对象you
       // weddingCompany.HappyMarry();//用婚庆公司调用一个方法结婚,而不是you直接调方法,代理
    }
}
//共同的接口
interface Marry{
    void HappyMarry();
}
//需要有一个对象来实现结婚这个接口
//You 你是真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("要结婚了");
    }
}
//WeddingCompany  婚庆公司只是代理角色,帮助你结婚
class WeddingCompany implements Marry{
    private Marry target;//代理对象,真实对象要作为参数传给代理对象
    //构造方法传递参数

    public WeddingCompany(Marry target) {//真实目标角色通过此处传递过来
        this.target = target;
    }

    @Override
    public void HappyMarry() {

        //结婚前要干什么
        before();//alt+enter创建一个方法
        this.target.HappyMarry();//真实对象调用结婚//这个对象要结婚,真实角色为You,要把他传递过来
        after();//alt+enter创建一个方法
    }

    private void after() {
        System.out.println("结婚之后收尾款");
    }

    private void before() {
        System.out.println("结婚之前布置现场");
    }
}

线程状态

31

线程方法

setPriority(int newPriority) 更改线程的优先级

static void sleep (long millis) 在指定的毫秒数内让当前正在执行的线程休眠

void join() 等待该线程终止

static void yield() 暂停当前正在执行的线程对象,并执行其他线程

void interrupt 线程中断,不建议用这个

boolean isAlive() 测试线程是否处于活动状态

停止线程

不推荐使用JDK提供的stop() destroy() 方法(已废弃),推荐让线程自己停下来。

建议使用一个标志位进行终止变量 当flag=false,则终止线程运行

package com.zhang.Thread.demo03.state;
//测试停止线程
//1.建议线程正常停止,利用次数,不建议死循环
//2.建议使用标志位   设置一个标志位
//3.不要使用stop或者destroy等过时或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();//创建了一个Runnable实现对象

        new Thread(testStop).start();//将这个线程对象丢入线程,启动线程

        for (int i = 0; i < 1000; i++) {
            System.out.println("main" +i);
            if (i==900){
                testStop.stop();//调用stop方法,切换标志位,让线程停止,这里调用的stop方法不是JDK的,而是自己写的
                System.out.println("线程该停止了");
            }
        }

    }
}

线程休眠

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

模拟网络延时

package com.zhang.Thread.demo03.state;

import com.zhang.Thread.demo01.TestThread04;

//模拟网络延时:可以放大问题的发生性
public class TestSleep 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) {
        //需要一个对象,三个线程
        TestSleep ticket=new TestSleep();//对象

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

模拟倒计时

package com.zhang.Thread.demo03.state;
//模拟倒计时
public class TestSleep2 {
    public static void main(String[] args) {

        try {
            turnDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    //方法   模拟倒计时
    public static void turnDown() throws InterruptedException{
        int num=10;
        while (true){
            Thread.sleep(1000);//这里需要抛出异常
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
}

模拟更新当前时间

package com.zhang.Thread.demo03.state;

import com.zhang.GUI.Lesson06.Data;

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

//获取当前系统时间
public class TestSleep3 {
    public static void main(String[] args) {
        Date startTime=new Date(System.currentTimeMillis());//获取系统当前的时间

        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime=new Date(System.currentTimeMillis());//更新当前时间 没有这个更新时间的话,输出的一直是一个时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程礼让(yield)

礼让线程:让当前正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态。但是礼让不一定会成功,还是看CPU的心情

package com.zhang.Thread.demo03.state;
//测试礼让线程
//礼让不一定成功,还是看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield=new MyYield();

        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");  //Thread.currentThread().getName()得到线程的名字
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

线程强制执行join

join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。可看作插队

package com.zhang.Thread.demo03.state;
//测试join方法,当做插队
public class TestJoin 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{
        //启动线程
       TestJoin testJoin= new TestJoin();
       Thread thread=new Thread(testJoin);
       thread.start();
       
       
       
       //主线程
        for (int i = 0; i < 1000; i++) {
            if (i==200){
                thread.join();//需要抛出异常//插队  当i=200时,执行线程VIP,执行完再接着执行主线程
                System.out.println("main"+i);
            }
        }
    }
}

线程状态观察

Thread.state

  • new 新生状态

尚未启动的线程处于此状态

  • runnable 运行状态

在Java虚拟机中执行的线程处于此状态

  • blocked

被阻塞等待监视器锁定的线程处于此状态

  • waiting

正在等待另一个线程执行特定动作的线程处于此状态

  • timed waiting

正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

  • terminated

已退出的线程处于此状态

package com.zhang.Thread.demo03.state;
//观察测试线程的状态
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(1000);//这里要捕获异常
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("///////");
        });


        //观察状态
        Thread.State state = thread.getState();//thread.getState();返回的是一个状态,接收返回的对象,alt+enter可以把他的变量创建出来
        System.out.println(state);//NEW

        //观察启动后
        thread.start();
        //启动完后继续观察状态
        state=thread.getState();//不用每次都创建一个新的对象,节省空间
        System.out.println(state);//RUNNABLE

        //跑的时候有可能阻塞也有可能死亡,监听一下
        while (state!=Thread.State.TERMINATED){//只要线程不停止,就一直输出状态
            Thread.sleep(100);
            state=thread.getState();//更新线程的状态
            System.out.println(state);//输出状态 TERMINATED
        }


        //如果线程停止之后,再调用start是启动不了的,死亡之后的线程就不能再启动了
        //thread.start();
    }
}

线程的优先级(priority)

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

线程的优先级用数字表示,范围1-10,优先级高不一定先执行,还是看CPU心情,但是优先级高,执行的时候权重就高

先设置优先级再启动

package com.zhang.Thread.demo03.state;
//测试线程的优先级
public class TestPriority {
}
//来一个线程的类,然后实现runnable接口
class MyPriority implements Runnable{
    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);
        Thread t5=new Thread(myPriority);
        Thread t6=new Thread(myPriority);

        //先设置优先级再启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
        t4.start();

        //t5.setPriority(-1);
        //t5.start();报错

        //t6.setPriority(11);
        //t6.start();报错
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());//得到线程的名字和线程的优先级
    }
}

守护线程(daemon)

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不必等待守护线程执行完毕

package com.zhang.Thread.demo03.state;
//测试守护线程
//上帝守护你
public class TestDaemon {
    public static void main(String[] args) {
        God god=new God();
        You you=new You();

       //让God变成守护线程
       Thread thread=new Thread(god);
       thread.setDaemon(true);//返回值位布尔值,默认是false,表示用户线程,正常的线程都是用户线程
        thread.start();

        //启动You  用户线程
        new Thread(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 < 36500; i++) {
            System.out.println("你的一生都开心");
        }
        System.out.println("goodBye");
    }
}

线程同步机制

发生再多个线程操作同一个资源的时候

并发:同一个对象被多个线程同时操作(多个线程访问同一个对象)

处理多线程问题时,多个线程访问同一个对象,这时就需要线程同步,线程同步其实就是一种等待机制 排队,等前面的线程使用完毕,下一个线程再使用

每个对象都有一个锁,队列+锁才能保证线程同步的安全性。就像排队上厕所,不仅要排队,还要给厕所安装个锁

锁机制:synchronized

当统一进程的多个线程共享一块存储空间时,会发生冲突问题,为保证数据在方法种被访问时的正确性,访问时加入锁机制,当一个i小鹌鹑获得对习性的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。但存在下列问题

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 多线程竞争时,加锁,释放锁会导致较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引发性能问题

安全和性能不可兼得

三大不安全案例

买票

package com.zhang.Thread.demo03.demo04.synch;
//不安全的买票
//运行后发现线程不安全,有负数
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket ststion= new BuyTicket();

        new Thread(ststion,"小明").start();
        new Thread(ststion,"小张").start();
        new Thread(ststion,"小邢").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;//外部停止方式
    @Override
    public void run() {
        //买票
        while (true){
            buy();
        }
    }
    //买票的方法
    private void buy(){
        //买票先得判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //有票就买
        System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
    }
}

取钱

package com.zhang.Thread.demo03.demo04.synch;
//不安全的取钱
//两个人去银行取钱。账户
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"基金");//账户
//线程
        Drawing you = new Drawing(account,50,"你");
        Drawing girlfriend = new Drawing(account,100,"girlfriend");

        you.start();
        girlfriend.start();
    }
}
//账户
class Account{
    int money;//余额
    String name;//卡名
    //构造方法

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行:模拟取款
class Drawing extends Thread{
    Account account;//账户
    //取了多少钱
    int drawingMoney;
    //现在手里多少钱
    int nowMoney;
    //写一个构造方法
    public Drawing(Account account,int drawingMoney,String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

        //重写run方法
        //取钱
        @Override
        public void run() {
        //还是先判断有没有钱
            if (account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;//钱不够就退出
            }
            //模拟延时    sleep可以放大问题的发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //钱够的话就取
            account.money=account.money-drawingMoney;//卡内余额=余额-取掉的钱
            //现在你手里的钱
            nowMoney=nowMoney+drawingMoney;

            System.out.println(account.name+"余额为"+account.money);
            System.out.println(this.getName()+"手里的钱"+nowMoney);
            //这里this.getName()与Thread.currentThread().getName()是一样的
    }
}

集合

package com.zhang.Thread.demo03.demo04.synch;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合:ArrayList  某两个线程同一瞬间操作了同一位置,把两个数组添加到了同一个位置,就覆盖了。元素就少了,也就是不是一个萝卜一个坑,而是多个萝卜一个坑
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {//开启了10000条线程
            new Thread(()->{
                list.add(Thread.currentThread().getName());//名字添加到集合list中
            }).start();
        }
        System.out.println(list.size());//集合的大小  8480    期待值应为10000
    }
}

同步方法及同步机制

2.1

2.11

把之前写的三个改安全

package com.zhang.Thread.demo03.demo04.synch;
//不安全的买票
//运行后发现线程不安全,有负数

//改为安全的买票,只需在买票的方法buy那加关键字synchronized
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket ststion= new BuyTicket();

        new Thread(ststion,"小明").start();
        new Thread(ststion,"小张").start();
        new Thread(ststion,"小邢").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;//外部停止方式
    @Override
    public void run() {
        //买票
        while (true){
            buy();
        }
    }
    //买票的方法
    private synchronized void buy(){//此处加synchronized  同步方法 锁的对象是this(BuyTicket)
        //买票先得判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //有票就买
        System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
    }
}



====================================================
    package com.zhang.Thread.demo03.demo04.synch;
//不安全的取钱
//两个人去银行取钱。账户
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"基金");//账户
//线程
        Drawing you = new Drawing(account,50,"你");
        Drawing girlfriend = new Drawing(account,100,"girlfriend");

        you.start();
        girlfriend.start();
    }
}
//账户
class Account{
    int money;//余额
    String name;//卡名
    //构造方法

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行:模拟取款
class Drawing extends Thread{
    Account account;//账户
    //取了多少钱
    int drawingMoney;
    //现在手里多少钱
    int nowMoney;
    //写一个构造方法
    public Drawing(Account account,int drawingMoney,String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

        //重写run方法
        //取钱  synchronized默认锁的是this
        @Override
        public  void run() {//此处加synchronized 不行,锁run方法中this指的是Drawing,但是操作的增 删 改的对象不是Drawing,应该锁银行(改变的是account,应该锁account),而不是锁this,用同步块
        synchronized (account){//锁的是account,把代码丢到块里(方法丢到方法里,块就丢到块里)    锁的对象是变化的量(需要增 删  改的对象)
            //还是先判断有没有钱
            if (account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;//钱不够就退出
            }
            //模拟延时    sleep可以放大问题的发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //钱够的话就取
            account.money=account.money-drawingMoney;//卡内余额=余额-取掉的钱
            //现在你手里的钱
            nowMoney=nowMoney+drawingMoney;

            System.out.println(account.name+"余额为"+account.money);
            System.out.println(this.getName()+"手里的钱"+nowMoney);
            //这里this.getName()与Thread.currentThread().getName()是一样的

        }

    }
}
===========================================================================
    package com.zhang.Thread.demo03.demo04.synch;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合:ArrayList  某两个线程同一瞬间操作了同一位置,把两个数组添加到了同一个位置,就覆盖了。元素就少了,也就是不是一个萝卜一个坑,而是多个萝卜一个坑
public class UnsafeList {
    public static  void main(String[] args) {//锁list
        List<String>  list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {//开启了10000条线程
            new Thread(()->{
                synchronized (list){//锁完就好了
                    list.add(Thread.currentThread().getName());//名字添加到集合list中
                }

            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());//集合的大小  8480    期待值应为10000
    }
}

CopyOnWriteArrayList 这个集合是安全的

package com.zhang.Thread.demo03.state;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>(); // 集合都加泛型 这个list是线程安全的

        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try {
            Thread.sleep(3000);//要捕获异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁

谁都不让谁,两个线程都想要这个资源,但谁都不让谁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有两个以上对象的锁时,就可能发生死锁的问题

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放(拿了镜子还想拿口红,但我就不想把镜子释放给另一个人)
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(我想要你的,你想要我的)

只要破坏其中一个或多个条件,就能避免死锁发生

package com.zhang.Thread.demo03.state;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DemoLock {
    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;//使用化妆品的人

    //构造器
    Makeup(int choice, String girlName) {

        this.choice = choice;
        this.girlName = girlName;
    }

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

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

                synchronized (mirror) {//1秒后想获得镜子的锁
                    System.out.println(this.girlName + "获得镜子的锁");
//这是第一个人的情况,抱着口红的锁,想获得镜子的锁
                }
                /*
                synchronized (mirror) {//1秒后想获得镜子的锁
                    System.out.println(this.girlName + "获得镜子的锁");
//这是第一个人的情况,抱着口红的锁,想获得镜子的锁
                }
                 */
            }
        } else {
            synchronized (mirror) {//获得镜子的锁
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);

                synchronized (lipstick) {//2秒后想获得口红的锁
                    System.out.println(this.girlName + "获得口红的锁");
                }//另一个人持有镜子的锁,想获得口红的锁,这样两个人就僵持住了,程序就运行不了了  怎样才能不僵持,把想拥有的锁拿到代码块外就行,如/**/中的代码

            }
        }
    }
}

lock锁

  • 从JDK5.0开始,java提供了更强大的线程同步机制---通过显示定义同步锁对象实现同步,同步锁使用lock对象充当
  • java.util.concurrent.locks.lock接口是控制多个线程对共享资源访问的工具。锁提供了对共享资源的独占访问,每次只能由一个线程对lock对象进行加锁,线程开始访问资源之前应先获得lock对象
  • ReentrantLock(可重入锁)类实现了Lock,他拥有与synchrinized想听的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
package com.zhang.Thread.demo03.state;

import java.util.concurrent.locks.ReentrantLock;

//测试lock锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();//三个线程同时操作一个对象

    }
}

class TestLock2 implements Runnable{
    int ticketNums=10;

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


    @Override
    public void run() {
        while (true){

            try{
                lock.lock();//加锁
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            }finally {
                lock.unlock();//解锁

            }

        }

    }
}

synchronized与Lock的对比

  • lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
  • lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
  • 优先顺序

lock>同步代码块(已经进入了方法体,分配了相应的资源)>同步方法(在方法体之外)

线程协作

生产者消费者模式

生产者生生产,消费者消费,两条线程可以通信

2.2

2.21

2.22

管程法

解决方式1

2.23

package com.zhang.Thread.demo03.state;
//测试生产者消费者模型   利用缓冲区解决(管程法)

//需要的4个对象:生产者、消费者、缓冲区、产品
public class TestPC {
    public static void main(String[] args) {
       SynContainer container=new SynContainer();

       new Productor(container).start();
       new Consumer(container).start();
    }
}
//生产者
class Productor extends Thread{
    SynContainer container;//容器
    //构造器
    public Productor(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+"只鸡");
            }
        }
    }



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

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

//缓冲区
class SynContainer {
    //需要一个容器大小  数组
    Chicken[] chickens = new Chicken[10];//容器数组的容量为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];

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

}

解决方式2:

信号灯法 并发协作模型 判断一个标志位

package com.zhang.Thread.demo03.state;
//测试生产者消费者问题2:信号灯法,利用标志位解决
public class TestPc2 {
    public static void main(String[] args) {
        TV tv = new TV();

        new Player(tv).start();
        new Watcher(tv).start();


    }
}
//生产者  演员
class Player extends Thread {
    TV tv;//共有的对象



    public Player(TV tv) {
        this.tv = tv;
    }
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {//20小时的节目,一半节目一半广告
                if (i%2==0){
                   this.tv.play("我的小确辛播放中");
                }else{
                    this.tv.play("广告播放中");
                }


        }

    }

}
//消费者   观众
class Watcher extends Thread{
    TV tv;//共有的对象
    public Watcher(TV tv) {
        this.tv = tv;
    }

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

        }

    }
}
//产品  节目
class TV{
    //演员表演的时候,观众等待true
    //观众观看的时候,演员等待false

    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==true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了"+voice);
        //观看完后,通知演员表演
        this.notifyAll();
        this.flag=!this.flag;//切换标志位
    }
}

线程池

2.24

2.25

package com.zhang.Thread.demo03.state;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

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

//测试线程池
public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool 的参数为线程池的大小
        ExecutorService service= Executors.newFixedThreadPool(10);

         //把线程丢进去
        service.execute(new MyThread());//把runnable实现类丢进去
        service.execute(new MyThread());//把runnable实现类丢进去
        service.execute(new MyThread());//把runnable实现类丢进去
        service.execute(new MyThread());//把runnable实现类丢进去

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

    }

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

Thread创建总结

package com.zhang.Thread.demo03.state;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//总结线程的创建
public class TestThread {
    public static void main(String[] args) {
        //启动
        //1
        new Thread().start();
        //2需要一个代理类
        new Thread(new MyThread2()).start();
        //3
        FutureTask<Integer> futureTask=new FutureTask<Integer>(new MyThread3());//了解
        new Thread(futureTask).start();

          //futureTask可以返回值的
        try {
            Integer integer = futureTask.get();//获得返回值
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }
}
//1.继承Thread
class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("mythread1");
    }
}
//2.实现Runnable接口
class MyThread2 implements Runnable{
    @Override
    public void run() {
        System.out.println("amthread2");
    }
}
//3.实现callable接口
class MyThread3 implements Callable<Integer>{//这里定义的返回值和call方法中的返回值类型一致
    @Override
    public Integer call() throws Exception {
        System.out.println("mythread3");
        return 100;
    }
}
posted on 2021-02-02 11:28  懵逼的程序小白  阅读(111)  评论(0)    收藏  举报