Java多线程

线程

 

线程简介

  • 线程就是独立的执行路径;

  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程, gc线程;

  • main()称之为主线程,为系统的入口,用于执行整个程序;

  • 在一-个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。

  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;

  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程实现

线程的创建

Thread,Runnable,Callable

三种创建方式

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

  2. Runnable 接口 实现Runnable接口(最重点)

  3. Callable 接口 实现Callable接口(了解)

Thread

  • 自定义线程类继承Thread类

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

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

 //创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
 //总结:注意,线程开启不一定立即执行,由cpu调度安排
 public class TestThread extends Thread{
     public void run(){
         //run方法线程体
         for(int i=0;i<20;i++)
        {
             System.out.println("我在看代码"+i);
        }
    }
     public static void main(String[] args) {
         //main线程,主线程
 
         //创建一个线程对象
         TestThread testThread1=new TestThread();
         //调用start()方法开启线程
         testThread1.start();
 
         for (int i = 0; i <20 ; i++) {
             System.out.println("我在学习多线程"+i);
        }
    }
 }

案例,多线程下载网络图片。先导入commoms.io.2.2.jar包

 //联系Thread,实现多线程同步下载图片
 public class TestThread2 extends Thread{
 
     public String url;    //网络图片地址
     public String name;    //保存的文件名
 
     public TestThread2(String url,String name){
         this.name=name;
         this.url=url;
    }
 
     public void run(){
 
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloaders(url,name);
         System.out.println("下载了文件名为:"+name);
    }
 
     public static void main(String[] args) {
         TestThread2 t1=new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596602548536&di=ab2a692542bb5cab6ab360ee60a66c25&imgtype=0&src=http%3A%2F%2F02.imgmini.eastday.com%2Fmobile%2F20180529%2F20180529144003_e08eaf9309c9bb92ec5a0b7e376bd21a_2.jpeg","gakki1.jpeg");
         TestThread2 t2=new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596602816858&di=4dd1739ba7c20c4be9e57a2fa3ac499b&imgtype=0&src=http%3A%2F%2F02imgmini.eastday.com%2Fmobile%2F20181106%2F20181106170917_96c0333f9e103b4b1abc3ec6ab20fdbd_1.jpeg","gakki2.jepg");
         TestThread2 t3=new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596602759994&di=7786381e0cecddf8d082218b068f9eb1&imgtype=0&src=http%3A%2F%2Fi1.hdslb.com%2Fbfs%2Farchive%2F213205624b29002ace7d11f2c546016400c10750.png","gakki3.jepg");
 
         t1.start();
         t2.start();
         t3.start();
    }
 }
 
 //下载器
 class WebDownloader{
     public void downloaders(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,download方法出现问题");
        }
    }
 }

实现Runnable

  • 定义MyRunnable类实现Runnable接口

  • 实现run()方法,编写线程执行体

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

 package com.zhou.demo1;
 
 //创建线程方式二:实现runnable接口,重写run方法,执行需要丢入runnable接口实现类,调用start方法。
 public class TestThread3 implements Runnable{
     public void run(){
         //run方法线程体
         for(int i=0;i<20;i++)
        {
             System.out.println("我在看代码"+i);
        }
    }
 
     public static void main(String[] args) {
         //main线程,主线程
 
         //创建runnable接口的实现类对象
         TestThread3 testThread3=new TestThread3();
         //创建线程对象,通过线程对象来开启我们的线程
         Thread thread=new Thread(testThread3);
         //调用start()方法开启线程
         thread.start();
 
         for (int i = 0; i <20 ; i++) {
             System.out.println("我在学习多线程"+i);
        }
    }
 }

小结

  • 继承Thread类

    • 子类继承Thread类具备多线程能力

    • 启动线程:子类对象.start()

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

  • 实现Runnable接口

    • 实现接口Runnable具有多线程能力

    • 启动线程:传入目标对象+Thread对象.start()

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

