Java学习笔记(No.17)

Java多线程详解(No.17)

1、任务、程序、进程、线程、多线程(Task,Program,Process,Thread,Multi-Thread)

1.1、任务(Task)

任务是计算机的基本工作单位

1.2、程序(Program)

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念

1.3、进程(Process)

进程是执行程序的一次运行过程,是一个动态的概念,是系统进行资源分配和调度的基本单位,是操作系统结构的基础程序是指令、数据及其组织形式的描述,进程是程序的实体(即,在操作系统中运行的程序就是进程,如:“微信、QQ、浏览器、IDEA”等软件)

1.4、线程(Thread)

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

1.5、多线程(Multi-Thread)

多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”,利用它编程的概念就叫作“多线程处理”

1.6、单线程与多线程调用方法示意图(Diagram Of Call Method For Single-Thread And Multi-Thread)

单线程与多线程调用方法示意图,如下图所示

单线程与多线程调用方法示意图

1.7、注意事项(Matters Needing Attention)

  • 1.7.1、线程就是独立的执行路径
  • 1.7.2、在程序运行时,即使没有自己创建线程,后台也会有多个线程,如“主线程(即,main,主方法,也叫用户线程)、GC(即,Garbage Collection,垃圾收集方法,也叫守护线程线程”)
  • 1.7.3、main()称之为主线程,为系统入口,用于执行整个程序
  • 1.7.4、在一个进程中,若开辟了多个线程,线程的运行由调度器(即CPU)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
  • 1.7.5、对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 1.7.6、线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 1.7.7、每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
  • 1.7.8、大多数多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如:服务器。若是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为CPU执行切换的很快,所以就有同时执行的错觉

2、线程创建(Thread Creation)

线程创建主要有三种方式,分别为:“继承Thread类、实现Runnable接口、实现Callable接口”

2.1、继承Thread类(Inherit Thread Class)

2.1.1、操作步骤(Operation Steps)
  • 2.1.1.1、自定义线程类并继承Thead类
  • 2.1.1.2、重写run()方法,编写线程执行体
  • 2.1.1.3、创建线程对象,调用start()方法启动线程
2.1.2、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.inheritthreadclass;
//创建线程方式一:继承Thread类;重写run()方法;调用Thread类对象.start()方法开启线程。
//特别注意:线程开启后不一定立即执行,而是由CPU安排调度执行。
public class InheritThreadClass extends Thread{
    //线程入口点
    @Override
    public void run() {
        //super.run();
        //重写run()方法线程体
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程:"+i);
        }
    }
    //main()方法,即“主线程”或“main线程”
    public static void main(String[] args) {
        //创建一个线程对象
        InheritThreadClass inheritThreadClass1 = new InheritThreadClass();
        //调用start()方法开启线程
        inheritThreadClass1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程:"+i);
        }
    }
}
2.1.3、运行结果(Run Result)

其运行结果,如以下信息所示

主线程:0
主线程:1
子线程:0
主线程:2
子线程:1
主线程:3
子线程:2
主线程:4
子线程:3
主线程:5
子线程:4
主线程:6
子线程:5
主线程:7
子线程:6
主线程:8
子线程:7
子线程:8
子线程:9
主线程:9
2.1.4、练习题:网络图片下载(Exercise:Web Picture Download)

其代码,如下所示”

package com.xueshanxuehai.multithread.inheritthreadclass;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
 * 练习题:使用继承Thread类来实现多线程同步下载网络图片
 */
public class WebPictureDownload extends Thread{
    private String pictureUrl;//网络图片地址
    private String pictureName;//网络图片保存名称
    //无参构造方法
    public WebPictureDownload() {
    }
    //有参构造方法
    public WebPictureDownload(String pictureUrl, String pictureName) {
        this.pictureUrl = pictureUrl;
        this.pictureName = pictureName;
    }
    //重写Thread类run方法:下载网络图片线程的执行体
    @Override
    public void run() {
        WebFileDownloader webFileDownloader=new WebFileDownloader();
        boolean bool =webFileDownloader.webPictureDownload(pictureUrl,pictureName);
        if(bool){
            System.out.println("下载网络图片文件("+pictureName+")成功");
        }else{
            System.out.println("下载网络图片文件("+pictureName+")失败");
        }
    }
    public static void main(String[] args) throws IOException {
        //下载网络图片保存路径
        String mySavePath=new File("").getCanonicalPath()+File.separator+"javase"+File.separator+"resources"+File.separator;
        //创建线程对象webPictureDownload1
        WebPictureDownload webPictureDownload1=new WebPictureDownload("https://img-blog.csdnimg.cn/bca8e2562a9d4c5798d0c0855ffd0e29.png",mySavePath+"1.png");
        //创建线程对象webPictureDownload2
        WebPictureDownload webPictureDownload2=new WebPictureDownload("https://img-blog.csdnimg.cn/9f46dc69112b4dd3bdde783e0f76e160.png",mySavePath+"2.png");
        //创建线程对象webPictureDownload3
        WebPictureDownload webPictureDownload3=new WebPictureDownload("https://img-blog.csdnimg.cn/3dc5e1755e7d4518b5d8fa87dcff398f.png",mySavePath+"3.png");
        //调用start()方法开启线程对象webPictureDownload1
        webPictureDownload1.start();
        //调用start()方法开启线程对象webPictureDownload2
        webPictureDownload2.start();
        //调用start()方法开启线程对象webPictureDownload3
        webPictureDownload3.start();
    }
}
/**
 *网络文件下载器
 */
class WebFileDownloader{
    /**
     * 网络图片下载方法
     * @param url
     * @param name
     */
    public boolean webPictureDownload(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
            return true;
        } catch (IOException e) {
            //e.printStackTrace();
            System.out.println("webPictureDownload方法异常,原因:"+e.toString());
            return false;
        }
    }
}

其运行结果,如下所示”

下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\1.png)成功
下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\3.png)成功
下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\2.png)成功
2.1.5、注意事项(Matters Needing Attention)
  • 2.1.5.1、线程开启后不一定立即执行,而是由CPU安排调度执行

2.2、实现Runnable接口(Implement Runnable Interface)

2.2.1、操作步骤(Operation Steps)
  • 2.2.1.1、自定义线程类并实现Runnable接口
  • 2.2.1.2、重写run()方法,编写线程执行体
  • 2.2.1.3、创建线程对象,调用start()方法启动线程
2.2.2、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.implementrunnableinterface;
//创建线程方式二:实现Runnable接口;重写run()方法;调用Thread类对象(实现Runnable接口类对象).start()方法开启线程。
public class ImplementRunnableInterface implements Runnable{
    //线程入口点
    @Override
    public void run() {
        //重写run()方法线程体
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程:"+i);
        }
    }
    //main()方法,即“主线程”或“main线程”
    public static void main(String[] args) {
        //创建实现类对象(Runnable接口)
        ImplementRunnableInterface implementRunnableInterface = new ImplementRunnableInterface();
//        //创建代理类对象(Runnable接口)
//        Thread thread=new Thread(implementRunnableInterface);
//        //调用start()方法开启线程
//        thread.start();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(implementRunnableInterface).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程:"+i);
        }
    }
}
2.2.3、运行结果(Run Result)

其运行结果,如以下信息所示

子线程:0
主线程:0
子线程:1
主线程:1
子线程:2
主线程:2
子线程:3
主线程:3
子线程:4
主线程:4
子线程:5
主线程:5
子线程:6
主线程:6
子线程:7
主线程:7
子线程:8
主线程:8
主线程:9
子线程:9
2.2.4、练习题:网络图片下载(Exercise:Web Picture Download)

其代码,如下所示”

package com.xueshanxuehai.multithread.implementrunnableinterface;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
 * 练习题:使用实现Runnable接口来实现多线程同步下载网络图片
 */
public class WebPictureDownload implements Runnable{
    private String pictureUrl;//网络图片地址
    private String pictureName;//网络图片保存名称
    //无参构造方法
    public WebPictureDownload() {
    }
    //有参构造方法
    public WebPictureDownload(String pictureUrl, String pictureName) {
        this.pictureUrl = pictureUrl;
        this.pictureName = pictureName;
    }
    //重写Runnable接口run方法:下载网络图片线程的执行体
    @Override
    public void run() {
        WebFileDownloader webFileDownloader=new WebFileDownloader();
        boolean bool =webFileDownloader.webPictureDownload(pictureUrl,pictureName);
        if(bool){
            System.out.println("下载网络图片文件("+pictureName+")成功");
        }else{
            System.out.println("下载网络图片文件("+pictureName+")失败");
        }
    }
    public static void main(String[] args) throws IOException {
        //下载网络图片保存路径
        String mySavePath=new File("").getCanonicalPath()+File.separator+"javase"+File.separator+"resources"+File.separator;
        //创建实现类对象(Runnable接口)webPictureDownload1
        WebPictureDownload webPictureDownload1=new WebPictureDownload("https://img-blog.csdnimg.cn/bca8e2562a9d4c5798d0c0855ffd0e29.png",mySavePath+"1.png");
        //创建实现类对象(Runnable接口)webPictureDownload2
        WebPictureDownload webPictureDownload2=new WebPictureDownload("https://img-blog.csdnimg.cn/9f46dc69112b4dd3bdde783e0f76e160.png",mySavePath+"2.png");
        //创建实现类对象(Runnable接口)webPictureDownload3
        WebPictureDownload webPictureDownload3=new WebPictureDownload("https://img-blog.csdnimg.cn/3dc5e1755e7d4518b5d8fa87dcff398f.png",mySavePath+"3.png");
        //创建代理类对象(Runnable接口),并调用start()方法开启线程对象webPictureDownload1
        new Thread(webPictureDownload1).start();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程对象webPictureDownload2
        new Thread(webPictureDownload2).start();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程对象webPictureDownload3
        new Thread(webPictureDownload3).start();
    }
}
/**
 *网络文件下载器
 */
