Java day18——线程

线程

并行:某时间点上A和B同时发生
并发:微观上的交替运行
线程的父类: Thread
程序: 安装的软件, 例如: QQ WeChat LOL…
进程: 在运行的程序
线程: 进程中多个同时在执行的任务
主方法程序运行就是打开了一个进程, 进程中至少存在一个线程 - 主线程

开启多线程任务

创建多个线程对象 Thread,并发的,有先后顺序
注意: 不是哪个线程先start, 就先执行哪个线程,线程的执行顺序, 是不固定的

自定义线程类

自定义线程类, 继承 Thread -> 重写run方法-> 创建线程对象 -> start() 开启线程

  • 特点:一个类只能有一个父类, 当他继承了Thread,这个类就只能是线程类, 有局限性
// 步骤1. 自定义的线程类, 继承 Thread
public class MyThread1 extends Thread {
    // 步骤2: 重写run方法, 线程要执行的任务
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("多线程执行了: " + i);
        }
    }
}

public static void main(String[] args) {
        // 开启多线程任务
        // 步骤3: 创建线程对象
        MyThread1 t = new MyThread1();
        // 步骤4: 开启线程
//        t.run(); // 错误写法
        t.start();
        // 主方法的主线程任务
        for (int i = 0; i < 10; i++) {
            System.out.println("main: " + i);
        }

自定义任务类

自定义任务类, 实现了Runnable接口 -> 重写run方法 -> 创建任务对象, 通过任务对象, 构造线程对象 -> start() 开启线程

  • 特点:类实现 Runnable 接口, 还可以继承其他的类,和其他的接口,功能扩展性比较强, 没有太多局限性
// 步骤1. 自定义的任务类, 实现 Runnable
public class MyThread2 implements Runnable {
    // 步骤2: 重写run方法, 线程要执行的任务
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("多线程执行了: " + i);
        }
    }
}

public static void main(String[] args) {
        // 开启多线程任务
        // 步骤3.1: 创建任务对象
        MyThread2 task = new MyThread2();
        // 步骤3.2: 通过任务对象, 构造线程对象
        Thread t = new Thread(task);
        // 步骤4: 开启线程
        t.start();
        // 主方法的主线程任务
        for (int i = 0; i < 10; i++) {
            System.out.println("main: " + i);
        }
    }

匿名内部类实现多线程

以上两种方式的匿名内部类改写

public static void main(String[] args) {
        Thread t = new Thread() {
            public void run() {
                Thread t = Thread.currentThread();//获得当前正在执行的线程对象
                for (int i = 0; i < 10; i++) {
                    System.out.println(t.getName() + ": " + i);
                }
            }
        };
        t.start();
        
        Runnable run = new Runnable() {
            public void run() {
                Thread t = Thread.currentThread();
                for (int i = 0; i < 10; i++) {
                    System.out.println(t.getName() + ": " + i);
                }
            }
        };
        Thread t1 = new Thread(run);
        t1.start();

构造方法和常用API

构造方法

1.new 自定义线程类(): 自定义类的构造方法, 随意

2.new Thread(): 无参构造器

3.new Thread(String): String->指定的线程名

4.new Thread(Runnable): Runnable->线程任务

5.new Thread(Runnable, String): Runnable->线程任务, String->指定的线程名

常用API

1.static Thread currentThread(): 获得当前正在执行的线程对象

2.String getName(): 获得线程对象的名字, 线程在创建时可以指定名字, 也可以默认分配名字

3.int getPriority(): 返回此线程的优先级
void setPriority(int): 设置线程的优先级

4.boolean isDaemon(): 测试这个线程是否是守护线程
void setDaemon(boolean): 设置这个线程是守护线程

5.static void sleep(long): 线程休眠指定时间
会有一个已检查异常, 所以必须要 try-catch

6.void join(): 等待调用这个方法的线程结束, 再继续后续代码
会有一个已检查异常, 所以必须要 try-catch

7.static void yield(): 主动放弃cpu的时间片

优先级和守护线程

  • 优先级: 1~10
    改变CPU分配时间片的概率,并不能改变先后顺序
    int getPriority(): 返回此线程的优先级
    void setPriority(int): 设置线程的优先级
Thread t1 = new Thread(run, "线程1");
        Thread t2 = new Thread(run, "线程2");
        Thread t3 = new Thread(run, "线程3");
        // 默认优先级, 都是5
        System.out.println("t1: " + t1.getPriority());
        System.out.println("t2: " + t2.getPriority());
        System.out.println("t3: " + t3.getPriority());

		t1.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(10); // 最大优先级,CPU分配的概率越大
        t1.start();
        t2.start();
        t3.start();
  • 守护线程 - 守护前台线程
    当所有的前台线程结束, 守护线程也会自动结束
    GC 就是守护线程
    boolean isDaemon(): 测试这个线程是否是守护线程
    void setDaemon(boolean): 设置这个线程是守护线程
public static void main(String[] args) {
        Runnable run1 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // 制造延迟, 让线程暂停一下
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("I will jump!");
                }
                System.out.println("aa a a a a a");
                System.out.println("噗通...!");
            }
        };

        Runnable run2 = new Runnable() {
            @Override
            public void run() {
                while (true) {//死循环
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("you jump, I jump!");
                }
            }
        };

        Thread rose = new Thread(run1);
        Thread jack = new Thread(run2);
        // 默认所有线程都不是守护线程
        System.out.println("jack是不是守护线程: " + jack.isDaemon());
        // 设置守护线程
        jack.setDaemon(true);

        rose.start();
        jack.start();//rose结束后,jack也跟着结束死循环
    }

