学习笔记19.多线程

线程介绍

三种创建方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

Thread

创建线程方式一:继承thread类,重写run()方法,调用start开启线程。

不建议使用:避免OPP单继承局限性

创建示例:

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

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

        //创建一个线程对象
        TestThrea1 testThrea1 = new TestThrea1();

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

        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习多线程----"+i);
        }
    }
}

注意:线程,线程开启不一定立即执行,由cpu调度执行。

用Thread多线程写的一个图片下载器:

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() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://...","1.jpg");
        TestThread2 t2 = new TestThread2("https://...","2.jpg");
        TestThread2 t3 = new TestThread2("https://...","3.jpg");

        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异常,downloader方法出现问题");
        }
    }
}

Runnable

创建方法:

  1. 定义类并实现Runnable接口
  2. 实现run()方法
  3. 创建线程对象,调用start()方法启动线程

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

示例:

public static void main(String[] args) {
        //创建runnable接口的实现对象
        TestThread3 testThread3 = new TestThread3();

        //创建线程对象,通过线程对象来开启我们的线程 代理
        new Thread(testThread3).start();
}

龟兔赛跑:

public class Race implements Runnable{
    //胜利者

    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (Thread.currentThread().getName().equals("兔子")&& i%10 ==0 ){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果比赛结束,就停止程序
            //判断比赛是否结束
            boolean flag = gameOver(i);
            if (flag){
                break;
            }

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

    //判断是否完成比赛
    private boolean gameOver(int steps){
        //判断是否有胜利者
        if (winner!=null){//已经存在胜利者
            return true;
        }{
            if(steps==100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }

        }
        return false;
    }

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

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

Callable(了解)

创建方法:

  1. 实现Callable类
  2. 重写call方法

使用方法:

//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(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();
//关闭服务
ser.shutdownNow();

callable的好处:

  1. 可以定义返回值。
  2. 可以抛出异常。

静态代理模式

示例:

public class StaticProxy {
    public static void main(String[] args) {
        You you = new You(); //你要结婚
        WeddingCompany weddingCompany = new WeddingCompany(you);
        weddingCompany.HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{

    @Override
    public void HappyMarry() {
        System.out.println("秦老师要结婚了,超开心");
    }
}


//代理角色,帮助你结婚
class WeddingCompany implements Marry{

    //代理谁--》正式目标角色
    private Marry target;

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

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

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

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

总结:

  1. 真实对象和代理对象都要实现同一个接口
  2. 代理对象必须代理真实角色

好处:

  1. 代理对象可以做很多真实对象做不了的事情
  2. 真实对象专注做自己的事情

Lamda表达式

为什么要使用lambda表达式:

  • 避免匿名内部类定义过多
  • 可以让你的代码看起来更简洁
  • 去掉了一堆没有意义的代码,只留下核心的逻辑。

函数式接口的定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
  • 对于函数式接口,我们可以通过lambda表达式来创建改接口的对象。

实现函数接口方式的不断简化:

public class TestLambda1 {

    //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.用lambda简化
        like = () ->{
            System.out.println("i like lambda4");
        };
        like.lambda();
    }
}

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

//2.实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

lambda表达式也可以简化:

//1.lambda表达式简化
Ilove love = (int a) -> {
    System.out.println("i love you-->"+a);
};
//简化1.参数类型
love = (a) -> {
    System.out.println("i love you-->"+a);
};

//简化2.简化括号
love = a -> {
    System.out.println("i love you-->" + a);
};
//简化3.去掉花括号(前提是只有一行代码)
love = a -> System.out.println("i love you-->" + a);

love.love(521);

总结:

  1. lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就有代码块包裹(花括号)。
  2. 使用lambda表达式的前提是接口为函数式接口。
  3. 多个参数类型也可以去掉参数类型,要去掉就都去掉,必须加上括号。

线程状态

线程状态

停止线程

  1. 不推荐使用JDK提高的stop()、destroy()等过时或者JDK不建议的方法。
  2. 建议线程正常停止--->利用次数,不建议死循环
  3. 建议使用标志位

示例:

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==900){
                //调用stop方法切换标志位,让让线程停止
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

线程休眠

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

线程礼让

  1. 礼让线程,让当前正在执行的线程暂停,但不阻塞
  2. 将线程从运行状态转为就绪状态
  3. 让cpu重写调度,礼让不一定成功!看CPU心情

Join

  1. Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。(可以想象成插队)

线程状态观测

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

  • NEW

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

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

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

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

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

  • TERMINATED
    已退出的线程处于此状态。

可用thread.getState();来观测线程当前处于什么状态

线程优先级

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

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

  • MIN_PRIORITY = 1
  • NORM_PRIORITY = 5
  • MAX_PRIORITY = 10

使用以下方法改变线程优先级(优先级的设定建议在start()调度前:

  • setPriority(int xxx)

注意:优先级低只是意味着获得调度的概率低。并不是优先级低就不会被调用了。这都看CPU的调度

守护(daemon)线程

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

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

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

如:后台记录操作日志,监控内存,垃圾回收等待...

设置守护线程可用:thread.setDaemon(true)

线程同步

并发:同一个对象多个线程同时操作。

同步方法

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。

同步方法:public synchronized viod method(int args){}

同步方法缺陷:若将一个大的方法声明为synchronized将会大大影响效率。

注意:

  1. synchronized默认锁的是this。

同步块

同步块:synchornized(Obj) { }

Obj 称之为 同步监视器

  • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器,锁定对象就是变化的量
  • 同步方法中无需指定同步监视器,因为同步监视器就是this,就是这个对象本身,或者是class

死锁

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

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

Lock

通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

class A {
    private final ReentrantLock lock = new ReentrantLock();
    public void m() {
        lock.lock();
        try {
                //保证线程安全的代码
        }finally { 
            lock.unlock();
            //如果同步代码块有异常,要将unlock()写入finally语句块
        }
    
}

线程协作

线程通信

应用场景:生产者消费者问题

  • 生产者生产,消费者消费,两条线程之间可进行通信。

java提供了几个方法解决线程之间的通信问题:

  • wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会一直释放锁
  • wait(long timeout) 指定等待的毫秒数
  • notify() 唤醒一个处于等待状态的线程
  • notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意:都只能在同步方法或同步代码块中使用。

解决方式1:管程法

生产者:负责生产数据的模块

消费者:负责处理数据的模块

缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。

解决方式2:信号灯发

即用标志位控制线程的唤醒和停止。

补充:线程池

好处:

  • 提高响应速度(减小了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
public static void main(String[] args) {
    //1.创建服务,创建线程池
    //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();
}
posted @ 2021-04-13 21:12  Starumi  阅读(87)  评论(0)    收藏  举报