class WebFileDownloader{
    /**
     * 网络图片下载方法
     * @param url
     * @param name
     */
    public boolean webPictureDownload(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
            return true;
        } catch (IOException e) {
            //e.printStackTrace();
            System.out.println("webPictureDownload方法异常,原因:"+e.toString());
            return false;
        }
    }
}

其运行结果,如下所示”

下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\1.png)成功
下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\3.png)成功
下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\2.png)成功
2.2.5、并发问题:购买火车票(Concurrent Problem:Buy Train Ticket)

其代码,如下所示”

package com.xueshanxuehai.multithread.implementrunnableinterface;
/**
 * 购买火车票的并发问题:多个线程操作同一个资源(即,同一对象)的情况下,线程不安全,数据容易紊乱。
 */
public class BuyTrainTicket implements Runnable {
    private int ticketNums = 20;//票数
    //重写Runnable接口run方法
    @Override
    public void run() {
//        while (true) {...}//方式一:指令多,占用寄存器,且有判断跳转
        for (; ; ) {//方式二:指令少,不占用寄存器,且没有判断跳转
            if (ticketNums <= 0) {
                break;
            }
            //模拟延时
            try {
                Thread.sleep(500);//延时500毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "买到了第" + (ticketNums--) + "张票");
        }
    }
    public static void main(String[] args) {
        //创建实现类对象(Runnable接口)
        BuyTrainTicket buyTrainTicket = new BuyTrainTicket();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(buyTrainTicket, "小红").start();
        new Thread(buyTrainTicket, "小橙").start();
        new Thread(buyTrainTicket, "小黄").start();
        new Thread(buyTrainTicket, "小绿").start();
        new Thread(buyTrainTicket, "小蓝").start();
        new Thread(buyTrainTicket, "小青").start();
        new Thread(buyTrainTicket, "小紫").start();
    }
}

其运行结果,如下所示”

小橙买到了第19张票
小紫买到了第17张票
小青买到了第20张票
小红买到了第20张票
小黄买到了第20张票
小蓝买到了第18张票
小绿买到了第20张票
小橙买到了第15张票
小紫买到了第11张票
小绿买到了第12张票
小黄买到了第16张票
小青买到了第14张票
小红买到了第13张票
小蓝买到了第10张票
小紫买到了第9张票
小橙买到了第8张票
小蓝买到了第7张票
小青买到了第6张票
小绿买到了第3张票
小黄买到了第4张票
小红买到了第5张票
小紫买到了第2张票
小橙买到了第1张票
小蓝买到了第0张票
小青买到了第-1张票
小黄买到了第-3张票
小红买到了第-2张票
小绿买到了第-4张票
2.2.6、案例:龟兔赛跑(Case:Tortoise And Rabbit Race)

其过程,如下所示”

1、首先来个赛道距离,然后要离终点越来越近。
2、判断比赛是否结束。
3、打印出胜利者。
4、龟兔赛跑开始。
5、故事中是乌龟赢的,兔子需要睡觉,所以要模拟兔子睡觉。
6、最终乌龟赢得比赛。

其代码,如下所示”

package com.xueshanxuehai.multithread.implementrunnableinterface;
/**
 * 模拟龟兔赛跑
 */
public class TortoiseAndRabbitRace implements Runnable {
    private static String winner;//胜利者
    //重写Runnable接口run方法
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i % 5 == 0) {//兔子每5步休息一下
                try {
                    Thread.sleep(5);//延时5毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = raceEnd(i);
            if (flag) {//比赛结束
                break;
            } else {
                System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
            }
        }
    }
    //判断比赛是否结束
    private boolean raceEnd(int step) {
        if (winner != null) {//已有胜利者
            return true;
        }
        if (step >= 100) {//比赛结束
            winner = Thread.currentThread().getName();
            System.out.println(Thread.currentThread().getName() + "跑了" + step + "步");
            System.out.println("比赛胜利者是:" + winner);
            return true;
        }
        return false;
    }
    public static void main(String[] args) {
        //创建实现类对象(Runnable接口)
        TortoiseAndRabbitRace tortoiseAndRabbitRace = new TortoiseAndRabbitRace();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(tortoiseAndRabbitRace, "兔子").start();
        new Thread(tortoiseAndRabbitRace, "乌龟").start();
    }
}

其运行结果,如下所示”

兔子跑了1步
兔子跑了2步
兔子跑了3步
兔子跑了4步
乌龟跑了1步
乌龟跑了2步
乌龟跑了3步
乌龟跑了4步
乌龟跑了5步
乌龟跑了6步
乌龟跑了7步
乌龟跑了8步
乌龟跑了9步
乌龟跑了10步
乌龟跑了11步
乌龟跑了12步
乌龟跑了13步
乌龟跑了14步
乌龟跑了15步
乌龟跑了16步
乌龟跑了17步
乌龟跑了18步
乌龟跑了19步
乌龟跑了20步
乌龟跑了21步
乌龟跑了22步
乌龟跑了23步
乌龟跑了24步
乌龟跑了25步
乌龟跑了26步
乌龟跑了27步
乌龟跑了28步
乌龟跑了29步
乌龟跑了30步
乌龟跑了31步
乌龟跑了32步
乌龟跑了33步
乌龟跑了34步
乌龟跑了35步
乌龟跑了36步
乌龟跑了37步
乌龟跑了38步
乌龟跑了39步
乌龟跑了40步
乌龟跑了41步
乌龟跑了42步
乌龟跑了43步
乌龟跑了44步
乌龟跑了45步
乌龟跑了46步
乌龟跑了47步
乌龟跑了48步
乌龟跑了49步
乌龟跑了50步
乌龟跑了51步
乌龟跑了52步
乌龟跑了53步
乌龟跑了54步
乌龟跑了55步
乌龟跑了56步
乌龟跑了57步
乌龟跑了58步
乌龟跑了59步
乌龟跑了60步
乌龟跑了61步
乌龟跑了62步
乌龟跑了63步
乌龟跑了64步
乌龟跑了65步
乌龟跑了66步
乌龟跑了67步
乌龟跑了68步
乌龟跑了69步
乌龟跑了70步
乌龟跑了71步
乌龟跑了72步
乌龟跑了73步
乌龟跑了74步
乌龟跑了75步
乌龟跑了76步
乌龟跑了77步
乌龟跑了78步
乌龟跑了79步
乌龟跑了80步
乌龟跑了81步
乌龟跑了82步
乌龟跑了83步
乌龟跑了84步
乌龟跑了85步
乌龟跑了86步
乌龟跑了87步
乌龟跑了88步
乌龟跑了89步
乌龟跑了90步
乌龟跑了91步
乌龟跑了92步
乌龟跑了93步
乌龟跑了94步
乌龟跑了95步
乌龟跑了96步
乌龟跑了97步
乌龟跑了98步
乌龟跑了99步
乌龟跑了100步
比赛胜利者是:乌龟
2.2.7、注意事项(Matters Needing Attention)
  • 2.2.7.1、实现多线程时推荐使用Runnable接口,可避免单继承局限性,且同一个对象可以被多个线程使用
  • 2.2.7.2、多个线程操作同一个资源的情况下,线程不安全,数据容易紊乱

2.3、实现Callable接口(Implement Callable Interface)