线程同步的安全问题

多线程中资源共享 - 抢夺资源

synchronized

synchronized: 同步锁, 锁方法/代码块[借助对象],只能同时被一个线程持有,当线程执行完这个方法, 才会将锁释放
加到方法上, 同步方法锁
加到代码上, 借助对象, 通常是锁this对象,确保同步的线程, 对象共享即可
锁静态方法: 锁 类.class(类的字节码) 对象

// 模拟当前票的余量
public class Ticket {
    // 票的余量是100张
    public int count = 100;
    private Object object = new Object();
    
    public void saleTicket() {
//        synchronized (this) {
        synchronized (object) {
            if (count == 0) {
                throw new RuntimeException("票卖完了!");
            }
            System.out.println(Thread.currentThread().getName() + "正在出票: " + count);
            count--;
        }

        System.out.println(Thread.currentThread().getName()+"卖完一张票");
    }
}

public class MyThread extends Thread {
    private Ticket ticket;
    public MyThread(Ticket ticket, String name) {
        super(name);
        this.ticket = ticket;
    }

    public void run() {
        // 卖票
        while (ticket.count > 0) {
            ticket.saleTicket();
            try {
                Thread.sleep((long)(Math.random() * 90 + 10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 public static void main(String[] args) {
        // 100张票
        Ticket ticket = new Ticket();

        MyThread t1 = new MyThread(ticket, "窗口1");
        MyThread t2 = new MyThread(ticket, "窗口2");
        MyThread t3 = new MyThread(ticket, "窗口3");

        t1.start();
        t2.start();
        t3.start();
        //窗口1正在出票: 4
		//窗口1卖完一张票
		//窗口3正在出票: 3
		//窗口3卖完一张票
		//窗口1正在出票: 2
		//窗口1卖完一张票
		//窗口3正在出票: 1
		//窗口3卖完一张票
		//加锁就可以让最后一个线程结束后把锁拿走,其他线程就没办法进来了,这样子就不会出现票数为负数的情况
    }

在这里插入图片描述

lock锁

Lock - 接口
实现类: ReentrantLock lock = new ReentrantLock();
加锁: 锁对象.lock();
解锁: 锁对象.unlock();

// 模拟当前票的余量
public class Ticket {
    // 票的余量是100张
    public int count = 100;
    // 创建锁对象
    private ReentrantLock lock = new ReentrantLock();

    public void saleTicket() {
        // 加上锁
        lock.lock();

        if (count == 0) {
            throw new RuntimeException("票卖完了!");
        }
        System.out.println(Thread.currentThread().getName() + "正在出票: " + count);
        count--;

        // 打开锁
        lock.unlock();

        System.out.println(Thread.currentThread().getName() + "卖完一张票");
    }

线程状态

在这里插入图片描述

  1. new - 对象
  2. start() -> 就绪状态/可执行状态 Runnable
  3. cpu分配时间片 -> 运行状态 running
  4. run方法结束 -> 死亡状态/被终止
  5. run->就绪状态: 时间片到期/yield()
  6. run->阻塞状态
    1.锁阻塞: 同步锁
    2.计时等待: sleep(long) wait(long)
    3.无限等待: wait()
    唤醒: notify() notifyAll()
    4.a.join(): 调用这个方法的线程进入阻塞

Block阻塞状态图

在这里插入图片描述

线程通信

两个线程有共享数据, 线程之间有动作交互
notify() - 每次只能唤醒一个线程, 只能唤醒等待时间久的那个线程
notifyAll() - 唤醒所有正在等待的线程
wait() -> 只能被notify() 或者 notifyAll() 唤醒
wait(long) -> 到时间以后, 自动醒来

  • void wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 简单来说,就是让当前线程进入阻塞,直到被唤醒,并且会释放调用当前线程占用的对象锁
  • void notify() :唤醒在此对象监视器上等待的单个线程。 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。简单来说,就是唤醒被wait方法进入阻塞的线程。
  • sleep虽然也能使当前线程进入阻塞状态,但是是不会释放锁和资源的

案例:图片加载显示下载

1.线程1 先负责图片的加载任务. 1%~100% -> 加载完成
再负责图片的下载任务. 1%~100% -> 下载完成
要求图片显示完才能下载
2.线程2 负责图片的显示任务. 要求图片加载完才能显示

public class Picture {

    public boolean isLoad; // 标记图片是否加载完成

    public boolean isShow; // 标记图片是否限时完成

}
public class LoadPicture extends Thread {
    private Picture picture;
    public LoadPicture(Picture picture) {
        this.picture = picture;
    }

    public void run() {
        // 图片进入加载过程
        System.out.println("图片开始加载....");
        for (int i = 0; i < 100; i++) {
            System.out.println("正在加载: " + (i+1) + "%");
        }
        System.out.println("图片加载完成");
        // 设置图片状态为已加载完成
        picture.isLoad = true;

        // 图片要开始下载了 - 唤醒正在等待的"显示线程"
        synchronized (picture) {
            picture.notify();
        }
        // 等待图片显示完成
        if (!picture.isShow) {
            synchronized (picture) {
                try {
                    picture.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        // 假设图片已经限时完成
        System.out.println("图片开始下载....");
        for (int i = 0; i < 100; i++) {
            System.out.println("正在下载: " + (i+1) + "%");
        }
        System.out.println("图片下载完成");
    }
public class ShowPicture extends Thread {
    private Picture picture ;
    public ShowPicture(Picture picture ) {
        this.picture = picture;
    }

    public void run() {
        System.out.println("等待图片加载完成....");
        // 等待图片状态 isLoad = true
        // 当图片没有加载完成, 显示线程 等待
        if (!picture.isLoad) {
            synchronized (picture) {
                try {
                    picture.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        // 假设图片已经加载完成
        System.out.println("显示图片!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("图片显示完成!");
        // 改变图片显示状态
        picture.isShow = true;

        // 图片开始下载了 - 唤醒"下载线程"
        synchronized (picture) {
            picture.notify();
        }
    }
public class Demo {
    public static void main(String[] args) {
        Picture picture = new Picture();
        LoadPicture load = new LoadPicture(picture);
        ShowPicture show = new ShowPicture(picture);

        load.start();
        show.start();
    }
}
图片开始加载....
等待图片加载完成....
正在加载: 1%
正在加载: 2%
正在加载: 3%
正在加载: 4%
正在加载: 5%
正在加载: 6%
正在加载: 96%
正在加载: 97%
正在加载: 98%
正在加载: 99%
正在加载: 100%
图片加载完成
显示图片!
图片显示完成!
图片开始下载....
正在下载: 1%
正在下载: 2%
正在下载: 3%
正在下载: 4%
正在下载: 99%
正在下载: 100%
图片下载完成

案例:包子铺

包⼦铺线程⽣产包⼦,吃货线程消费包⼦。当包⼦没有时( 包⼦状态为false ),吃货线程等待,包⼦铺线程⽣产包⼦( 即包⼦状态为true ),并通知吃货线程( 解除吃货的等待状态 ),因为已经有包⼦了,那么包⼦铺线程进⼊等待状态。接下来,吃货线程能否进⼀步执⾏则取决于锁的获取情况。如果吃货获取到锁,那么就执⾏吃包⼦动作,包⼦吃完( 包⼦状态为false ),并通知包⼦铺线程( 解除包⼦铺的等待状态 ),吃货线程进⼊等待。包⼦铺线程能否进⼀步执⾏则取决于锁的获取情况。

public class BaoZi {
    String pier ;
    String xianer ;
    boolean flag = false ; // 包⼦资源 是否存在 包⼦资源状态
}
/*
消费者: 吃货 线程
  run: 1.判断包子状态false
           等待包子铺生产包子
       2.判断包子状态true
           吃包子 baozi.flag = false
           唤醒包子铺线程
 */
public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name, BaoZi bz) {
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true) {
            synchronized (bz) {
                if (bz.flag == false) { // 没包⼦
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包⼦");
                bz.flag = false;
                bz.notify();
            }
        }
    }
/*
生产者: 包子铺  线程
 run: 1.判断包子状态false
            生产包子 baozi.flag = true
            唤醒吃货线程, 来吃包子
      2.判断包子状态true
            不需要生产
            等待 wait
 */
public class BaoZiPu extends Thread{
    private BaoZi bz;

    public BaoZiPu(String name, BaoZi bz) {
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        int count = 0;
        // 造包⼦
        while (true) {
            // 同步
            synchronized (bz) {
                if (bz.flag == true) { // 包⼦资源 存在
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 没有包⼦ 造包⼦
                System.out.println("包⼦铺开始做包⼦");
                if (count % 2 == 0) {
                       // 冰⽪ 五仁
                    bz.pier = "冰⽪";
                    bz.xianer = "五仁";
                } else {
                    // 薄⽪ ⽜⾁⼤葱
                    bz.pier = "薄⽪";
                    bz.xianer = "⽜⾁⼤葱";
                }
                count++;
                bz.flag = true;
                System.out.println("包⼦造好了:" + bz.pier + bz.xianer);
                System.out.println("吃货来吃吧");
                // 唤醒等待线程 (吃货)
                bz.notify();
            }
        }
    }
public static void main(String[] args) {
        // 等待唤醒案例
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo("吃货", bz);
        BaoZiPu bzp = new BaoZiPu("包⼦铺", bz);

        ch.start();
        bzp.start();
    }
包⼦铺开始做包⼦
包⼦造好了:冰⽪五仁
吃货来吃吧
吃货正在吃冰⽪五仁包⼦
包⼦铺开始做包⼦
包⼦造好了:薄⽪⽜⾁⼤葱
吃货来吃吧
吃货正在吃薄⽪⽜⾁⼤葱包⼦
包⼦铺开始做包⼦
包⼦造好了:冰⽪五仁
吃货来吃吧
吃货正在吃冰⽪五仁包⼦
......死循环

线程池

Executors 工厂类中的方法,是⼀个容纳多个线程的容器,其中的线程可以反复使⽤,省去了频繁创建线程对象的操作,⽆需反复创建线程⽽消耗过多资源。

线程池种类

  • newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。

  • newFixedThreadPool(int nThreads): 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。

  • newScheduledThreadPool(int corePoolSize): 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。

  • newSingleThreadExecutor(): 创建一个使用从无界队列运行的单个工作线程的执行程序。

线程池开启线程任务的API

  • submit(Runnable/Callable)有返回值,提交指定的任务去执行并且返回Future对象
  • execute(Runnable)
public static void main(String[] args) {
        // 通过线程任务, 来获得线程对象并且直接开始线程
        Runnable run = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        // 创建一个固定线程数量的线程池, 创建好线程池的时候, 就已经有了三个线程对象
        ExecutorService pool = Executors.newFixedThreadPool(3);
        // 将线程任务交给线程池 -- 线程对象可以反复使用
        pool.execute(run);
        Future f = pool.submit(run); // f = null  ,submit(Runnable/Callable)有返回值,提交指定的任务去执行并且返回Future对象
        pool.submit(run);
        pool.submit(run);
        // 如果没有关闭线程池, 线程对象依然在, 程序就不会结束
        // 手动关闭线程池 - 会自动将里面的线程对象销毁
        pool.shutdown();

线程池的好处/why使用

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务。
  2. 提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
  3. 提⾼线程的可管理性。可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)。

Callable

Callable(线程任务, 只能用在线程池) -> Runnable
new Thread(new Runnable(){});
new Thread(new Callable(){}); // — 错误的!!

Callable对象只能在 : Future f = pool.submit(Callable);
f.get() -> 得到call方法的返回值
可能会遇到阻塞
f.get(long, TimeUnit.xx) -> 超时继续

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个固定线程数量的线程池, 创建好线程池的时候, 就已经有了三个线程对象
        ExecutorService pool = Executors.newFixedThreadPool(3);
        // 通过线程任务, 来获得线程对象并且直接开始线程
        Callable run = new Callable<Date>(){
            @Override
            public Date call() throws Exception{
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
                Thread.sleep(10000);
                return new Date();
            }
        };
        // 将线程任务交给线程池 -- 线程对象可以反复使用
        Future<Date> f = pool.submit(run);
        Date d = f.get(); // 得到Callable里面call方法的返回值
        System.out.println(d);

        /*try {
            Date date = f.get(3, TimeUnit.SECONDS);
            System.out.println(date);
        } catch (TimeoutException e) {
            System.out.println("结果超时了");
        }*/
        System.out.println("主线程继续");
        // 如果没有关闭线程池, 线程对象依然在, 程序就不会结束

        // 手动关闭线程池 - 会自动将里面的线程对象销毁
        pool.shutdown();
    }
  
pool-1-thread-1: 0
pool-1-thread-1: 1
pool-1-thread-1: 2
pool-1-thread-1: 3
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
pool-1-thread-1: 8
pool-1-thread-1: 9
Thu Jul 30 20:26:34 CST 2020
主线程继续

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值