认识并发问题

 package com.zhou.demo1;
 
 import java.security.spec.RSAOtherPrimeInfo;
 
 //多个线程同时操作同一个对象
 //买火车票的例子
 //发现问题:多个线程操作统一个资源时,不安全状态.可能会出现内存修改问题
 public class TestThread4 implements Runnable{
 
     //票数
     private int ticketNums=10;
     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) {
         TestThread4 testThread4=new TestThread4();
         new Thread(testThread4,"小明").start();
         new Thread(testThread4,"老师").start();
         new Thread(testThread4,"黄牛党").start();
    }
 }

案例:龟兔赛跑,

 package com.zhou.demo1;
 
 //模拟龟兔赛跑
 public class Race implements Runnable{
     //胜利者
     private static String winner;
     public void run(){
         for (int i = 0; i <=100 ; i++) {
             //模拟兔子休息
             if(Thread.currentThread().getName().equals("兔子")&&i%10==0)
            {
                 try {
                     Thread.sleep(1);
                } 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接口(了解即可)

  • 实现Callable接口,需要返回类型

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

  • 创建目标对象

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

  • 提交执行:Future<Boolean> result1=ser.submit(t1);

  • 获取服务:boolean r1=result1.get()

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

 package com.zhou.demo02;
 
 
 import com.zhou.demo1.TestThread2;
 import org.apache.commons.io.FileUtils;
 
 import java.util.concurrent.*;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 
 //线程创建方式三:实现Callable接口
 /*
 Callable的好处
 1.可以定义返回值
 2.可以抛出异常
 * */
 public class TestCallable implements Callable<Boolean> {
 
     public String url;    //网络图片地址
     public String name;    //保存的文件名
 
     public TestCallable(String url,String name){
         this.name=name;
         this.url=url;
    }
 
     //下载图片的线程执行体
     public Boolean call(){
 
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloaders(url,name);
         System.out.println("下载了文件名为:"+name);
         return true;
    }
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         TestCallable t1=new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596602548536&di=ab2a692542bb5cab6ab360ee60a66c25&imgtype=0&src=http%3A%2F%2F02.imgmini.eastday.com%2Fmobile%2F20180529%2F20180529144003_e08eaf9309c9bb92ec5a0b7e376bd21a_2.jpeg","gakki1.jpeg");
         TestCallable t2=new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596602816858&di=4dd1739ba7c20c4be9e57a2fa3ac499b&imgtype=0&src=http%3A%2F%2F02imgmini.eastday.com%2Fmobile%2F20181106%2F20181106170917_96c0333f9e103b4b1abc3ec6ab20fdbd_1.jpeg","gakki2.jpeg");
         TestCallable t3=new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596602759994&di=7786381e0cecddf8d082218b068f9eb1&imgtype=0&src=http%3A%2F%2Fi1.hdslb.com%2Fbfs%2Farchive%2F213205624b29002ace7d11f2c546016400c10750.png","gakki3.png");
 
         //创建执行服务:   参数:创建线程数
         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();
    }
 
 }
 //下载器
 class WebDownloader{
     public void downloaders(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,download方法出现问题");
        }
    }
 }

静态模式

 

Lamda表达式

image-20200805115342793

  • 为什么要使用lambda表达式

    • 避免匿名内部类定义过多

    • 可以让你的代码看起来很简洁

    • 去掉了一堆没有意义的代码,只留下核心的逻辑。

  • 也许你会说,我看了Lambda表达式,不但不觉得简洁,反 而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多 了,看习惯了,就好了。

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

  • 函数式接口的定义:

    • 任何接口,如果只包含唯一一个抽象方法, 那么它就是-个函数式接口。 public interface Runnable { public abstract void run()

      }

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

实现:

 package com.zhou.lambda;
 
 /*
 * 推导lambda表达式*/
 public class TestLambda1 {
     //3.静态内部类
     static class Like2 implements ILike{
         public void lambda(){
             System.out.println("I like lambda2");
        }
    }
 
 
     public static void main(String[] args) {
         ILike iLike = new Like();
         iLike.lambda();
 
         
         iLike=new Like2();
         iLike.lambda();
 
         //4.局部内部类
         class Like3 implements ILike{
 
             public void lambda(){
                 System.out.println("I like lambda3");
            }
        }
         iLike=new Like3();
         iLike.lambda();
 
         //5.匿名内部类,没有类的名称,必须借助接口或父类
         iLike=new ILike() {
             public void lambda()
            {
                 System.out.println("I like lambda4");
            }
        };
         iLike.lambda();
 
         //6.用lamdba简化
         iLike = ()->{
             System.out.println("I like lambda5");
        };
         iLike.lambda();
    }
 }
 
 //1.定义一个函数式接口
 interface ILike{
     void lambda();
 }
 
 //2.实现类
 class Like implements ILike{
 
     public void lambda(){
         System.out.println("I like lambda");
    }
 }

总结:

  • lambda表达式只能有一行代码的情况下才就能简化为一行,如果有多行,那么就用代码块包裹。

  • 前提是接口为函数式接口

  • 多个参数也可以去掉参数类型

 

线程状态

五大状态

image-20200805144640737

image-20200805144730740

线程方法

image-20200805144931356

停止线程

  • 不推荐使用JDK提供的stop(),destory()。【已废弃】

  • 推荐县城自己停止下来。

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

 package com.zhou.state;
 
 //测试stop
 //1.建议线程正常停止-->利用次数,不建议死循环
 //2.建议使用标志位-->设置一个标志位
 //3.不要使用stop或者destory等过时的方法
 public class TestStop implements Runnable{
 
     //1.设置标志位
     private boolean flag=true;
 
     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("线程该停止了");
            }
        }
 
    }
 }

 