2.3.1、操作步骤(Operation Steps)
  • 2.3.1.1、自定义线程类并实现Callable接口,需要返回值类型
  • 2.3.1.2、重写call()方法,编写线程执行体,需要抛出异常
  • 2.3.1.3、创建线程对象(如:“WebPictureDownload webPictureDownload1=new WebPictureDownload(“https://img-blog.csdnimg.cn/bca8e2562a9d4c5798d0c0855ffd0e29.png”,“1.png”);”)
  • 2.3.1.4、创建执行服务(如:“ExecutorService service= Executors.newFixedThreadPool(1);”)
  • 2.3.1.5、提交执行(如:“Future future1=service.submit(webPictureDownload1);")
  • 2.3.1.6、获取结果(如:“boolean result1=future1.get();")
  • 2.3.1.7、关闭服务(如:“service.shutdownNow();")
2.3.2、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.implementcallableinterface;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
 * 创建线程方式三:实现Callable接口
 * 优点:1、可以定义返回值;2、可以抛出异常。
 * 练习题:使用实现Callable接口来实现多线程同步下载网络图片
 */
public class WebPictureDownload implements Callable {
    private String pictureUrl;//网络图片地址
    private String pictureName;//网络图片保存名称
    //无参构造方法
    public WebPictureDownload() {
    }
    //有参构造方法
    public WebPictureDownload(String pictureUrl, String pictureName) {
        this.pictureUrl = pictureUrl;
        this.pictureName = pictureName;
    }
    //重写Callable接口call方法:下载网络图片线程的执行体
    @Override
    public Boolean call()  throws Exception {
        WebFileDownloader webFileDownloader=new WebFileDownloader();
        boolean bool =webFileDownloader.webPictureDownload(pictureUrl,pictureName);
        if(bool){
            System.out.println("下载网络图片文件("+pictureName+")成功");return true;
        }else{
            System.out.println("下载网络图片文件("+pictureName+")失败");return false;
        }
    }
    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
        //下载网络图片保存路径
        String mySavePath=new File("").getCanonicalPath()+File.separator+"javase"+File.separator+"resources"+File.separator;
        //创建实现类对象(Callable接口)webPictureDownload1
        WebPictureDownload webPictureDownload1=new WebPictureDownload("https://img-blog.csdnimg.cn/bca8e2562a9d4c5798d0c0855ffd0e29.png",mySavePath+"1.png");
        //创建实现类对象(Callable接口)webPictureDownload2
        WebPictureDownload webPictureDownload2=new WebPictureDownload("https://img-blog.csdnimg.cn/9f46dc69112b4dd3bdde783e0f76e160.png",mySavePath+"2.png");
        //创建实现类对象(Callable接口)webPictureDownload3
        WebPictureDownload webPictureDownload3=new WebPictureDownload("https://img-blog.csdnimg.cn/3dc5e1755e7d4518b5d8fa87dcff398f.png",mySavePath+"3.png");
        //创建执行服务
        ExecutorService service= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> future1=service.submit(webPictureDownload1);
        Future<Boolean> future2=service.submit(webPictureDownload2);
        Future<Boolean> future3=service.submit(webPictureDownload3);
        //获取结果
        boolean result1=future1.get();
        boolean result2=future2.get();
        boolean result3=future3.get();
        System.out.println(result1+"\r\n"+result2+"\r\n"+result3);
        //关闭服务
        service.shutdownNow();
    }
}
/**
 *网络文件下载器
 */
class WebFileDownloader{
    /**
     * 网络图片下载方法
     * @param url
     * @param name
     */
    public boolean webPictureDownload(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
            return true;
        } catch (IOException e) {
            //e.printStackTrace();
            System.out.println("webPictureDownload方法异常,原因:"+e.toString());
            return false;
        }
    }
}
2.3.3、运行结果(Run Result)

其运行结果,如以下信息所示

下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\1.png)成功
下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\3.png)成功
下载网络图片文件(E:\Environment\Java\IDEA\StageOne\javase\resources\2.png)成功
true
true
true
2.3.4、注意事项(Matters Needing Attention)
  • 2.3.4.1、实现Callable接口的优点:1、可以定义返回值;2、可以抛出异常

3、静态代理模式(Static Proxy Mode)

3.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.staticproxymode;
/**
 * 静态代理模式总结:真实对象和代理对象都要实现同一个接口,代理对象要代理真实对象
 * 静态代理模式优点:代理对象可以做很多真实对象做不了的事情,真实对象专注做自己的事情
 */
public class StaticProxyMode {
    public static void main(String[] args) {
        WeddingCompany weddingCompany=new WeddingCompany(new MarryPerson("学山学海"));
        weddingCompany.marry();
    }
}
//结婚接口
interface Marry{
    void marry();//结婚方法
}
//真实角色(即,真实对象):结婚人,自己去结婚
class MarryPerson implements Marry{
    private String marryPersonName;
    public MarryPerson() {
    }
    public MarryPerson(String marryPersonName) {
        this.marryPersonName = marryPersonName;
    }
    //重写Marry接口的marry方法
    @Override
    public void marry() {
        System.out.println("恭喜"+this.marryPersonName+"要结婚了!!!");
    }
}
//代理角色(即,代理对象):婚庆公司,帮助结婚人去结婚
class WeddingCompany implements Marry{
    private Marry target;//代理真实目标角色
    public WeddingCompany() {
    }
    public WeddingCompany(Marry target) {
        this.target = target;
    }
    //重写Marry接口的marry方法
    @Override
    public void marry() {
        beforeWedding();
        this.target.marry();//真实对象
        afterWedding();
    }
    private void beforeWedding(){
        System.out.println("结婚之前,婚庆公司布置现场");
    }
    private void afterWedding(){
        System.out.println("结婚之后,婚庆公司收取尾款");
    }
}

3.2、运行结果(Run Result)

其运行结果,如以下信息所示

结婚之前,婚庆公司布置现场
恭喜学山学海要结婚了!!!
结婚之后,婚庆公司收取尾款

3.3、注意事项(Matters Needing Attention)

  • 3.3.1、静态代理模式总结:“真实对象和代理对象都要实现同一个接口,代理对象要代理真实对象”
  • 3.3.2、静态代理模式优点:“代理对象可以做很多真实对象做不了的事情,真实对象专注做自己的事情”

4、Lamda表达式(Lamda Expression)

Lamda表达式是Java8的新特性,λ 希腊字母表中排序第十一位的字母,英语名称为 Lamda

4.1、Lamda表达式本质(Lamda Expression Essence)

Lamda表达式本质就是“属于函数式编程的概念

4.2、Lamda表达式语法(Lamda Expression Grammer)

Lamda表达式语法格式,如下所示

/**
 * “(参数列表)->表达式;”,如“(a)->a<<2;”。
 */
(params)->expression;
/**
 * “(参数列表)->语句;”,如“(a)->{System.out.println(a<<2);};”。
 */
(params)->statement;
/**
 * “(参数列表)->{代码块};”,如“(a)->{a<<2;System.out.println(a);};”。
 */
(params)->{statements};

4.3、Lamda表达式作用(Lamda Expression Function)

  • 4.3.1、避免匿名内部类定义过多
  • 4.3.2、简化代码,只保留核心逻辑代码

4.4、函数式接口(Functional Interface)

理解函数式接口是学习Java8中新特性"Lamda表达式"的关键所在**。

  • 4.4.1、任何接口,若只包含唯一一个抽象方法,那么它就是一个函数式接口
  • 4.4.2、对于函数式接口,可以通过Lamda表达式来创建接口的对象

4.5、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.lamdaexpression;
/**
 * 推导Lamda表达式:5种实现函数式接口的方式。
 * 总结:1、使用Lamda表达式的前提是"对应接口必须为函数式接口"。
 *      2、Lamda表达式只能在只有一行代码时去除大括号(即,花括号),否则多行代码时必须用包裹。
 *      3、Lamda表达式中参数列表有多个参数时,必须加上括号,且其参数类型要么都简化,要么都不简化。
 */
public class LamdaExpression {
    //实现函数式接口方式2:实现静态内部类(即,实现函数式接口的静态内部类)
    static class ImplementStaticInnerClass implements ImplementMethod{
        //重写ImplementMethod接口implementMethod方法
        @Override
        public void implementMethod(int num) {
            System.out.println("实现函数式接口的静态内部类(方式"+num+")");
        }
    }
    public static void main(String[] args) {
        //实现函数式接口的外部类(方式1)
        ImplementMethod im1=new ImplementOuterClass();
        im1.implementMethod(1);
        //实现函数式接口的静态内部类(方式2)
        ImplementMethod im2=new ImplementStaticInnerClass();
        im2.implementMethod(2);
        //局部内部类
        class ImplementLocalInnerClass implements ImplementMethod{
            //重写ImplementMethod接口implementMethod方法
            @Override
            public void implementMethod(int num) {
                System.out.println("实现函数式接口的局部内部类(方式"+num+")");
            }
        }
        //实现函数式接口的局部内部类(方式3)
        ImplementMethod im3=new ImplementLocalInnerClass();
        im3.implementMethod(3);
        /**
         * 实现函数式接口的匿名内部类(方式4)
         * 匿名内部类:没有类的名称,必须借助接口或者父类
         */
        ImplementMethod im4=new ImplementMethod() {
            @Override
            public void implementMethod(int num) {
                System.out.println("实现函数式接口的匿名内部类(方式"+num+")");
            }
        };
        im4.implementMethod(4);
        /**
         * 实现函数式接口的Lamda表达式(方式5)
         */
        ImplementMethod im5=(int num)->{
            System.out.println("实现函数式接口的Lamda表达式(方式"+num+")");
        };
        im5.implementMethod(5);
        /**
         * 实现函数式接口的Lamda表达式简化1(方式5)
         * 简化1:简化参数类型
         */
        ImplementMethod im6=(num)->{
            System.out.println("实现函数式接口的Lamda表达式简化1(方式"+num+")");
        };
        im6.implementMethod(5);
        /**
         * 实现函数式接口的Lamda表达式简化2(方式5)
         * 简化2:简化参数列表外面的括号
         */
        ImplementMethod im7=num->{
            System.out.println("实现函数式接口的Lamda表达式简化2(方式"+num+")");
        };
        im7.implementMethod(5);
        /**
         * 实现函数式接口的Lamda表达式简化3(方式5)
         * 简化3:简化代码外面的大括号(即,花括号)
         */
        ImplementMethod im8=num->System.out.println("实现函数式接口的Lamda表达式简化3(方式"+num+")");;
        im8.implementMethod(5);
    }
}
//定义一个函数式接口(即,只有一个抽象方法的接口)
interface ImplementMethod{
    void implementMethod(int num);
}
//实现函数式接口方式1:实现外部类(即,实现函数式接口的外部类)
class ImplementOuterClass implements ImplementMethod{
    //重写ImplementMethod接口implementMethod方法
    @Override
    public void implementMethod(int num) {
        System.out.println("实现函数式接口的外部类(方式"+num+")");
    }
}

4.6、运行结果(Run Result)

其运行结果,如以下信息所示

实现函数式接口的外部类(方式1)
实现函数式接口的静态内部类(方式2)
实现函数式接口的局部内部类(方式3)
实现函数式接口的匿名内部类(方式4)
实现函数式接口的Lamda表达式(方式5)
实现函数式接口的Lamda表达式简化1(方式5)
实现函数式接口的Lamda表达式简化2(方式5)
实现函数式接口的Lamda表达式简化3(方式5)

4.7、注意事项(Matters Needing Attention)

  • 4.7.1、使用Lamda表达式的前提是"对应接口必须为函数式接口"
  • 4.7.2、Lamda表达式只能在只有一行代码时去除大括号(即,花括号),否则多行代码时必须用包裹
  • 4.7.3、Lamda表达式中参数列表有多个参数时,必须加上括号,且其参数类型要么都简化,要么都不简化

5、线程状态示意图(Thread Status Diagram)

线程状态示意图,如下图所示

线程状态示意图

6、线程方法(Thread Method)

线程方法,如下表所示

方法说明
void setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程对象
void interrupt()中断线程,别用这个方法(极不建议)
boolean isAlive()测试线程是否处于活动状态

7、线程停止(Thread Stop)

7.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
/**
 * 线程停止
 * 1、建议线程正常停止:利用次数,不建议死循环。
 * 2、建议使用标志位:设置一个标志位进行终止判定。
 * 3、不要使用Thread类的stop方法或者destroy方法等过时以及JDK不建议使用的方法
 */
public class ThreadStop implements Runnable{
    //线程中定义线程体使用的标志位
    private boolean flag=true;
    //重写Runnable接口run方法
    @Override
    public void run() {
        int i=0;
        //线程体使用标志位
        while(flag){
            System.out.println("线程"+(i++));
        }
    }
    //设置一个公开的方法转换标志位(即,停止线程)
    public void stopThread(){
        this.flag=false;
    }
    public static void main(String[] args) {
        //创建实现类对象(Runnable接口)
        ThreadStop threadStop=new ThreadStop();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(threadStop).start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main方法"+i);
            if(i==15){
                /**
                 * 调用实现Runnable接口的ThreadStop类的stopThread方法
                 * 转换标志位,让线程停止
                 */
                threadStop.stopThread();
                System.out.println("线程停止");
            }
        }
    }
}

