Java高并发与多线程网络编程


1. 线程介绍

概念

  • 程序:静态的概念(资源分配的单位)
  • 进程:运行的程序(调度执行的单位)
  • 线程:一个程序中有多个事件同时执行,每个事件一个线程,多个线程共享代码和数据空间

图片源于网络

2. 创建并启动线程

当JVM启动时,会创建一个非守护线程 main,作为整个程序的入口,以及多个与系统相关的守护线程

package concurrency.chapter1;

public class TryConcurrency {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                write();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                read();
            }
        }.start();
    }

    private static void write(){
        System.out.println("start to write");
        try {
            Thread.sleep(1000*10L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("write success!");
    }

    private static void read(){
        System.out.println("start to read");
        try {
            Thread.sleep(1000*10L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("read success!");
    }
}

线程生命周期(Thread.State)
图片源自网络

除了新生状态和死亡状态,任意状态都可能发生线程死亡。

  • NEW:新生状态,作为普通java对象,尚未启动
  • RUNNABLE:在JVM中执行(包含就绪状态和运行状态)
  • BLOCKED:被阻塞
  • WAITING:正在等待另一个线程执行特定动作
  • TIMED_WAITING:正在等待另一个线程达到指定运行时间
  • TERMINATED:死亡状态

传统的多线程共享数据方法是使用static修饰,但是由于static修饰的数据将在整个程序结束之后才释放,因此比较耗资源。可参考内存模型,类加载
而且这种方式会发生并发问题。

实例:银行有多个柜台,柜台共用一个叫号系统

package concurrency.chapter2;

/**
 * 叫号的柜台
 */
public class TicketWindow extends Thread{
    private static final int MAX=500;
    private static int index=1;
    private String name;

    TicketWindow(String name){
        this.name=name;
    }
    @Override
    public void run() {
        while (index<=MAX){
            System.out.println(name+" 叫号:"+(index++));
        }
    }
}
package concurrency.chapter2;

/**
 * 银行叫号
 */
public class Bank {
    public static void main(String[] args) {
        final TicketWindow t1 = new TicketWindow("一号柜台");
        final TicketWindow t2 = new TicketWindow("二号柜台");
        final TicketWindow t3 = new TicketWindow("三号柜台");
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

上述方式通过继承Thread的形式实现多线程,但是显然,业务数据与被混在了线程类当中,这种方式显得混乱,我们使用更好的办法——使用runnable。

package concurrency.chapter2;

public class Bank2 {
    public static void main(String[] args) {
        final TicketWindow2 ticketWindow2 = new TicketWindow2();
        Thread t1 = new Thread(ticketWindow2,"1号柜台");
        Thread t2 = new Thread(ticketWindow2,"2号柜台");
        Thread t3 = new Thread(ticketWindow2,"3号柜台");
        t1.start();
        t2.start();
        t3.start();

    }
}

package concurrency.chapter2;

/**
 * 叫号的柜台
 */
public class TicketWindow2 implements Runnable{
    private static final int MAX=500;
    private static int index=1;

    public void run() {
        while (index<=MAX){
            System.out.println(Thread.currentThread().getName()+" 叫号:"+(index++));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

依旧有线程安全问题。
在这里插入图片描述

3. 函数式接口编程

Thread支持函数式接口编程,Runnable接口就是一个函数式接口。

首先看看什么是函数式接口编程。

Java中使用@FunctionalInterface注解标注函数式接口。

所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法

这种类型的接口也称为SAM接口,即Single Abstract Method interfaces
特点

  • 接口有且仅有一个抽象方法
  • 允许定义静态方法
  • 允许定义默认方法
  • 允许java.lang.Object中的public方法

@FunctionalInterface注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错

例子

// 正确的函数式接口
@FunctionalInterface
public interface TestInterface {
 
    // 抽象方法
    public void sub();
 
    // java.lang.Object中的public方法
    public boolean equals(Object var1);
 
    // 默认方法
    public default void defaultMethod(){
    
    }
 
    // 静态方法
    public static void staticMethod(){
 
    }
}

// 错误的函数式接口(有多个抽象方法)
@FunctionalInterface
public interface TestInterface2 {

    void add();
    
    void sub();
}

函数式接口其实策略模式中的“策略”

计算纳税款的实例:
纳税款的计算方式可能多变,因此将计算方式抽象出来作为一个接口(策略),然后可以实现动态的改变改计算方法。

接口:传入工资

package concurrency.func;

@FunctionalInterface
public interface SimpleCalculator {
    double calculated(double money);
}

计算器:关联上述接口

package concurrency.func;

public class Calculator {
    private double money;
    private final SimpleCalculator calculator;
    Calculator(double money,SimpleCalculator calculator){
        this.calculator=calculator;
        this.money=money;
    }
    public double calculated(){
        return calculator.calculated(money);
    }
}

客户端

public class CalculatorMain {
    public static void main(String[] args) {
        final Calculator calculator = new Calculator(10000, (m)->m*0.2 );
        System.out.println(calculator.calculated());
    }
}

通过传入不同的lambda表达式,实现不同的计算方法。

通过函数式接口编程,将银行叫号整合到一个类中。

public class Bank3 {
    public final static int MAX=50;
    public static int index=1;
    public static void main(String[] args) {
        final Runnable runnable = ()->{
            while (index<=50){
                System.out.println(Thread.currentThread().getName()+" 柜台叫号 "+(index++));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable,"1号柜台").start();
        new Thread(runnable,"2号柜台").start();
        new Thread(runnable,"3号柜台").start();
    }
}

上述代码同样有严重的并发问题。

4. Thread 构造器

构造器
1.Thread()
只做命名工作,命名方式为
Thread-i (i从0开始)

2.Thread(Runnable)
因为runnable只有一个run方法,因此相当于声明一个run方法。如果Runnable为空,则线程什么也不做。

3.Thread(String)
传入线程名字,由于Runnable为空,因此什么也不做。

4.Thread(Runnable,String)
声明run方法,以及线程名字,此时就是一个可工作的线程。

5.ThreadGroup的概念:
在构造器中可以传入一个ThreadGroup,当ThreadGroup为空时,查看源码后可知会设定CurrentThread的ThreadGroup为当前的ThreadGroup。
CurrentThread就是启动当前线程的线程。
比如在main函数中启动的线程,那么CurrentThread就是main,main的ThreadGroup名为main

6.stacksize概念
首先得知道基本的内存模型
在这里插入图片描述
堆和方法区是线程共享,而虚拟机栈是线程私有,stacksize主要影响虚拟机栈。
栈作为内存结构,肯定有限制大小,通过改变stacksize,能够更改自定义线程的虚拟机栈大小。
在官方文档中有说明,stacksize高度依赖平台,不同的运行平台,stacksize可能不起作用。
如果不传stacksize,则默认为0,表示会被忽略。
stacksize被JVM使用,Java中没有直接引用。

5. 守护线程

Daemon:守护线程,会跟随父线程结束。
非守护线程(默认),父线程结束之后,依旧运行。

这么理解: “守护”的对象指的是系统,而非线程本身。当父线程结束之后子线程依旧还在跑,可能出现一些意外的情况,因此需要对系统进行守护。

问题:
1.非守护线程outer中有个长耗时的守护线程inner,那么当outer结束时,inner是什么状态?
inner是守护线程,会随父线程结束,因此当outer结束后,inner未执行完就结束了。

2.守护线程outer中有个长耗时的非守护线程inner,那么当outer结束时,inner是什么状态?
inner是非守护线程,不会随父线程结束,因此当outer结束后它会继续运行。

无论外层的线程是什么类型,只关注本身的类型即可。

通过T.setDaemon(true)设为守护线程。
注意,只有在start之前设置才生效

线程关系

以下内容为个人实验结果,可能存在偏颇之处

线程之间的关系分为三种:
最外层:线程之间是平等的,线程开始之后,就有了自己独立的运行空间,不会受执行该线程的线程所影响
举例:守护线程中执行非守护线程,当守护线程随着main结束之后,内部的非守护线程依旧在执行,既不会阻塞守护线程,也不会跟着守护线程结束。

package concurrency.chapter1;

public class Try {
    public static void main(String[] args) throws InterruptedException {
        // 守护线程中套一个长时间的非守护线程,
        // 一般来说,当main结束后,守护线程会结束,但是由于其内还有个非守护线程,那么它会被阻塞住吗?
        Thread t1 = new Thread(()->{
            Thread t2 = new Thread(()->{
                while (true){
                    System.out.println("尽管父线程都结束了,但是我还在跑");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t2.setDaemon(false);
            t2.start();
            System.out.println("t1结束");
        });
        t1.setDaemon(true);
        t1.start();
        // 模拟t1在工作,
        // 如果不加主线程睡眠,那么程序会直接结束
        // 猜测:t2的创建需要耗时间,还没等t2创建,main就结束了,也导致守护线程t1结束
        Thread.sleep(100);
    }
}

关系二:线程之间存在执行与被执行关系
举例:CurrentThread、Thread的静态方法受执行位置所影响、join等都是这个关系的体现。
比较重要的应用就是内部为守护线程时,内部线程的运行时间受外部线程所影响。

关系三:ThreadGroup
这个通常是人为设置,将多个线程放在一个组中进行管理。

6. join

让CurrentThread等待join线程执行完毕,CurrentThread才继续前进。

在main方法中使用Thread.CurrentThread.join(),相当于让main等待自己结束,这会陷入死循环。

7. interrupt

当线程处于block状态(wait、join、sleep)时,调用interrupt会让线程接收一个InterruptException(如果线程中没有捕获该异常,即便interrupt了,线程也不会收到中断信号)

interrupt不会中断线程,而是将线程的状态改为中断。通过在线程内捕获该状态,然后边写处理代码,实现中断。

block状态也有对象之分:x.wait、x.sleep,当x是谁(或者在哪个线程内)调用,则wait、sleep对象就是x(或当前调用的线程),但是x.join,对象仅仅指CurrentThread,如在main中调用thread1.join(),join的对象是main,即main进入join状态,而非thread1。
根据上述描述,interrupt可以打断block状态的线程,当线程处于join时,可以进行打断。
但是注意进入join状态的线程是哪个。

可以试试以下实验:

package concurrency.chapter3;

public class Interrupt {
    public static void main(String[] args) {
        // 启动测试线程对象 t
        Thread t = new Thread(()->{
           while (true){
           }
        });
        t.start();
        // 由于t.join()之后的代码都不执行,因此新建一个临时线程用于监控t.join()之后的状态
        new Thread(()->{
            try {
                // sleep保证t.join()执行完毕
                Thread.sleep(1000);
                // 拿到main线程
                ThreadGroup group = Thread.currentThread().getThreadGroup();
                Thread[] threads = new Thread[group.activeCount()];
                group.enumerate(threads);
                for (Thread thread:threads){
                    if (thread.getName().equals("main")){
                        System.out.println("main state:"+thread.getState());
                        System.out.println("t state:"+ t.getState());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        try {
            // 在main中调用t.join()
            System.out.println("t.join() invoked in main()");
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果为

t.join() invoked in main()
main state:WAITING
t state:RUNNABLE

说明t.join()之后,main进入join阻塞状态,而非t。

因此,我们通过以下代码想实现通过join打断t是不行的,因此进入join的不是t,而是CurrentThread

package concurrency.chapter3;

public class Interrupt {
    public static void main(String[] args) {
        // 启动测试线程对象 t
        Thread t = new Thread(()->{
           while (true){
           }
        });
        t.start();
        // 由于t.join()之后的代码都不执行,因此新建一个临时线程用于监控t.join()之后的状态
        new Thread(()->{
            System.out.println("在临时线程中interrupt->t");
            t.interrupt();
        }).start();
        try {
            // 在main中调用t.join()
            System.out.println("t.join() invoked in main()");
            t.join();
        } catch (InterruptedException e) {
            System.out.println("t在join过程中被interrupt");
        }
    }
}

以上代码相当于改变了t的interrupt状态为中断状态,但是没有对象去捕获该异常进行处理。
因此,通过join捕获interrupt异常,只能捕获CurrentThread。

8. 优雅的结束线程

方式一:flag

package concurrency.chapter4;

public class Stop1 {
    public static class Test extends Thread{
        private volatile boolean start=true;

        @Override
        public void run() {
            while (start){
            }
        }
        public void shutdown(){
            this.start=false;
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.start();
        // 模拟test工作耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        test.shutdown();
    }
}

方式二:使用interrupt+block

package concurrency.chapter4;

public class Stop2 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(0);
                } catch (InterruptedException e) {
                    System.out.println("结束进程");
                    return;
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

方式三:Thread.interrupted()
Thread.interrupted()用于检测当前线程是否被interrupt,相当于thread.isInterrupted(),只是一个是静态方法,一个是实例方法。
之所以多一个静态方法,是因为在lambda或者匿名内部类中不能使用实例方法。

package concurrency.chapter4;

public class Stop2 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                if (Thread.interrupted()){
                    System.out.println("结束进程");
                    return;
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

考虑以下情况,在线程中,有个事件本身就block了(比如预计10秒,但是一个小时也没结束),这时候,它无法读取到flag,也监听不到interrupted,该如何结束它?
利用守护线程与非守护线程的概念。
父线程结束,守护线程也会结束,因此,我们可以用一个父线程来执行目标线程,并将目标线程设为守护线程。通过控制父线程的结束,实现目标线程的结束。

方式四:利用守护线程
**父线程ThreadService **

package concurrency.chapter4;

public class ThreadService {
    private Thread executeThread;
    private volatile boolean finished=false;
    private String taskName;
    ThreadService(String taskName){
        this.taskName=taskName;
    }
    // 将执行任务设为守护线程
    public void execute(Runnable task){
        executeThread = new Thread(()->{
            Thread runner = new Thread(task);
            runner.setDaemon(true);
            runner.start();
            // executeThread等待runner执行完毕
            try {
                runner.join();
                finished=true;
            } catch (InterruptedException e) {
                // 打断,结束程序体
            }
        });
        executeThread.start();
    }
    public void shutdown(long mills){
        long currentTime = System.currentTimeMillis();
        // 如果没有结束
        while (!finished){
            // 如果超时
            if (System.currentTimeMillis()-currentTime>mills){
                // 并非interrupt()结束了线程,而是interrupt()控制了线程执行语句
                // 调用本行代码时,会进入第21行,
                // 由于executeThread本身没有其他处理逻辑,因此executeThread结束,同时runner也跟着结束
                executeThread.interrupt();
                System.out.println(this.taskName+" 任务超时!强行结束");
                break;
            }
            // 由于finished加了volatile关键字,因此当线程结束时,此处的finished也会被观测为true
            // 所以如果没有超时,while也能够自动断开
        }
        finished=false;
    }
}

客户端实例

package concurrency.chapter4;

public class Stop3 {
    public static void main(String[] args) {
        ThreadService threadService = new ThreadService("test线程");
        long start = System.currentTimeMillis();
        threadService.execute(()->{
            // 执行一个非常重的任务
//            while (true){
//            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadService.shutdown(1000);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}

9. 线程安全、数据共享

同步锁 synchronized
可锁对象:

  1. 实例对象:this,synchronized作为实例方法关键字时,
  2. 类对象:class,synchronized作用于静态方法、修饰静态对象时
  3. 块对象:锁住对应字节码

死锁:相互等待对方的资源,一般发生在锁套锁的情况。

synchronized核心

实例锁:锁特定实例,this
类锁:锁类,class

每次执行之前模拟以下过程:判断是否需要锁-->锁竞争-->得到锁-->执行-->释放锁

①synchronized修饰非静态方法-->锁this
	-->1.1 多个线程访问同一个对象的该方法-->同步
	-->1.2 同一个对象,一个线程访问synchronized方法,另一个对象访问非synchronized方法-->异步
	-->1.3 同一个对象,一个线程访问synchronized方法,另一个对象访问另一个synchronized方法-->同步
结论:一个实例只有一个this锁,
对于1.1,存在锁竞争的过程,因此同步
对于1.2,非synchronized方法不需要锁竞争,因此异步
对于1.3,尽管是两个不同的synchronized方法,但是是同一个锁,也需要锁竞争,因此同步
	
②synchronized修饰静态方法-->锁class
	-->2.1 一个线程访问synchronized静态方法,另一个对象访问非synchronized静态方法-->异步
    -->2.2 一个线程访问synchronized静态方法,另一个对象访问另一个synchronized静态方法-->同步
对于2.1,非synchronized方法不需要锁竞争,因此异步
对于2.2,尽管是两个不同的synchronized方法,但是是同一个锁,也需要锁竞争,因此同步


③定义静态变量lock,通过synchronized(lock){}对代码块进行加锁-->锁lock变量
	-->3.1 一个线程访问synchronized静态方法,另一个线程访问包含synchronized(lock){}的静态方法-->异步
	-->3.2 一个线程访问synchronized静态方法,另一个线程访问包含synchronized(当前类名.class){}的静态方法-->同步
对于3.1,一个锁是class,一个锁是lock,不存在锁竞争,因此异步
对于3.2,两个锁都是当前类名.class,存在锁竞争,因此异步
注意,有种lock的写法是 private static 当前类名 lock = new 当前类名();
此时的lock与当前类名.class依旧不是同一个锁。

④定义静态变量int i,一个线程运行synchronized方法修改i,另一个线程运行费synchronized方法修改i,此时是异步,而且数据不安全。
--> synchronized没有锁住资源,只锁住了代码,在其他入口访问同一份资源依旧会出现数据不同步问题。

⑤定义静态代码块内的synchronized
static{
	synchronized(xx){}
}
-->静态代码块会阻塞该类中的所有资源,因为加载静态代码块属于类的初始化过程

综上,其实synchronized的核心就在于加锁过程,我们需要判断当前锁是否存在、是否是同一个锁对象,进而判断是否存在锁竞争,从而得知是否是同步、异步。

上述内容皆为实验所得,如有遗漏,敬请留言。

10. 死锁

死锁:相互等待对方的资源,一般发生在锁套锁的情况。
A等待B,B等待C,C等待A

常见于以下情况:
在使用第三方service时,第三方service需要传入我们自己写的类。我们自己写的类又加了锁,就可能出现锁套锁的情况。

检测死锁的方法:
打开cmd
jps 查看所有java进程
jstack 进程号 如果有死锁,控制台会通知

11. 线程间的通讯(生产者与消费者)

模型一:单生产者+单消费者

package concurrency.chapter6;

// 生产者消费者模型
public class ProductorConsumerModel {
    private int i;
    private boolean needProduct=true;

    void product(){
        synchronized (this) {
            // 如果需要生产,那么就生产
            if (needProduct) {
                System.out.println("生产:" + (i++));
                // 生产完了通知消费者消费
                this.notify();
                needProduct=false;
            }else{
                // 如果不需要生产,那么就让生产者等待。
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    void comsumed(){
        synchronized (this) {
            // 如果不需要生产,那就消费
            if (!needProduct) {
                System.out.println("消费:" + (--i));
                // 消费完了通知生产者生产
                this.notify();
                needProduct=true;
            }else {
                // 需要生产,就让消费者等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ProductorConsumerModel model = new ProductorConsumerModel();
        new Thread(()->{
            while (true){
                model.product();
            }
        }).start();
        new Thread(()->{
            while (true) {
                model.comsumed();
            }
        }).start();
    }
}

注意,xx.wait()是让执行这个方法的线程等待,直到被通知。xx.notify()是唤醒被当前锁锁住的对象

这个模型只允许构建一个生产者线程和一个消费者线程,多个生产者消费者的时候会出现假死锁状态。
因为不能确定notify作用于哪个对象,导致所有线程进入wait状态

模型二:多消费者-多生产者
把模型一中的notify改为notifyAll()即可(注意被唤醒并且被执行的线程是从上次阻塞的位置从下开始运行,也就是从wait()方法后开始执行。
因此判断是否进入某一线程的条件 是用while判断,而不是用If判断判断。

package concurrency.chapter6;

// 生产者消费者模型
public class ProductorConsumerModel2 {
    private int i;
    private boolean needProduct=true;

    void product(){
        synchronized (this) {
            // 如果需要生产,那么就生产
            while (needProduct) {
                System.out.println(Thread.currentThread().getName() + "生产:" + (i++));
                // 生产完了通知消费者消费
                this.notifyAll();
                needProduct = false;
            }
            // 如果不需要生产,那么就让生产者等待。
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    void comsumed(){
        synchronized (this) {
            // 如果不需要生产,那就消费
            while (!needProduct) {
                System.out.println(Thread.currentThread().getName()+"消费:" + (--i));
                // 消费完了通知生产者生产
                this.notifyAll();
                needProduct=true;
            }
            // 需要生产,就让消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        ProductorConsumerModel2 model = new ProductorConsumerModel2();
        new Thread(()->{
            while (true){
                model.product();
            }
        },"P1").start();

        new Thread(()->{
            while (true){
                model.product();
            }
        },"P2").start();
        new Thread(()->{
            while (true){
                model.product();
            }
        },"P3").start();
        new Thread(()->{
            while (true){
                model.product();
            }
        },"P4").start();

        new Thread(()->{
            while (true) {
                model.comsumed();
            }
        },"C1").start();
        new Thread(()->{
            while (true) {
                model.comsumed();
            }
        },"C2").start();
    }
}

方式三:将数据、生产者、消费者解耦(重点掌握)

package concurrency.chapter6;

import concurrency.chapter4.ThreadService;

import java.util.stream.Stream;

// 生产者消费者模型
public class ProductorConsumerModel4 {

    public static void main(String[] args) {
        Data data = new Data();
        Stream.of("P1","P2").forEach(name->
                new Thread(()->{
                    Productor productor = new Productor(data);
                    while (true){
                        productor.producted();
                    }
                },name).start());

        Stream.of("C1","C2").forEach(name->
                new Thread(()->{
                    Consumer consumer = new Consumer(data);
                    while (true){
                        consumer.consumed();
                    }
                },name).start());
    }
}

// 数据容器
class Data{
    private int i=0;
    private boolean needPush;

    synchronized public void push() {
        while (needPush){
            System.out.println(Thread.currentThread().getName()+"生产: "+ (++i));
            needPush=!needPush;
            notifyAll();
        }
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void pop() {
        while (!needPush){
            System.out.println(Thread.currentThread().getName()+"消费: "+ (i--));
            needPush=!needPush;
            notifyAll();
        }
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 生产者
class Productor{
    private Data data;
    Productor(Data data){
        this.data=data;
    }
    public void producted(){
        data.push();
    }
}

// 消费者
class Consumer{
    private Data data;
    Consumer(Data data){
        this.data=data;
    }
    public void consumed(){
        data.pop();
    }

说明:原理其实非常简单:生产者和消费者的速度都是一样的,那么理论情况下,生产一个,就被消费一个。因此,需要同步的对象就是当前这个被生产/被消费的数据,与之相关的操作就是“生产”与“消费”,因此只需要让这两个操作去竞争同一个锁就行了

思考:如果是一个生产者和一个消费者,并且生产者和消费者都在抢一个锁(对数据的写入和写出权),这不还是单线程么?
此外,如果队列入口一个锁,出口一个锁,生产者和消费者各抢各的锁,用容器上限来判断是否生产\消费,那么多个生产者和消费者的速度是不是还是跟单个生产者\消费者一样?
毕竟队列只有一个入口和出口,同一时刻只允许一个生产者和消费者操作。
上述问题博主暂时也没找到答案,等以后学会了回来补充,如果各位看官懂,敬请留言指点

12. sleep与wait的区别

1.sleep是Thread方法,wait是Object方法
2.sleep不会释放锁,wait会释放锁
3.sleep不依赖于锁,wait的使用依赖于锁
4.sleep不需要唤醒,wait需要(wait(time)除外)

13. 综合案例–数据采集

线程切换是需要开销的,多线程效率是一个开口向下的抛物线,当线程过多的时候,效率会越来越慢。
案例:对n台机器进行数据采集工作,显然,我们需要定义一定数量的线程,当某个线程结束后,再启动一个线程去采集,保证线程的数量不超过设定的最大值。

假设有10台机器,线程最大数为5。

package concurrency.chapter7;

import java.util.*;
import java.util.stream.Stream;

public class DataCapture {
    final private static int MAXSIZE=5;
    // FIFO队列,用于控制运行时的线程个数
    final static private LinkedList<Object> CONTROLS = new LinkedList<>();

    public static void main(String[] args) {
        // 由于流是一次性的,我们用一个容器临时保存线程,保证在后面能够让所有线程join
        List<Thread> threads = new ArrayList<>();
        // 创建10个线程,注意我们能够同时运行的线程最大个数为5,因此通过wait对线程的运行进行控制
        Stream.of("M1","M2","M3","M4","M5","M6","M7","M8","M9","M10")
                .map(DataCapture::threadCreate)
                .forEach(t->{
                    t.start();
                    threads.add(t);
                });
        // 拿到所有线程,进行join
        threads.forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Optional.of("所有线程工作结束").ifPresent(System.out::println);
    }
    private static Thread threadCreate(String name){
        return new Thread(()->{
            // 运行时控制线程个数,进队列抢锁
            synchronized (CONTROLS) {
                // 如果当前个数大于MAXSIZE了(也就是第六个)就让其wait
                while (CONTROLS.size() >= MAXSIZE) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ": 正在等待!");
                        CONTROLS.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                CONTROLS.addLast(new Object());
                // 这句通知由队列发出(即在synchronized中),能够清楚看到工作顺序
                System.out.println(Thread.currentThread().getName()+": 开始工作");
            }

            // 工作时都在自己的工作空间,不需要进行synchronized
            // 模拟工作耗时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+": 工作结束");
            // 出队列抢锁
            synchronized (CONTROLS){
                // 工作完毕,自己退出队列,并通知其他线程
                CONTROLS.removeFirst();
                CONTROLS.notifyAll();
            }
        },name);
    }
}

在个数判断中必须使用while (CONTROLS.size() >= MAXSIZE) {,大于等于号,才能保证同时运行5个线程,我也不知道为啥。按照逻辑来说应该是用大于号,望懂得朋友留言告知。

14. 显式锁(实现自定义锁)

API接口设计

package concurrency.chapter8;

import java.util.Collection;

// 显式锁API接口
public interface Lock {
    // 超时异常
    class TimeOutException extends Exception{
        TimeOutException(String msg){
            super(msg);
        }
    }
    // 加锁
    void lock() throws InterruptedException;
    // 按时加锁
    void lock(long mills) throws InterruptedException,TimeOutException;
    // 解锁
    void unlock();
    // 查看阻塞线程
    Collection<Thread> getBlockedThreads();
    // 查看阻塞个数
    int getBlockedSize();
}

实现

package concurrency.chapter8;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

// 通过boolean值去操控锁
package concurrency.chapter8;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeoutException;

// 通过boolean值去操控锁
public class BooleanLock implements Lock {
    // 如果LOCK为true,说明锁被人持有,否则说明锁为空闲状态
    private boolean LOCK;

    // 保证当前操作锁的对象式currentThread,不然其他线程也能自由调用lock和unlock
    private Thread currentThread;

    // 保存当前被阻塞的线程
    private Collection<Thread> blockedThreads = new ArrayList<>();

    // synchronized不能在接口中声明,在这里进行标注
    @Override
    synchronized public void lock() throws InterruptedException {
        // 如果锁被人持有,那就等待
        while (LOCK){
            this.wait();
            this.blockedThreads.add(Thread.currentThread());
        }
        // 结束while之后说明锁被当前线程拿到,那就更改锁状态
        this.blockedThreads.remove(Thread.currentThread());
        LOCK=true;
        this.currentThread = Thread.currentThread();
    }

    // synchronized加的锁不具备超时的能力,因此我们自定义超时锁
    @Override
    synchronized public void lock(long mills) throws InterruptedException, TimeOutException {
        if (mills<=0){
            lock();
            return;
        }
        // flag用于判断是否超时
        long flag = mills;
        // timeout表示超时时间
        long timeout = System.currentTimeMillis()+mills;
        // 如果锁被持有,就让其等待
        while (LOCK){
            if (flag<=0){
                throw new TimeOutException(Thread.currentThread().getName()+"Time out");
            }
            blockedThreads.add(Thread.currentThread());
            this.wait();
            flag = timeout-System.currentTimeMillis();
        }
        // 拿到锁
        this.LOCK=true;
        blockedThreads.remove(Thread.currentThread());
        this.currentThread = Thread.currentThread();
    }

    @Override
    synchronized public void unlock() {
        // 如果锁被人持有,并且当前试图解锁的也是当前线程,那就释放锁
        while (LOCK && this.currentThread==Thread.currentThread()){
            System.out.println("锁已被释放");
            this.notifyAll();
            LOCK=false;
        }
        // 如果锁处于释放状态,那就不做操作
    }

    @Override
    public Collection<Thread> getBlockedThreads() {
        // unmodifiableCollection 在返回的过程中,不允许对其进行修改
        return Collections.unmodifiableCollection(blockedThreads);
    }

    @Override
    public int getBlockedSize() {
        return blockedThreads.size();
    }
}

客户端测试

package concurrency.chapter8;

import com.sun.org.apache.xpath.internal.operations.Bool;

import java.util.stream.Stream;

public class LockTest {
    public static void main(String[] args) {
        Lock lock = new BooleanLock();
        // 普通lock测试
        Stream.of("T1","T2","T3")
                .forEach(name->{
                    new Thread(()->{
                        // 当前线程拿到锁,开始工作
                        try {
                            lock.lock();
                            System.out.println(Thread.currentThread().getName()+"  拿到锁");
                            work();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            //结束后释放锁
                            lock.unlock();
                        }
                    },name).start();
                });
        // main函数视图释放锁,但是我们有验证,实际操作不了
        lock.unlock();

        // 超时lock测试
        Stream.of("T4","T5","T6")
                .forEach(name->{
                    new Thread(()->{
                        // 当前线程拿到锁,开始工作
                        try {
                            lock.lock(100L);
                            System.out.println(Thread.currentThread().getName()+"  拿到锁");
                            work();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (Lock.TimeOutException e) {
                            System.out.println(Thread.currentThread().getName()+" 超时!");
                        } finally {
                            //结束后释放锁
                            lock.unlock();
                        }
                    },name).start();
                });
        // main函数视图释放锁,但是我们有验证,实际操作不了
        lock.unlock();
    }
    // 模拟工作
    private static void work(){
        try {
            System.out.println(Thread.currentThread().getName()+"  正在工作");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

15. 钩子方法处理系统退出工作

当系统被终止的时候,往往还有很多连接资源没有关闭,比如数据库连接、网络连接等等,因此我们在终止程序的时候,需要一些关闭各种资源的操作——用钩子方法。

package concurrency.chapter9;

public class HookToExit {
    public static void main(String[] args) {
        // 钩子方法
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println("系统正在退出");
            //进行退出资源回收
            nofigyAndRelease();
        }));

        System.out.println("开始工作");
        while (true){

        }
    }
    private static void nofigyAndRelease() {
        //这里处理资源回收或通知
        System.out.println("关闭缓存");
        System.out.println("关闭数据源连接");
        System.out.println("关闭socket");
        System.out.println("系统已退出");
    }
}

也能在nofigyAndRelease可以捕获异常

上述代码在linux下测试可以看到清楚的效果。
无论是手动shutdowm还是kill进程,都能处理退出工作。
注意kill -9 是强制终结命令,无法完成退出处理工作。

16. ThreadException与stackTrace(了解)

package concurrency.chapter9;

import java.util.Optional;
import java.util.stream.Stream;

public class ThreadException {
    private static int A=1;
    private static int B=0;
    public static void main(String[] args){
        Thread t = new Thread(()->{
            // 这个异常无法抛出,只能try-catch
            try {
                Thread.sleep(100);
                A/=B;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
        // 这里捕获异常并处理
        t.setUncaughtExceptionHandler((group,e)->{
            System.out.println(group);
            System.out.println(e);
        });
        System.out.println("打印方法调用栈");
        Stream.of(Thread.currentThread().getStackTrace())
                .filter(e->!e.isNativeMethod())
                .forEach(e-> Optional.of("类名:"+e.getClassName()+"  方法名:"+e.getMethodName()+"  行数:"+e.getLineNumber()).ifPresent(System.out::println));
    }

}

17. ThreadGroup

如果不设置group,会将线程加到当前group中。
ThreadGroup是树形结构。
不能跨ThreadGroup访问(既不能访问父group,也不能访问兄弟group)

group.destroy(),销毁线程组,如果期内还有活跃的线程,抛出异常。

group.enumerate(Thread[] list【, boolean recurse】);
拷贝线程到list中,recurse指定是否需要对其内的其他线程组进行递归拷贝(深拷贝)
不加recurse则默认为true

group.interrupt() 打断其内的所有线程(递归式)

group.setDaemon()
线程组的Daemon跟线程不一样,线程组的Daemon指的是,如果线程组被destroy或其内的所有线程都执行结束,那就销毁该线程。
也就是说,非Daemon线程组需要手动回收
如果创建ThreadGroup时制定了parentThreadGroup,则Daemon与parent一致。

18. 手写线程池

概念:
1.任务队列:所有执行线程去任务队列里面拿任务执行
2.线程队列:线程池有一个线程队列
3.拒绝策略(抛出异常、丢弃、阻塞、临时队列):当任务数量超过线程池设定的可接受数量时,进行拒绝的处理策略
4.容量:线程池的线程队列个数可以动态变化。

package chapter10;

import java.util.*;

// 线程池的简单实现
public class ThreadPool extends Thread {

    //定义线程池大小
    private int size;

    //定义线程池扩容最大容量
    private final static int MAXZISE=30;

    //可提交任务的最大值
    private final int taskQueueMaxSize;

    //线程池默认大小
    public final static int DEFAULT_SIZE=10;

    //任务队列,用于存放外部传进来的所有任务,执行一个就删除一个
    private final static LinkedList<Runnable> TASK_QUEUE = new LinkedList<>();

    // 默认的任务最大个数
    public final static int DEFAULT_TASK_QUEUE_SIZE=2000;

    // 用于存放线程池的所有线程
    private final static List<WorkerTask> WORKER_THREAD_QUEUE = new ArrayList<>();

    // 用于线程命名自增
    private static volatile int seq;

    // 用于线程命名前缀
    private final static String THREAD_PREFIX = "THREAD_POOL-";

    private final static ThreadGroup GROUP = new ThreadGroup("THREAD_POOL");

    // 默认的拒绝策略,超过数量直接抛出异常
    public final static DiscardPolicy DEFAULT_DISCARD_POLICY = ()->{
        throw new DiscardException("提交任务数量过多,任务被拒绝!");
    };
    // 提供给外部传入
    private DiscardPolicy discardPolicy;

    // 线程池的状态
    private boolean isDead;

    //空构造,设为默认大小
    public ThreadPool(){
        this(DEFAULT_SIZE,DEFAULT_TASK_QUEUE_SIZE,DEFAULT_DISCARD_POLICY);
    }
    public ThreadPool(int size, int taskQueueMaxSize, DiscardPolicy discatdPolicy){
        this.size=size;
        this.taskQueueMaxSize = taskQueueMaxSize;
        this.discardPolicy = discatdPolicy;
        // 初始化线程池
        init();
    }

    private void init() {
        // 初始化创建size个工作线程
        for (int i=0;i<size;i++){
            createWorkTask();
        }
        this.start();
    }

    // 对外提供接口,加入任务
    public void submit(Runnable runnable){
        // 队列入口,抢锁
        synchronized (TASK_QUEUE){
            // 拒绝策略判断,任务队列的个数大于最大值
            if (TASK_QUEUE.size()> taskQueueMaxSize){
                discardPolicy.discard();
            }
            TASK_QUEUE.addLast(runnable);
            // 加入队列之后就通知其他休眠的(类似生产者消费者模型)
            TASK_QUEUE.notifyAll();
        }
    }
    // 线程池的状态监控:扩容、缩容等
    @Override
    public void run(){
        while (!isDead){
            // 扩容
            extend();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 缩容
            unextend();
        }
    }

    // 扩容
    private void extend(){
        synchronized (WORKER_THREAD_QUEUE){
            // 如果提交的任务个数TASK_QUEUE.size()/2 > WROKER_THREAD_QUEUE,就再创建一个工作线程
            if (TASK_QUEUE.size()<<1 > WORKER_THREAD_QUEUE.size() && WORKER_THREAD_QUEUE.size()<MAXZISE){
                System.out.println("扩容前:size:"+size);
                createWorkTask();
                size++;
                System.out.println("扩容后:size:"+size);
            }
        }
    }

    //缩容
    private void unextend(){
        synchronized (WORKER_THREAD_QUEUE){
            // 如果提交的任务个数TASK_QUEUE.size()/2 < WROKER_THREAD_QUEUE,就减少一个工作线程
            if (TASK_QUEUE.size()<<1 < WORKER_THREAD_QUEUE.size() && TASK_QUEUE.size()>0){
                System.out.println("缩容前:size:"+size);
                for (Iterator<WorkerTask> iterator = WORKER_THREAD_QUEUE.iterator();iterator.hasNext();){
                    WorkerTask task = iterator.next();
                    if (task.state==TaskState.BLOCKED){
                        task.interrupt();
                        task.close();
                        size--;
                        System.out.println("缩容后:size:"+size);
                    }
                }

            }
        }
    }


    // 创建自定义的工作线程
    private void createWorkTask(){
        WorkerTask task = new WorkerTask(GROUP,THREAD_PREFIX+(seq++));
        // 线程池中的线程在线程池创建后就被启动,具体的状态根据任务需求会自动更改
        task.start();
        // 加到工作队列中
        WORKER_THREAD_QUEUE.add(task);
    }


    // 拒绝策略,提供接口,让外部也能实现自定义拒绝策略
    public interface DiscardPolicy{
        void discard() throws DiscardException;
    }
    // 通过抛出异常,抛出拒绝
    public static class DiscardException extends RuntimeException{
        public DiscardException(String msg){
            super(msg);
        }
    }

    // 关闭线程池
    public void shutdown() throws InterruptedException {
        // 如果任务队列还有,那就稍等一会
        while (!TASK_QUEUE.isEmpty()){
            Thread.sleep(1);
        }
        // 如果任务队列里面没有任务了,那就结束,需要循环结束
        int initVal = WORKER_THREAD_QUEUE.size();
        while (initVal>0){
            for (WorkerTask task :
                    WORKER_THREAD_QUEUE) {
                //如果任务执行完毕,那么状态必为阻塞
                if (task.state==TaskState.BLOCKED) {
                    task.close(); //state设为DADE
                    task.interrupt();//打断,退出任务循环
                    initVal--;
                    // 如果不是,就先等待一下,不要疯狂运行
                }else {
                    System.out.println(TASK_QUEUE.size());
                    Thread.sleep(1);
                }
            }
        }
        isDead=true;
        System.out.println("--------------线程池已被关闭----------------");
    }


    // 定义任务状态,空闲、运行、阻塞、死亡
    private enum TaskState {
        FREE,RUNNING,BLOCKED,DEAD
    }
    // 封装Thread对象,让其拥有我们定义的任务状态等其他信息
    private static class WorkerTask extends Thread{

        //初始化为空闲状态
        private volatile TaskState state = TaskState.FREE;

        // 获取任务状态
        public TaskState getTaskState(){
            return this.state;
        }

        //调用父类的group的构造方法
        public WorkerTask(ThreadGroup group,String name){
            super(group,name);
        }
        //run,让其执行完之后不能销毁,而是放回池中
        @Override
        public void run() {
            //如果线程状态没有死亡,就去队列中获取任务,保证工作线程在系统运行期间永不消亡
            OUTER:
            while (this.state!=TaskState.DEAD){
                // 声明任务
                Runnable runnable;
                //出队,抢锁
                synchronized (TASK_QUEUE){
                    // 如果队列为空,就让线程等待,释放锁
                    while (TASK_QUEUE.isEmpty()){
                        try {
                            // 进入wait,修改状态
                            state = TaskState.BLOCKED;
                            TASK_QUEUE.wait();
                        } catch (InterruptedException e) {
                            //如果线程被打断,就重新去获取任务,保证工作线程永不消亡
                            break OUTER;
                        }
                    }
                    // 如果队列不为空,就拿到第一个任务
                    runnable = TASK_QUEUE.removeFirst();
                    }
                // 拿到任务释放锁,开始工作
                if (runnable!=null){
                    // 开始执行,修改状态
                    state = TaskState.RUNNING;
                    runnable.run();
                    // 执行完毕,修改状态
                    state = TaskState.FREE;
                }
            }
        }

        // 关闭任务
        public void close(){
            this.state = TaskState.DEAD;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 初始化一个线程池
        ThreadPool pool = new ThreadPool();
        // 提交40个任务
        for (int i = 0; i < 40; i++) {
            pool.submit(()->{
                System.out.println("任务被线程"+Thread.currentThread().getName()+"执行");
                try {
                    // 模拟工作
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务被线程"+Thread.currentThread().getName()+"执行完毕");
            });
        }
        // 等待线程工作
        Thread.sleep(20000);
        pool.shutdown();
    }
}

未完待续

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章