线程休眠

  • sleep(时间)指定当前线程堵塞的毫秒数;1000ms=1s

  • sleep存在异常InterruptedException;

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

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

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

package com.zhou.state;
import java.text.SimpleDateFormat;
import java.util.Date;

//模拟网络延时:放大问题的发生性
public class TestSleep {

    public static void main(String[] args) {
        Date startTime=new Date(System.currentTimeMillis());//获取系统当前时间
        while(true){
            try {
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime=new Date(System.currentTimeMillis());//获取系统当前时间
        }
    }
}

 

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不堵塞

  • 将线程从运行状态转为就绪状态

  • 让CPU重新调度,礼让不一定成功!看CPU心情

package com.zhou.state;

//测试礼让线程
//不一定成功
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{

    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

Join

  • Join合并线程,代此线程执行完成后,再执行其他线程,其他线程堵塞

  • 可以想象成插队

package com.zhou.state;

public class Join implements Runnable{

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

    public static void main(String[] args) {
        //启动我们的线程
        Join jion=new Join();
        Thread thread = new Thread(jion);
        thread.start();

        //主线程
        for (int i = 0; i < 1000; i++) {
            if(i==100) {
                try {
                    thread.join();   //插队
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("主线程"+i);
        }
    }
}

线程状态观测

Thread State

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

  • NEW

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

  • RUNNABLE

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

  • BLOCKED

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

  • WAITING

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

  • TERMINATED

    已退出的线程处于此状态

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

package com.zhou.state;

//观察测试线程的状态
public class TestState {
    public static void main(String[] args) {
        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();
        System.out.println("state = " + state);

        //观察启动后
        thread.start();   //启动线程
        state=thread.getState();
        System.out.println("state = " + state);//Run
        while(state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();    //更新线程状态
            }
            state=thread.getState();
            System.out.println(state);
        }
    }
}

线程优先级

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

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

    • Thread.MIN_PRIORITY=1;

    • Thread.MAX_PRIORITY=10;

    • Thread.NORM_PRIORITY=5;

  • 使用以下方法改变或获取优先级

    • getPriority.setPriority(int *)

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

package com.zhou.state;

//测试线程的优先级
public class TestPriority {

    public static void main(String[] args) {
        //主线程默认优先级5
        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.start();  //不设置优先级默认为5

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

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

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();
    }
}

class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"的优先级="+Thread.currentThread().getPriority());
    }
}