7.2、运行结果(Run Result)

其运行结果,如以下信息所示

线程0
线程1
线程2
线程3
线程4
线程5
线程6
线程7
线程8
线程9
线程10
线程11
线程12
线程13
线程14
线程15
线程16
线程17
线程18
线程19
线程20
线程21
线程22
线程23
线程24
线程25
线程26
线程27
线程28
线程29
线程30
线程31
线程32
线程33
线程34
线程35
线程36
线程37
线程38
线程39
线程40
线程41
线程42
线程43
线程44
线程45
线程46
线程47
main方法0
main方法1
main方法2
线程48
线程49
线程50
线程51
线程52
线程53
线程54
线程55
线程56
线程57
线程58
线程59
线程60
线程61
线程62
main方法3
main方法4
main方法5
main方法6
main方法7
main方法8
main方法9
main方法10
main方法11
main方法12
main方法13
main方法14
main方法15
线程停止
main方法16
main方法17
main方法18
main方法19
线程63

7.3、注意事项(Matters Needing Attention)

  • 7.3.1、建议线程正常停止:利用次数,不建议死循环
  • 7.3.2、建议使用标志位:设置一个标志位进行终止判定
  • 7.3.3、不要使用Thread类的stop方法或者destroy方法等过时以及JDK不建议使用的方法

8、线程休眠(Thread Sleep)

8.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 模拟倒计时
 */
public class ThreadSleep {
    public static void main(String[] args) {
        //模拟倒计时
        try {
            System.out.println("倒计时开始");
            countDown(10);
            System.out.println("倒计时结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印当前系统日期时间
        Date startTime = new Date(System.currentTimeMillis());//获取系统当前日期时间
        System.out.println("打印当前系统日期时间开始");
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);//延时1000毫秒
                System.out.println("第" + (i+1) + "次当前系统日期时间:" + (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(startTime)));
                startTime = new Date(System.currentTimeMillis());//更新系统当前日期时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("打印当前系统日期时间结束");
    }
    //倒计时方法
    public static void countDown(int countNum) throws InterruptedException {
        for (; ; ) {
            Thread.sleep(1000);//延时1000毫秒
            System.out.println(countNum--);
            if (countNum <= 0) {//倒计时结束,退出循环
                break;
            }
        }
    }
}

8.2、运行结果(Run Result)

其运行结果,如以下信息所示

倒计时开始
10
9
8
7
6
5
4
3
2
1
倒计时结束
打印当前系统日期时间开始
第1次当前系统日期时间:2021/08/02 09:31:362次当前系统日期时间:2021/08/02 09:31:373次当前系统日期时间:2021/08/02 09:31:384次当前系统日期时间:2021/08/02 09:31:395次当前系统日期时间:2021/08/02 09:31:406次当前系统日期时间:2021/08/02 09:31:417次当前系统日期时间:2021/08/02 09:31:428次当前系统日期时间:2021/08/02 09:31:439次当前系统日期时间:2021/08/02 09:31:4410次当前系统日期时间:2021/08/02 09:31:45
打印当前系统日期时间结束

8.3、注意事项(Matters Needing Attention)

  • 8.3.1、sleep(时间)指定当前线程阻塞的毫秒数
  • 8.3.2、sleep存在异常InterruptedException
  • 8.3.3、sleep时间达到后线程进入就绪状态
  • 8.3.4、sleep可以模拟网络延时(其作用为"放大问题的发生性")、倒计时等
  • 8.3.5、每一个对象都有一个锁,sleep不会释放锁

9、线程礼让(Thread Yield)

9.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
/**
 * 线程礼让
 * 1、线程礼让时,让当前正在执行的线程暂停,但不阻塞。
 * 2、线程礼让时,将线程从运行状态转换为就绪状态。
 * 3、线程礼让时,不一定会成功礼让线程,还是由CPU重新调度。
 */
public class ThreadYield {
    public static void main(String[] args) {
        YieldThread yieldThread=new YieldThread();
        new Thread(yieldThread,"001").start();
        new Thread(yieldThread,"002").start();
    }
}
class YieldThread implements Runnable{
    //重写Runnable接口run方法
    @Override
    public void run() {
        System.out.println("线程("+Thread.currentThread().getName()+")执行开始");
        Thread.yield();//线程礼让
        System.out.println("线程("+Thread.currentThread().getName()+")执行结束");
    }
}

9.2、运行结果(Run Result)

其运行结果,如以下信息所示

线程(001)执行开始
线程(002)执行开始
线程(001)执行结束
线程(002)执行结束

9.3、注意事项(Matters Needing Attention)

  • 9.3.1、线程礼让时,让当前正在执行的线程暂停,但不阻塞
  • 9.3.2、线程礼让时,将线程从运行状态转换为就绪状态
  • 9.3.3、线程礼让时,不一定会成功礼让线程,还是由CPU重新调度

10、线程强制执行(Thread Enforcement)

即,使用Thread类join方法来合并线程强制执行

10.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
/**
 * 合并线程:待此线程执行完成后,再执行其他线程,从而导致其他线程阻塞(类似于生活中的插队现象)
 */
public class ThreadJoin implements Runnable {
    //重写Runnable接口run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程" + (i+1));
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //启动线程
        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);
        thread.start();
        //main线程(主线程)
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                thread.join();//main线程阻塞(即,合并线程时插队)
            }
            System.out.println("main线程" + (i+1));
        }
    }
}

10.2、运行结果(Run Result)

其运行结果,如以下信息所示

main线程1
main线程2
main线程3
main线程4
main线程5
线程1
线程2
线程3
线程4
线程5
线程6
线程7
线程8
线程9
线程10
main线程6
main线程7
main线程8
main线程9
main线程10

10.3、注意事项(Matters Needing Attention)

  • 10.3.1、使用Thread类join方法时,合并线程,待此线程执行完成后,再执行其他线程,从而导致其他线程阻塞(类似于生活中的插队现象)

11、观测线程状态(Observe Thread Status)

即,使用Thread类State枚举来观测线程状态。具体请参考JDK帮助文档

11.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
/**
 * 查看线程状态:具体请参考JDK帮助文档。
 */
public class ThreadStatus {
    public static void main(String[] args) throws InterruptedException {
        //创建线程
        Thread thread=new Thread(()->{//使用Lamda表达式来重写Runnable接口run方法
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000);//阻塞状态(TIMED_WAITING):延时1000毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+(i+1));
            }
        });
        Thread.State state=thread.getState();
        System.out.println("线程状态:"+state);//新生状态(即,创建状态,NEW)
        //启动线程
        thread.start();
        state=thread.getState();
        System.out.println("线程状态:"+state);//运行状态(RUNNABLE)
        //只要线程不停止(即,线程不是死亡状态),就一直输出线程状态
        while(state!=Thread.State.TERMINATED){
            Thread.sleep(1000);//阻塞状态(TIMED_WAITING):延时1000毫秒
            state=thread.getState();//更新线程状态
            System.out.println("线程状态:"+state);
        }
        System.out.println("线程状态:"+state);//死亡状态(TERMINATED)
    }
}

11.2、运行结果(Run Result)

其运行结果,如以下信息所示

线程状态:NEW
线程状态:RUNNABLE
线程状态:TIMED_WAITING
线程1
线程状态:TIMED_WAITING
线程2
线程状态:TIMED_WAITING
线程3
线程状态:TERMINATED
线程状态:TERMINATED

11.3、注意事项(Matters Needing Attention)

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

12、线程优先级(Thread Priority)

线程优先级用数字表示,范围从1~10,默认优先级为“5”。具体请参考JDK帮助文档

12.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
/**
 * 线程优先级
 */
public class ThreadPriority {
    public static void main(String[] args) {
        //main线程(主线程)默认优先级
        System.out.println("线程(" + Thread.currentThread().getName() + ")优先级(" + Thread.currentThread().getPriority() + ")");
        //创建实现类(Runnable接口)
        PriorityThread priorityThread = new PriorityThread();
        //创建代理类
        Thread thread1 = new Thread(priorityThread,"1");
        Thread thread2 = new Thread(priorityThread,"2");
        Thread thread3 = new Thread(priorityThread,"3");
        Thread thread4 = new Thread(priorityThread,"4");
        Thread thread5 = new Thread(priorityThread,"5");
        //使用线程优先级时,先设置线程优先级,再启动线程,若未设置线程优先级,则线程为默认优先级
        thread1.start();
        thread2.setPriority(Thread.MIN_PRIORITY);//最小线程优先级
        thread2.start();
        thread3.setPriority(3);
        thread3.start();
        thread4.setPriority(8);
        thread4.start();
        thread5.setPriority(Thread.MAX_PRIORITY);//最大线程优先级
        thread5.start();
    }
}
class PriorityThread implements Runnable {
    //重写Runnable接口run方法
    @Override
    public void run() {
        System.out.println("线程(" + Thread.currentThread().getName() + ")优先级(" + Thread.currentThread().getPriority() + ")");
    }
}

12.2、运行结果(Run Result)

其运行结果,如以下信息所示

线程(main)优先级(5)
线程(4)优先级(8)
线程(1)优先级(5)
线程(5)优先级(10)
线程(2)优先级(1)
线程(3)优先级(3)

12.3、注意事项(Matters Needing Attention)

  • 12.3.1、Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 12.3.2、优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,而是由CPU资源调度决定,并且当CPU先调用优先级低的线程时,会发生优先级倒置问题
  • 12.3.3、使用线程优先级时,先设置线程优先级,再启动线程,若未设置线程优先级,则线程为默认优先级

13、守护线程(Daemon Thread)

13.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread;
/**
 * 守护线程
 */
public class ThreadDaemon {
    public static void main(String[] args) {
        //创建实现类(Runnable接口)
        DaemonThread daemonThread=new DaemonThread();
        UserThread userThread=new UserThread();
        //创建代理类对象(Runnable接口)
        Thread thread=new Thread(daemonThread);
        /**
         * 调用setDaemon(布尔参数)方法设置线程为守护线程
         * 默认布尔参数为“false”,表示用户线程,且正常的线程都是用户线程
         * 当布尔参数设置为“false”时,表示守护线程
         */
        thread.setDaemon(true);//守护线程
        //调用start()方法开启线程
        thread.start();//启动守护线程
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(userThread).start();//启动用户线程
    }
}
class DaemonThread implements Runnable{
    private int daemonThreadNum=0;
    //重写Runnable接口run方法
    @Override
    public void run() {
        for (;;) {
            System.out.println("守护线程:"+(++daemonThreadNum));
        }
    }
}
class UserThread implements Runnable{
    //重写Runnable接口run方法
    @Override
    public void run() {
        System.out.println("用户线程开始");
        for (int i = 0; i < 10; i++) {
            System.out.println("用户线程:"+(i+1));
        }
        System.out.println("用户线程结束");
    }
}

13.2、运行结果(Run Result)

其运行结果,如以下信息所示

用户线程开始
用户线程:1
用户线程:2
用户线程:3
用户线程:4
用户线程:5
用户线程:6
用户线程:7
用户线程:8
用户线程:9
用户线程:10
用户线程结束
守护线程:1
守护线程:2
守护线程:3
守护线程:4
守护线程:5
守护线程:6
守护线程:7
守护线程:8
守护线程:9
守护线程:10
守护线程:11
守护线程:12
守护线程:13
守护线程:14
守护线程:15
守护线程:16
守护线程:17
守护线程:18
守护线程:19
守护线程:20
守护线程:21
守护线程:22
守护线程:23
守护线程:24
守护线程:25
守护线程:26
守护线程:27
守护线程:28
守护线程:29
守护线程:30
守护线程:31
守护线程:32
守护线程:33
守护线程:34
守护线程:35
守护线程:36
守护线程:37
守护线程:38
守护线程:39
守护线程:40
守护线程:41
守护线程:42
守护线程:43
守护线程:44
守护线程:45
守护线程:46
守护线程:47
守护线程:48
守护线程:49
守护线程:50
守护线程:51
守护线程:52
守护线程:53
守护线程:54
守护线程:55
守护线程:56
守护线程:57
守护线程:58
守护线程:59
守护线程:60
守护线程:61
守护线程:62
守护线程:63
守护线程:64
守护线程:65
守护线程:66
守护线程:67
守护线程:68
守护线程:69
守护线程:70
守护线程:71
守护线程:72
守护线程:73
守护线程:74
守护线程:75
守护线程:76
守护线程:77
守护线程:78
守护线程:79
守护线程:80
守护线程:81
守护线程:82
守护线程:83
守护线程:84
守护线程:85
守护线程:86
守护线程:87
守护线程:88
守护线程:89
守护线程:90
守护线程:91
守护线程:92
守护线程:93
守护线程:94
守护线程:95
守护线程:96
守护线程:97
守护线程:98
守护线程:99
守护线程:100
守护线程:101
守护线程:102
守护线程:103
守护线程:104
守护线程:105
守护线程:106
守护线程:107
守护线程:108
守护线程:109
守护线程:110
守护线程:111
守护线程:112
守护线程:113
守护线程:114
守护线程:115
守护线程:116
守护线程:117
守护线程:118

13.3、注意事项(Matters Needing Attention)

  • 13.3.1、线程分为"用户线程"与"守护线程"
  • 13.3.2、虚拟机必须确保用户线程执行完毕,如:main线程
  • 13.3.3、虚拟机不用等待守护线程执行完毕,如:gc线程

14、线程同步机制(Thread Synchronization Mechanism)

线程同步就是“多个线程操作同一个资源(对象)”

  • 14.1、线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,然后下一个线程再使用
  • 14.2、处理多线程问题时,若多个线程访问同一个对象,且某些线程会修改此对象,此时就需要线程同步
  • 14.3、线程同步安全的形成条件:队列与锁
  • 14.4、由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了“锁机制(synchronized)”,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。但是也存在以下问题:
    • 14.4.1、一个线程持有锁会导致其它需要此锁的线程挂起
    • 14.4.2、在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 14.4.3、若一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置的性能问题

15、三大不安全线程同步案例(Three Unsafe Thread Synchronization Cases)

15.1、不安全线程同步案例一(Unsafe Thread Synchronization Case One)

不安全线程同步的买票(Buy Tickets)案例

15.1.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization;
/**
 * 不安全线程同步案例一:买票问题???
 * 线程不安全,会出现负数。
 */