守护(daemon)线程

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

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

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

  • 如,后台记录操作日志,监控日志,垃圾回收等。。。

package com.zhou.state;

import jdk.nashorn.internal.ir.CallNode;

//测试线程
//钱守护你
public class TestDaemon {

    public static void main(String[] args) {
        Money money=new Money();
        You you=new You();

        Thread thread=new Thread(money);
        thread.setDaemon(true);   //默认为false。表示为用户线程。值为true,表示为守护线程

        thread.start();   //钱守护线程启动

        new Thread(you).start();  //你  用户线程启动。
    }

}

//钱
class Money implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("money daemon");
        }

    }
}

//你
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 365000; i++) {
            System.out.println("你一天都可以赚那么多钱");
        }
        System.out.println("====hello world====");
    }
}

 

线程同步

多个线程操作同一个资源

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

形成条件:队列和锁

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;

    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

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

    同步方法: publis synchronized void method(int args) {}

  • synchronized方法控制对“对象”的访问,每个对象对应一把锁), 每个 synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行 ,就独占该锁直到该方法返回才释放锁 ,后面被阻塞的线程才 能获得这个锁,继续执行

    缺陷:若将一个大的方法申明为synchronized将会影响效率

    image-20200806115842240

    同步块
    • 同步块: synchronized (Obj){}

    • Obj称之为同步监视器

      • Obj可以是任何对象,但是推荐使用共享资源作为同步监视

      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class [反射中讲解]

         

    • 同步监视器的执行过程

     

    1. 第一个线程访问,锁定同步监视器,执行其中代码. 2.第二个线程访问 ,发现同步监视器被锁定,无法访问. 3.第一个线程访问完毕,解锁同步监视器. 4.第二个线程访问, 发现同步监视器没有锁,然后锁定并访问

 

死锁问题

某一个同步块同时拥有“两个以上对象的锁”,就可能发生“死锁“问题。

线程通信

 

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

    方法名 作用

    wait() 表示线程一直等待,直到其他线程通知,

    与sleep不同,会释放锁

    wait() 指定等待的毫秒数

    notify() 唤醒一个处于等待状态的线程

    notifyAll() 唤醒同一个对象上所有调用wait()方法的线

    程,优先级高的线程优先调度

  • 注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException

管程法

 package com.zhou.goja;
 
 //测试生产者消费者模型->利用缓冲区解决:管程法
 //生产者,消费者,产品,缓冲区
 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;
    }
 
     //生产
     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;
    }
 
     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];
     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;
    }
 }

信号灯法

 package com.zhou.goja;
 
 //测试生产者消费者问题:信号灯法,标志位解决
 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;
    }
 
     public void run(){
         for (int i = 0; i < 20; i++) {
             if(i%2==0){
                 this.tv.play("新垣结衣播放中");
            }else{
                 this.tv.play("广告时间");
            }
        }
    }
 }
 
 //消费者----观众
 class Watcher extends Thread{
     TV tv;
     public Watcher(TV tv){
         this.tv=tv;
    }
     public void run(){
         for (int i = 0; i < 20; i++) {
             tv.watch();
        }
    }
 }
 
 //产品----节目
 class TV{
     //演员表演,观众等待 T
     //观众观看,演员等待 F
     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){
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         System.out.println("观看了"+voice);
 
         //通知演员表演
         this.notifyAll();
         this.flag=!this.flag;
    }
 }

 

高级主题

 

线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

  • 好处:

    • 提高响应速度(减少了创建新线程的时间)

    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    • 便于线程管理(....

      • corePoolSize: 核心池的大小

      • maximumPoolSize:最大线程数

      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

  • JDK 5.0起提供了线程池相关API: ExecutorService和Executors

  • ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command) :执行任务/命令,没有返回值,-般用来执行Runnable

    • <T> Future<T> submit(Callable<T> task): 执行任务,有返回值,一般又来执行Callable

    • void shutdown() :关闭连接池

  • Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 

posted @ 2020-08-05 21:10  雅典娜Athena  阅读(442)  评论(0编辑  收藏  举报