public class UnsafeBuyTicketsCase {
    public static void main(String[] args) {
        //创建实现类(Runnable接口)
        BuyTickets buyTickets=new BuyTickets();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(buyTickets,"小红").start();
        new Thread(buyTickets,"小绿").start();
        new Thread(buyTickets,"小蓝").start();
    }
}
class BuyTickets implements Runnable {
    private int ticketsNum = 10;//票数
    private boolean flag = true;//线程外部停止标志
    //重写Runnable接口run方法
    @Override
    public void run() {
        while (flag) {//还有余票
            try {
                buyTickets();//买票
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private void buyTickets() throws InterruptedException {
        //判断是否还有余票
        if (ticketsNum <= 0) {
            flag = false;
            return;
        }
        //模拟延时
        Thread.sleep(10);//延时10毫秒
        System.out.println(Thread.currentThread().getName() + "买到第" + (ticketsNum--) + "张票");
    }
}
15.1.2、运行结果(Run Result)

其运行结果,如以下信息所示

小绿买到第9张票
小蓝买到第8张票
小红买到第10张票
小红买到第7张票
小绿买到第6张票
小蓝买到第5张票
小红买到第4张票
小绿买到第3张票
小蓝买到第2张票
小绿买到第1张票
小红买到第-1张票
小蓝买到第0张票

15.2、不安全线程同步案例二(Unsafe Thread Synchronization Case Two)

不安全线程同步的银行取款(Bank Withdrawal)案例

15.2.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.unsafecases;
/**
 * 不安全线程同步案例二:银行取款问题???
 * 两个人去银行取钱,同一张卡或帐号
 * 线程不安全,会出现负数。
 */
public class UnsafeBankWithdrawalCase {
    public static void main(String[] args) {
        //创建帐号类对象
        Account account = new Account(1000, "未来发展基金");
        //创建银行取款类对象
        BankWithdrawal bankWithdrawal1 = new BankWithdrawal(account, 500, "小红");
        BankWithdrawal bankWithdrawal2 = new BankWithdrawal(account, 1000, "小蓝");
        //调用Thread类start方法启动线程
        bankWithdrawal1.start();
        bankWithdrawal2.start();
    }
}
//帐号
class Account {
    int money;//帐号余额
    String name;//帐号名称

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行取款
class BankWithdrawal extends Thread {
    Account account;//帐号
    private int withdrawalMoney;//帐号当前取款金额
    private static int withdrawalMoneyTotal;//累计帐号取款总金额

    public BankWithdrawal(Account account, int withdrawalMoney, String threadName) {
        super(threadName);
        this.account = account;
        this.withdrawalMoney = withdrawalMoney;
    }
    //重写Thread类run方法
    @Override
    public void run() {
        //判断帐号余额是否还有钱
        if (account.money - withdrawalMoney < 0) {
            //Thread.currentThread().getName()等价于this.getName()
            System.out.println(this.getName() + "取款金额(" + withdrawalMoney + ")时,帐号(" + account.name + ")余额(" + account.money + ")不足");
            return;
        }
        //模拟延时:使用sleep方法,可以放大问题的发生性
        try {
            Thread.sleep(1000);//延时100毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money -= withdrawalMoney;//取款后的余额
        //Thread.currentThread().getName()等价于this.getName()
        System.out.println(this.getName() + "取款金额(" + withdrawalMoney + ")后的帐号(" + account.name + ")余额(" + account.money + ")");
        withdrawalMoneyTotal += withdrawalMoney;//帐号累计取款总金额
        //Thread.currentThread().getName()等价于this.getName()
        System.out.println(this.getName() + "取款金额(" + withdrawalMoney + ")后的帐号(" + account.name + ")累计取款总金额(" + withdrawalMoneyTotal + ")");
    }
}
15.2.2、运行结果(Run Result)

其运行结果,如以下信息所示

小红取款金额(500)后的帐号(未来发展基金)余额(-500)
小红取款金额(500)后的帐号(未来发展基金)累计取款总金额(500)
小蓝取款金额(1000)后的帐号(未来发展基金)余额(0)
小蓝取款金额(1000)后的帐号(未来发展基金)累计取款总金额(1500)

15.3、不安全线程同步案例三(Unsafe Thread Synchronization Case Three)

不安全线程同步的列表集合(List Collection)案例

15.3.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.unsafecases;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * 不安全线程同步案例三:列表集合问题???
 * 线程不安全,会出现同一索引的数据重复写入覆盖,从而会出现列表集合容量小于实际列表集合容量。
 */
public class UnsafeListCollectionCase {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            },""+(i+1)).start();
        }
        try {
            Thread.sleep(1000);//延时1000毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("列表集合容量:"+list.size());
        System.out.println(Arrays.toString(list.toArray()));
    }
}
15.3.2、运行结果(Run Result)

其运行结果,如以下信息所示

列表集合容量:998
[1, 2, 3, 4, 6, 5, 7, 8, 9, 10, 11, 14, 12, 13, 16, 15, 17, 18, 19, 21, 20, 22, 23, 24, 25, 26, 27, 29, 28, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 71, 72, 69, 74, 75, 70, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 91, 89, 92, 94, 93, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 158, 157, 159, 160, 161, 162, 163, 166, 164, 167, 165, 168, 169, 170, 171, 172, 173, 174, 175, 176, 178, 177, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 246, 245, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 262, 261, 263, 264, 265, 266, 267, 268, 269, 271, 270, 272, 273, 275, 274, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 443, 444, 442, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 514, 513, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 937, 938, 939, 936, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]

16、同步方法及同步块(Synchronization Method And Synchronization Block)

由于通过 private关键字来保证数据对象只能被方法访问,所以需要针对方法提出一套机制,这套机制就是synchronized关键字,有两种用法:“synchronized方法(即,同步方法)”与“synchronized块(即,同步块)”

16.1、同步方法(Synchronization Method)

其语法格式为:“访问修饰符 synchronized 返回值类型 方法名(参数类型 参数名称…){方法体}”

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

16.1.1、安全线程同步案例一(Safe Thread Synchronization Case One)

安全线程同步的买票(Buy Tickets)案例

16.1.1.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.safecases;
/**
 * 安全线程同步案例一:买票问题!!!
 * 使用同步方法:线程安全,可能由于锁的内容太多,会浪费资源,从而也会影响效率。
 */
public class SafeBuyTicketsCase {
    public static void main(String[] args) {
        //创建实现类(Runnable接口)
        BuyTickets buyTickets=new BuyTickets();
        //创建代理类对象(Runnable接口),并调用start()方法开启线程
        new Thread(buyTickets,"小红").start();
        new Thread(buyTickets,"小绿").start();
        new Thread(buyTickets,"小蓝").start();
    }
}
class BuyTickets implements Runnable {
    private int ticketsNum = 10;//票数
    private boolean flag = true;//线程外部停止标志
    //重写Runnable接口run方法
    @Override
    public void run() {
        while (flag) {//还有余票
            try {
                buyTickets();//买票
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 同步方法:锁定的是this(即,对象本身)
     * @throws InterruptedException
     */
    private synchronized void buyTickets() throws InterruptedException {
        //判断是否还有余票
        if (ticketsNum <= 0) {
            flag = false;
            return;
        }
        //模拟延时
        Thread.sleep(10);//延时10毫秒
        System.out.println(Thread.currentThread().getName() + "买到第" + (ticketsNum--) + "张票");
    }
}
16.1.1.2、运行结果(Run Result)

其运行结果,如以下信息所示

小红买到第10张票
小红买到第9张票
小红买到第8张票
小红买到第7张票
小红买到第6张票
小红买到第5张票
小红买到第4张票
小红买到第3张票
小红买到第2张票
小红买到第1张票

16.2、同步块(Synchronization Block)

其语法格式为:“synchronized (同步监视器){代码块}”

16.2.1、同步监视器的执行过程(Synchronization Monitor Execution Process)
  • 16.2.1.1、第一个线程访问,锁定同步监视器,执行其中代码
  • 16.2.1.2、第二个线程访问,发现同步监视器被锁定,无法访问
  • 16.2.1.3、第一个线程访问结束,解锁同步监视器
  • 16.2.1.4、第二个线程访问,发现同步监视器未锁定,然后锁定并访问
16.2.2、安全线程同步案例二(Safe Thread Synchronization Case Two)

安全线程同步的银行取款(Bank Withdrawal)案例

16.2.2.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.safecases;
/**
 * 安全线程同步案例二:银行取款问题!!!
 * 两个人去银行取钱,同一张卡或帐号
 * 使用同步块:线程安全,不会出现负数。
 */
public class SafeBankWithdrawalCase {
    public static void main(String[] args) {
        //创建帐号类对象
        Account account = new Account(1000, "未来发展基金");
        //创建银行取款类对象
        BankWithdrawal bankWithdrawal1 = new BankWithdrawal(account, 500, "小红");
        BankWithdrawal bankWithdrawal2 = new BankWithdrawal(account, 1000, "小蓝");
        //调用Thread类start方法启动线程
        bankWithdrawal1.start();
        bankWithdrawal2.start();
    }
}
//帐号
class Account {
    int money;//帐号余额
    String name;//帐号名称

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行取款
class BankWithdrawal extends Thread {
    Account account;//帐号
    private int withdrawalMoney;//帐号当前取款金额
    private static int withdrawalMoneyTotal;//累计帐号取款总金额

    public BankWithdrawal(Account account, int withdrawalMoney, String threadName) {
        super(threadName);
        this.account = account;
        this.withdrawalMoney = withdrawalMoney;
    }
    //重写Thread类run方法
    @Override
    public void run() {
        /**
         * 同步块:锁的对象是变化的量,即,需要更改的对象(如:“增加、更改、删除”操作的对象)
         */
        synchronized (account){
            //判断帐号余额是否还有钱
            if (account.money - withdrawalMoney < 0) {
                //Thread.currentThread().getName()等价于this.getName()
                System.out.println(this.getName() + "取款金额(" + withdrawalMoney + ")时,帐号(" + account.name + ")余额(" + account.money + ")不足");
                return;
            }
            //模拟延时:使用sleep方法,可以放大问题的发生性
            try {
                Thread.sleep(1000);//延时100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= withdrawalMoney;//取款后的余额
            //Thread.currentThread().getName()等价于this.getName()
            System.out.println(this.getName() + "取款金额(" + withdrawalMoney + ")后的帐号(" + account.name + ")余额(" + account.money + ")");
            withdrawalMoneyTotal += withdrawalMoney;//帐号累计取款总金额
            //Thread.currentThread().getName()等价于this.getName()
            System.out.println(this.getName() + "取款金额(" + withdrawalMoney + ")后的帐号(" + account.name + ")累计取款总金额(" + withdrawalMoneyTotal + ")");
        }
    }
}
16.2.2.2、运行结果(Run Result)

其运行结果,如以下信息所示

小红取款金额(500)后的帐号(未来发展基金)余额(500)
小红取款金额(500)后的帐号(未来发展基金)累计取款总金额(500)
小蓝取款金额(1000)时,帐号(未来发展基金)余额(500)不足
16.2.3、安全线程同步案例三(Safe Thread Synchronization Case Three)

安全线程同步的列表集合(List Collection)案例

16.2.3.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.safecases;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * 安全线程同步案例三:列表集合问题!!!
 * 使用同步块:线程安全,不会再出现列表集合容量小于实际列表集合容量。
 */
public class SafeListCollectionCase {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                /**
                 * 同步块:锁定list列表集合(容器)对象
                 */
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            },""+(i+1)).start();
        }
        try {
            Thread.sleep(1000);//延时1000毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("列表集合容量:"+list.size());
        System.out.println(Arrays.toString(list.toArray()));
    }
}
16.2.3.2、运行结果(Run Result)

其运行结果,如以下信息所示

列表集合容量:1000
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 47, 48, 49, 45, 46, 50, 51, 52, 53, 54, 55, 56, 57, 59, 58, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 123, 122, 124, 125, 127, 126, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 152, 151, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 230, 229, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 243, 242, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 265, 264, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 422, 421, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 587, 586, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 754, 753, 755, 756, 757, 758, 759, 760, 762, 761, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 884, 883, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]

16.3、注意事项(Matters Needing Attention)

  • 16.3.1、由于synchronized方法中实际上只有需更改的内容才需要锁,而synchronized方法会把其中所有内容都会锁,所以导致锁的内容太多,会浪费资源,从而也会影响效率
  • 16.3.2、同步块中的同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 16.3.3、同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中会讲解]

17、CopyOnWriteArrayList()

17.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.safecases;
import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
/**
 * JUC并发编程的安全线程同步的列表集合(容器):其线程同步已安全,无需使用同步方法或同步块。
 */
public class JUC_CopyOnWriteArrayList {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            },""+(i+1)).start();
        }
        try {
            Thread.sleep(1000);//延时1000毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("列表集合容量:"+list.size());
        System.out.println(Arrays.toString(list.toArray()));
    }
}

17.2、运行结果(Run Result)

其运行结果,如以下信息所示

列表集合容量:1000
[1, 2, 3, 4, 5, 6, 7, 9, 8, 10, 13, 12, 14, 15, 16, 17, 11, 19, 22, 21, 18, 20, 24, 25, 23, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 57, 56, 55, 54, 58, 66, 65, 62, 64, 63, 61, 60, 59, 67, 68, 70, 69, 71, 74, 78, 79, 77, 73, 81, 72, 80, 82, 75, 76, 85, 84, 83, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 155, 154, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 170, 169, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 231, 229, 230, 232, 233, 234, 235, 237, 236, 238, 239, 241, 240, 243, 242, 244, 245, 246, 247, 249, 248, 250, 251, 252, 254, 253, 255, 258, 257, 256, 259, 260, 262, 263, 265, 266, 261, 267, 264, 269, 270, 268, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 327, 325, 326, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 586, 585, 587, 589, 588, 591, 590, 593, 594, 592, 595, 596, 597, 598, 599, 602, 601, 600, 603, 604, 605, 607, 606, 609, 610, 608, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 622, 621, 623, 624, 625, 626, 627, 629, 628, 630, 631, 632, 633, 634, 637, 639, 638, 640, 641, 635, 636, 643, 642, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 659, 658, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]

17.3、注意事项(Matters Needing Attention)

  • 17.3.1、JUC并发编程的安全线程同步的列表集合(容器):其线程同步已安全,无需使用同步方法或同步块

18、死锁(Dead Lock)

死锁就是“多个线程互相抱着对方需要的资源,从而形成僵持(亦即,相持不下)现象”

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

18.1、四个必要条件(Four Necessary Conditions)

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

18.2、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization;
/**
 * 死锁:多个线程互相抱着对方需要的资源,从而形成僵持(亦即,相持不下)现象。
 */
public class DeadLock {
    public static void main(String[] args) {
        MakeUp makeUp1=new MakeUp(0,"小美");
        MakeUp makeUp2=new MakeUp(1,"小丽");
        makeUp1.start();
        makeUp2.start();
    }
}
//化妆品类
class Cosmetics{
}
//镜子类
class Mirrors{
}
//化妆类
class MakeUp extends Thread{
    //需要资源只有一份,用static来保证只有一份
    private static Cosmetics cosmetics=new Cosmetics();//创建化妆品对象
    private static Mirrors mirrors=new Mirrors();//创建镜子对象
    private int choice;//选择类型(镜子或化妆品)
    private String name;//使用化妆品的人名称
    public MakeUp(int choice,String name){
        this.choice=choice;
        this.name=name;
    }
    //重写Thread类run方法
    @Override
    public void run() {
        try {
            makeUp();//调用化妆方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妆方法:互相持有对方的锁,都需要拿到对方的资源。
    private void makeUp() throws InterruptedException {
        if(choice==0){
//            //会发生死锁(存在多个线程互相抱着对方需要的资源)
//            synchronized(mirrors){//获得镜子锁
//                System.out.println(this.name+"获得镜子锁");
//                Thread.sleep(1000);//延时1000毫秒
//                synchronized(cosmetics){//获得化妆品锁
//                    System.out.println(this.name+"获得化妆品锁");
//                }
//            }
            //不会发生死锁(不存在多个线程互相抱着对方需要的资源)
            synchronized(mirrors){//获得镜子锁
                System.out.println(this.name+"获得镜子锁");
            }
            Thread.sleep(1000);//延时1000毫秒
            synchronized(cosmetics){//获得化妆品锁
                System.out.println(this.name+"获得化妆品锁");
            }
        }else{
//            //会发生死锁(存在多个线程互相抱着对方需要的资源)
//            synchronized(cosmetics){//获得化妆品锁
//                System.out.println(this.name+"获得化妆品锁");
//                Thread.sleep(1000);//延时1000毫秒
//                synchronized(mirrors){//获得镜子锁
//                    System.out.println(this.name+"获得镜子锁");
//                }
//            }
            //不会发生死锁(不存在多个线程互相抱着对方需要的资源)
            synchronized(cosmetics){//获得化妆品锁
                System.out.println(this.name+"获得化妆品锁");
            }
            Thread.sleep(1000);//延时1000毫秒
            synchronized(mirrors){//获得镜子锁
                System.out.println(this.name+"获得镜子锁");
            }
        }
    }
}

18.3、运行结果(Run Result)

其运行结果,如以下信息所示

小美获得镜子锁
小丽获得化妆品锁
小美获得化妆品锁
小丽获得镜子锁

18.4、注意事项(Matters Needing Attention)

  • 18.4.1、对于死锁的四个必要条件,只要想办法破坏其中任意一个或多个条件就可以避免死锁的发生

19、Lock(锁)

从JDK5开始,Java提供了更强大的线程同步机制"通过显示定义同步锁对象(即,Lock对象)来实现同步"

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

ReentrantLock(即,“可重入锁”)类实现了Lock接口,其拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock类对象,其可以显示加锁、释放锁

19.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Lock锁:使用实现Lock接口的ReentrantLock(可重入锁)类
 */
public class LockInterfaceAndReentrantLock {
    public static void main(String[] args) {
        //创建实现类(Runnable接口)
        ReentrantLockClass reentrantLockClass=new ReentrantLockClass();
        //创建代理类对象(Runnable接口),并调用start方法开启线程
        new Thread(reentrantLockClass,"小红").start();
        new Thread(reentrantLockClass,"小绿").start();
        new Thread(reentrantLockClass,"小蓝").start();
    }
}
//实现类(Runnable接口)
class ReentrantLockClass implements Runnable{
    private int ticketsNum=10;//票数
    private final ReentrantLock reentrantLock=new ReentrantLock();//创建Lock锁对象
    //重写Runnable接口的run方法
    @Override
    public void run() {
        for (;;) {//死循环
            try{
                reentrantLock.lock();//加锁
                if(ticketsNum>0){//票未卖完
                    Thread.sleep(1000);//延时1000毫秒
                    System.out.println(Thread.currentThread().getName()+"买到了第"+(ticketsNum--)+"张票。");
                }else{//票已卖完
                    break;//退出循环
                }
            } catch (InterruptedException e) {
                e.printStackTrace();//输出打印异常
            } finally{
                reentrantLock.unlock();//解锁
            }
        }
    }
}

19.2、运行结果(Run Result)

其运行结果,如以下信息所示

小红买到了第10张票。
小红买到了第9张票。
小红买到了第8张票。
小红买到了第7张票。
小红买到了第6张票。
小红买到了第5张票。
小红买到了第4张票。
小红买到了第3张票。
小红买到了第2张票。
小红买到了第1张票。

19.3、Lock与synchronized的对比(Lock And Synchronized Comparison)

  • 19.3.1、Lock是显示锁,需要手动打开与关闭锁,切记勿忘关闭锁;而synchronized是隐式锁,会自动开启锁,并且一旦出了其作用域会自动释放锁
  • 19.3.2、Lock只有代码块锁(即,同步块锁),而synchronized有代码块锁(即,同步块锁)与方法锁(即,同步方法锁)
  • 19.3.3、使用Lock锁时,JVM将花费较少的时间来调度线程,性能会更好,并且其具有更好的扩展性(提供更多的子类)
  • 19.3.4、优先使用顺序:“Lock > 同步代码块(已进入方法体,分配了相应资源) > 同步方法(在方法体之外)”

20、线程通信之生产者消费者问题及解决方法(Producer Consumer Problems And Solutions Of Thread Communication)

线程通信就是不同线程之间的消息传递

20.1、线程通信方法(Thread Communication Methods)

Java提供了解决线程之间通信问题的一些方法,如下表所示

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

特别注意:以上线程通信方法均为Object类的方法,且都只能在"同步方法"或“同步块"中使用,否则会抛出异常IllegalMonitorStateException(即,非法监视器状态异常)

20.2、生产者消费者问题的来源(Sources Of Producer Consumer Problems)

  • 20.2.1、生产者消费者问题是线程通信的一个应用场景,如下所示

    • 20.2.1.1、若仓库中只能存放一件产品,生产者将生产出的产品放入仓库,消费者将仓库中产品取出
    • 20.2.1.2、若仓库中没有产品,则生产者将产品放入仓库中,否则停止生产并等待,直到仓库中的产品被消费者取出为止
    • 20.2.1.3、若仓库中放有产品,则消费者可以将产品取出,否则停止消费并等待,直到仓库中再次放入产品为止
  • 20.2.2、生产者消费者问题是线程同步的一个问题,其中,生产者与消费者共享同一个资源,且生产者与消费者之间互相依赖,互为条件。如下所示

    • 20.2.2.1、对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
    • 20.2.2.2、对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
    • 20.2.2.3、在生产者消费者问题中,仅有synchronized是不够的,如下所示
      • 20.2.2.3.1、synchronized可阻止并发更新同一个共享资源,实现了同步
      • 20.2.2.3.2、synchronized不能用来实现不同线程之间的消息传递(即,线程通信)

20.3、生产者消费者问题的解决方法(Solutions Of Producer Consumer Problems)

生产者消费者问题的解决方法一般有两种(即,“管程法"与"信号灯法”)

20.3.1、管程法(Pipe Program Method)
并发协作模式“生产者/消费者模式”:“管程法”,利用缓冲区解决问题。
	1、生产者:负责生产数据的模块(可能是方法/对象/线程/进程)2、消费者:负责处理数据的模块(可能是方法/对象/线程/进程)3、缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。
总结:“生产者将生产好的数据放入缓冲区,消费者从缓冲区取出数据”。

生产者消费者问题的解决方法之管程法示意图,如下图所示

生产者消费者问题的解决方法之管程法示意图

20.3.1.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.producerconsumerproblemsolutions;
/**
 * 生产者消费者问题的解决方法一:“管程法”,利用同步缓冲区解决。
 * 生产者、消费者、产品、同步缓冲区
 */
public class PipeProgramMethod {
    public static void main(String[] args) {
        //创建"同步缓冲区"类对象
        SynchronizationBuffer synchronizationBuffer = new SynchronizationBuffer(10);
        //创建实现类对象(继承Thread类),并调用start方法开启线程
        new Producers(synchronizationBuffer, 1, 20).start();
        new Consumers(synchronizationBuffer, 1, 10).start();
        new Consumers(synchronizationBuffer, 2, 10).start();
    }
}
//生产者
class Producers extends Thread {
    SynchronizationBuffer synchronizationBuffer;//缓冲区
    private int producerID;//生产者编号
    private int producerNum;//生产数量
    public Producers(SynchronizationBuffer synchronizationBuffer, int producerID, int producerNum) {
        this.synchronizationBuffer = synchronizationBuffer;
        this.producerID = producerID;
        this.producerNum = producerNum;
    }
    //重写Thread类run方法
    @Override
    public void run() {//生产
        for (int i = 0; i < this.producerNum; i++) {
            synchronizationBuffer.putInProduct(this.producerID, new Products(i + 1));
        }
    }
}
//消费者
class Consumers extends Thread {
    SynchronizationBuffer synchronizationBuffer;//缓冲区
    private int consumerID;//消费者编号
    private int consumerNum;//消费数量
    public Consumers(SynchronizationBuffer synchronizationBuffer, int consumerID, int consumerNum) {
        this.synchronizationBuffer = synchronizationBuffer;
        this.consumerID = consumerID;
        this.consumerNum = consumerNum;
    }
    //重写Thread类run方法
    @Override
    public void run() {//消费
        for (int i = 0; i < this.consumerNum; i++) {
            synchronizationBuffer.takeOutProduct(this.consumerID);
        }
    }
}
//产品
class Products {
    private int productID;//产品编号
    public int getProductID() {
        return productID;
    }
    public Products(int productID) {
        this.productID = productID;
    }
}
//同步缓冲区
class SynchronizationBuffer {
    private int bufferNum;//缓冲区大小
    Products[] products;//缓冲区
    private int bufferCount = 0;//缓冲区计数

    public SynchronizationBuffer(int bufferNum) {
        this.bufferNum = bufferNum;
        this.products = new Products[this.bufferNum];//缓冲区
    }
    //生产者放入产品
    public synchronized void putInProduct(int producerID, Products products1) {
        //若缓冲区已满,就需要等待消费者消费后才能再放入生产的产品
        while (bufferCount == bufferNum) {
            //生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //若缓冲区未满,则继续放入生产的产品
        products[bufferCount] = products1;
        bufferCount++;
        //输出生产产品信息
        System.out.println("生产者(" + producerID + ")生产了第" + products1.getProductID() + "件产品");
        //放入生产的产品后,通知消费者消费
        this.notifyAll();
    }
    //消费者取出产品
    public synchronized void takeOutProduct(int consumerID) {
        //若缓冲区已空,就需要等待放入生产的产品后消费者才能再消费
        while (bufferCount == 0) {
            //消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //若缓冲区未空,则消费者继续消费
        bufferCount--;
        Products products1 = products[bufferCount];
        //输出消费产品信息
        System.out.println("消费者(" + consumerID + ")消费了第" + products1.getProductID() + "件产品");
        //消费者消费后,通知生产者继续生产
        this.notifyAll();
    }
}
20.3.1.2、运行结果(Run Result)

其运行结果,如以下信息所示

生产者(1)生产了第1件产品
生产者(1)生产了第2件产品
生产者(1)生产了第3件产品
生产者(1)生产了第4件产品
生产者(1)生产了第5件产品
生产者(1)生产了第6件产品
生产者(1)生产了第7件产品
生产者(1)生产了第8件产品
生产者(1)生产了第9件产品
生产者(1)生产了第10件产品
消费者(2)消费了第10件产品
消费者(2)消费了第9件产品
消费者(2)消费了第8件产品
消费者(2)消费了第7件产品
消费者(2)消费了第6件产品
消费者(2)消费了第5件产品
消费者(2)消费了第4件产品
消费者(2)消费了第3件产品
消费者(2)消费了第2件产品
消费者(2)消费了第1件产品
生产者(1)生产了第11件产品
生产者(1)生产了第12件产品
生产者(1)生产了第13件产品
生产者(1)生产了第14件产品
生产者(1)生产了第15件产品
生产者(1)生产了第16件产品
生产者(1)生产了第17件产品
生产者(1)生产了第18件产品
生产者(1)生产了第19件产品
生产者(1)生产了第20件产品
消费者(1)消费了第20件产品
消费者(1)消费了第19件产品
消费者(1)消费了第18件产品
消费者(1)消费了第17件产品
消费者(1)消费了第16件产品
消费者(1)消费了第15件产品
消费者(1)消费了第14件产品
消费者(1)消费了第13件产品
消费者(1)消费了第12件产品
消费者(1)消费了第11件产品
20.3.2、信号灯法(Signal Lamp Method)
并发协作模式“生产者/消费者模式”:“信号灯法”,利用标志位解决问题(类似于生活中交通信号指示灯的方法原理)
20.3.2.1、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization.producerconsumerproblemsolutions;
/**
 * 生产者消费者问题的解决方法二:“信号灯法”,利用同步标志位解决。
 * 生产者(演员)、消费者(观众)、产品(表演)、同步标志位
 */
public class SignalLampMethod {
    public static void main(String[] args) {
        //创建"同步标志位"类对象
        Performance performance=new Performance();
        //创建实现类对象(继承Thread类),并调用start方法开启线程
        new Performer(performance,1,20).start();
        new Audience(performance,1,10).start();
        new Audience(performance,2,10).start();
    }
}
//生产者(演员)
class Performer extends Thread {
    Performance performance;//同步标志位
    private int performerID;//演员ID
    private int performItemNum;//表演项目数量
    public Performer(Performance performance, int performerID, int performItemNum) {
        this.performance = performance;
        this.performerID = performerID;
        this.performItemNum = performItemNum;
    }
    //重写Thread类run方法
    @Override
    public void run() {
        for (int i = 0; i < this.performItemNum; i++) {
            if(i%3==0){
                this.performance.perform("建国大业"+(i+1),this.performerID);
            }else if(i%3==1){
                this.performance.perform("建军大业"+(i+1),this.performerID);
            }else if(i%3==2){
                this.performance.perform("建党大业"+(i+1),this.performerID);
            }
        }
    }
}
//消费者(观众)
class Audience extends Thread {
    Performance performance;//同步标志位
    private int audienceID;//观众ID
    private int watchItemNum;//观看项目数量
    public Audience(Performance performance, int audienceID, int watchItemNum) {
        this.performance = performance;
        this.audienceID = audienceID;
        this.watchItemNum = watchItemNum;
    }
    //重写Thread类run方法
    @Override
    public void run() {
        for (int i = 0; i < watchItemNum; i++) {
            this.performance.watch(this.audienceID);
        }
    }
}
//产品(表演)
class Performance {
    private String performanceItemName;//表演项目名称
    /**
     * 同步标志位
     * 演员表演时,观众观看等待,同步标志位为“true”。
     * 观众观看时,演员表演等待,同步标志位为“false”。
     */
    private boolean flag = true;//同步标志位
    //表演
    public synchronized void perform(String performanceItemName, int performerID) {
        while (!flag) {
            //观众观看时,演员表演等待
            try {
                this.wait();//当前线程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员("+performerID+")表演了:" + performanceItemName);
        //演员表演完成后,通知观众观看
        this.notifyAll();//所有线程唤醒
        this.performanceItemName = performanceItemName;
        this.flag = !this.flag;
    }
    //观看
    public synchronized void watch(int audienceID) {
        while (flag) {
            //演员表演时,观众观看等待
            try {
                this.wait();//当前线程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众("+audienceID+")观看了:" + this.performanceItemName);
        //观众观看完成后,通知演员表演
        this.notifyAll();//所有线程唤醒
        this.flag = !this.flag;
    }
}
20.3.2.2、运行结果(Run Result)

其运行结果,如以下信息所示

演员(1)表演了:建国大业1
观众(1)观看了:建国大业1
演员(1)表演了:建军大业2
观众(2)观看了:建军大业2
演员(1)表演了:建党大业3
观众(2)观看了:建党大业3
演员(1)表演了:建国大业4
观众(1)观看了:建国大业4
演员(1)表演了:建军大业5
观众(1)观看了:建军大业5
演员(1)表演了:建党大业6
观众(1)观看了:建党大业6
演员(1)表演了:建国大业7
观众(1)观看了:建国大业7
演员(1)表演了:建军大业8
观众(1)观看了:建军大业8
演员(1)表演了:建党大业9
观众(1)观看了:建党大业9
演员(1)表演了:建国大业10
观众(1)观看了:建国大业10
演员(1)表演了:建军大业11
观众(1)观看了:建军大业11
演员(1)表演了:建党大业12
观众(1)观看了:建党大业12
演员(1)表演了:建国大业13
观众(2)观看了:建国大业13
演员(1)表演了:建军大业14
观众(2)观看了:建军大业14
演员(1)表演了:建党大业15
观众(2)观看了:建党大业15
演员(1)表演了:建国大业16
观众(2)观看了:建国大业16
演员(1)表演了:建军大业17
观众(2)观看了:建军大业17
演员(1)表演了:建党大业18
观众(2)观看了:建党大业18
演员(1)表演了:建国大业19
观众(2)观看了:建国大业19
演员(1)表演了:建军大业20
观众(2)观看了:建军大业20

21、线程池(Thread Pool)

从JDK5开始,Java提供了线程池相关API:“ExecutorService接口与Executors工具类”,如下所示

  • ExecutorService接口:“真正的线程池接口,常见实现类有ThreadPoolExecutor类”,常见方法如下所示
    • void execute(Runnable command):“执行任务或命令,没有返回值,一般用来执行Runnable”
    • Future submit(Callable task):“执行任务,有返回值,一般用来执行Callable”
    • void shutdown():“关闭连接池“
  • Executors工具类:“线程池的工厂类,用于创建并返回不同类型的线程池”

21.1、背景(Background)

由于经常创建和销毁使用量很大的资源,如并发下的线程,会对性能影响很大,所以使用线程池解决

21.2、原理(Principle)

提前创建多个线程,放入线程池中,使用时直接从线程池中获取,使用完再放回线程池中,可以避免频繁创建与销毁,从而实现重复利用(类似于生活中的公共交通工具)

21.3、优点(Advantage)

  • 21.3.1、提高响应速度(减少了创建新线程的时间)
  • 21.3.2、降低资源消耗(重复利用线程池中的线程,不需要每次都创建线程)
  • 21.3.3、便于线程管理(可以查看最大线程数等性能参数,如下所示)
    • 21.3.3.1、corePoolSize:”核心池的大小“
    • 21.3.3.2、maximumPoolSize:”最大线程数“
    • 21.3.3.3、keepAliveTime:”线程没有任务时最多保持多长时间会终止“

21.4、示例代码(Sample Code)

其示例,如以下代码所示

package com.xueshanxuehai.multithread.threadsynchronization;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 线程池
 */
public class ThreadPool {
    public static void main(String[] args) {
        //创建服务:即,创建线程池,且指定线程池大小
        ExecutorService executorService= Executors.newFixedThreadPool(5);
        //执行服务
        executorService.execute(new UseThreadPool());
        executorService.execute(new UseThreadPool());
        executorService.execute(new UseThreadPool());
        executorService.execute(new UseThreadPool());
        executorService.execute(new UseThreadPool());
        //关闭服务:即,关闭线程池连接
        executorService.shutdown();
    }
}
//实现类(Runnable接口)
class UseThreadPool implements Runnable{
    //重写Runnable接口run方法
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

21.5、运行结果(Run Result)

其运行结果,如以下信息所示

pool-1-thread-1
pool-1-thread-3
pool-1-thread-4
pool-1-thread-2
pool-1-thread-5

参考资料(Reference Data):任务程序进程线程多线程

学习网站地址(即"学习网址",Learning Website Address):Java多线程详解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值