阶段一:Java多线程基础知识

1、线程介绍

进程、线程、单核CPU和多核、协程

2、创建并启动线程

 我们现在想一个场景:这里我们尝试并发的做一些事情读数据库的时候写磁盘,当然我们这里不会真正的读数据库和写磁盘,只是模拟。

 

package com.wangwenjun.concurrency.chapter1;

/**
 * 这里我们尝试并发的做一些事情
 * 读数据库的时候写磁盘,当然我们这里不会真正的读数据库和写磁盘,只是模拟
 **/
public class TryConcurrency01 {

    public static void main(String[] args) {
        readFromDataBase();
        writeDataToFile();
    }

    private static void readFromDataBase() {
        // read data from database and handle it.
        try {
            println("Begin read data from db");
            Thread.sleep(1000 * 10L);
            println("Read data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }
    private static void writeDataToFile() {
        // read data from database and handle it.
        try {
            println("Begin write data to file");
            Thread.sleep(2000 * 10L);
            println("Write data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }
    private static void println(String message) {
        System.out.println(message);
    }
}

我们发现上面的代码是无法进行并行操作的。

我们再举一个例子:

package com.wangwenjun.concurrency.chapter1;
public class TryConcurrency01 {

    public static void main(String[] args) {
        // 第二个例子(这两个for循环也是顺序执行,不会交替执行,同时执行)
        for (int i = 0; i < 100; i++) {
            println("Tash 1=>" + i);
        }

        for (int j = 0; j < 100; j++) {
            println("Tash 2=>" + j);
        }
    }
    private static void println(String message) {
        System.out.println(message);
    }
}

同样地:这两行for循环代码也无法同时输出。

那我们怎么用并行来处理上面的问题呢?就要用到线程。我们这里先用Thread类来创建。

Thread类如下:public class Thread implements Runnable

这里又两种方式:用匿名内部类;新建一个类继承Thread并重写run方法。

对于上面的并发两个for循环,如下:

用匿名内部类实现(此时这里面有两个线程:Custom-Thread和Main线程)

package com.wangwenjun.concurrency.chapter1;
public class TryConcurrency01 {

    public static void main(String[] args) {
        Thread t1 = new Thread("Custom-Thread") {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    println("Tash 1=>" + i);
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        for (int j = 0; j < 100; j++) {
            println("Tash 2=>" + j);
            try {
                Thread.sleep(1000 *10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private static void println(String message) {
        System.out.println(message);
    }
}

用新建类实现(继承Thread并重写run方法)

package com.wangwenjun.concurrency.chapter1;
public class TryConcurrency01 {
    public static void main(String[] args) {
        new MyThread01().start();

        for (int j = 0; j < 100; j++) {
            println("Tash 2=>" + j);
            try {
                Thread.sleep(1000 *10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void println(String message) {
        System.out.println(message);
    }
}
class MyThread01 extends Thread {
    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {
            TryConcurrency01.println("Tash 1=>" + j);
            try {
                Thread.sleep(1000 *10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

那么我们现在用并行实现下数据从数据库读和写入磁盘(此时里面是三个线程,包含Main线程)

package com.wangwenjun.concurrency.chapter1;

/**
 * 这里我们尝试并发的做一些事情
 * 读数据库的时候写磁盘,当然我们这里不会真正的读数据库和写磁盘,只是模拟
 **/
public class TryConcurrency {

    public static void main(String[] args) {
        new Thread("READ-THREAD") {
            @Override
            public void run() {
                readFromDataBase();
            }
        }.start();
        new Thread("READ-THREAD") {
            @Override
            public void run() {
                writeDataToFile();
            }
        }.start();
    }

    private static void readFromDataBase() {
        // read data from database and handle it.
        try {
            println("Begin read data from db");
            Thread.sleep(1000 * 10L);
            println("Read data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }
    private static void writeDataToFile() {
        // read data from database and handle it.
        try {
            println("Begin write data to file");
            Thread.sleep(2000 * 10L);
            println("Write data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }
    private static void println(String message) {
        System.out.println(message);
    }
}

3、线程的生命周期

 其实我们在上面的案例中用java自带的线程监控界面(命令是cmd下jps查看进程ID和jconsole打开监控界面)看过,线程是会消亡的。

线程的生命周期如下图所示:

 

 当线程创建时,即new Thread时,就进入了new状态;当启用start()方法时,就进入了runnable状态,即可运行状态;此时CPU就看着赏你资源,拿到CPU资源就运行是Running状态;在Running状态可能因为Sleep等原因进入阻塞状态,也有可能因为CPU资源被收回,变回runnable状态,也有可挂了,进入terminated状态;在block状态等阻塞结束会进入runnable状态,注意阻塞结束是不会直接到runnable状态的,其实我在想万一正好给他资源呢??哈哈,不用纠结这个,也可能挂了,进入terminated状态;此外,runnable状态,也可能进入terminated状态。

对于线程周期上面已经说完了,我们来想一个其它问题,我们启动线程为什么调用start方法,而不是run方法??。

https://www.nonelonely.com/article/1559282468236,看下这个链接,笔记我暂时不整理了。

package com.wangwenjun.concurrency.chapter1;

/**
 * @author YCKJ-GaoJT
 * @create 2020-11-20 9:33
 **/
public class TemplateMethod {

    public final void print(String message) {
        System.out.println("#############");
        wrapPrint(message);
        System.out.println("#############");
    }

    protected void wrapPrint(String args) {

    }

    public static void main(String[] args) {
        TemplateMethod t1 = new TemplateMethod() {
            @Override
            protected void wrapPrint(String message) {
                System.out.println("*" + message + "*");
            }
        };
        t1.print("Hello Thread");

        TemplateMethod t2 = new TemplateMethod() {
            @Override
            protected void wrapPrint(String message) {
                System.out.println("+" + message + "+");
            }
        };
        t2.print("Hello Thread");
    }
}

 

正常情况下:我们会让print声明为final,因为不想让子类继承,继承即可修改里面的代码,可能就不调用wrapPrint方法了;我们会把waroPrint方法声明为protected,这样就只是暴露给子类。

那为什么start和run方法不这么声明,暂时不得而知。

4、Runnable接口介绍

 我们现在实现一个场景:银行叫号。假设有三个柜台,票号从1-50,来的人取号排队,超过50就从1重新记号(最后这句话我们就不实现了)。

package com.wangwenjun.concurrency.chapter2;
public class TicketWindow extends Thread {
    private final String name;
    private final int MAX =50;
    private int index = 1;
    public TicketWindow(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println("柜台"+ name +"当前的号码是:"+ (index++));
        }
    }
}

 

上面是Thread代码,现在启动线程:

package com.wangwenjun.concurrency.chapter2;
public class Bank {
    public static void main(String[] args) {
        TicketWindow ticketWindow1 = new TicketWindow("一号柜台");
        ticketWindow1.start();
        TicketWindow ticketWindow2 = new TicketWindow("二号柜台");
        ticketWindow2.start();
        TicketWindow ticketWindow3 = new TicketWindow("三号柜台");
        ticketWindow3.start();
    }
}

但是此时你运行会发现一个非常尴尬的事情,就是三个窗口,每个输出都是1-50号票。这和我们的初衷不一致的。那该怎么办??如果想怎么办?要想为什么出现了这样的问题,我们每new一个TicketWindow,里面就会有自己的一份变量index和常量MAX;我们这里创建了三个TicketWindow,那么就是三份,各自在各自的空间执行,互不干扰。那我们处理的关键就是仅此一份,怎么办呢?这两个变量都用static声明,类加载时只此一份,和实例的操作范围已无关。

但是其实上面用static声明后还是有一个问题:线程安全,后面讲解;因为操作了公共数据;表现为可能打出两个1号票。

但是上面用static声明,会有性能问题,因为声明周期,其是根据类的加载而存在,JVM退出(类消亡),而消失。

那还有其他方法吗?想其它方法,就要想上面的问题具体是什么?上面的问题根本是声明线程(这里是声明的子类)和逻辑数据(变量和操作这些变量的run方法)是在一起的。我们要把他们分开,怎么分开呢??你是想不到的,其实就是用runnable接口。

package com.wangwenjun.concurrency.chapter2;
public class TicketWindowRunnable implements Runnable {
    private int index = 0;
    private final static int MAX = 50;
    @Override
    public void run() {

        while (index <= MAX) {
            System.out.println(Thread.currentThread() + "的号码是" + (index++));
        }
    }
}

 

下面是启动

package com.wangwenjun.concurrency.chapter2;
//逻辑和线程分离,逻辑和线程在不同的object,不同的class里面。
//runnable接口的作用就是将线程单元和逻辑单元分离,这是面向对象思想的一个很好的体现
//这个思想和哪个设计模式很像??后面讲
public class BankWindow2 {
    public static void main(String[] args) {
        TicketWindowRunnable ticketWindow = new TicketWindowRunnable();
        Thread windowThread1 = new Thread(ticketWindow, "一号窗口");
        Thread windowThread2 = new Thread(ticketWindow, "二号窗口");
        Thread windowThread3 = new Thread(ticketWindow, "三号窗口");
        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
    }
}

 

那么上面这个Runnable采用了什么设计模式呢??

package com.wangwenjun.concurrency.chapter2;

/**
 * @author YCKJ-GaoJT
 * @create 2020-11-20 10:59
 **/
public class TaxCalaculator {
    private final double salary;
    private final double bonus;
    public TaxCalaculator(double salary, double bonus) {
        this.salary = salary;
        this.bonus = bonus;
    }
    protected double calcTax() {
        return 0.0d;
    }
    public double calculate() {
        return this.calcTax();
    }

    public double getSalary() {
        return salary;
    }

    public double getBonus() {
        return bonus;
    }
}

 

调用:

package com.wangwenjun.concurrency.chapter2;

/**
 * @author YCKJ-GaoJT
 * @create 2020-11-20 11:01
 **/
public class TaxCalculatorMain {
    public static void main(String[] args) {
        TaxCalaculator calaculator = new TaxCalaculator(10000d, 2000d) {
            @Override
            protected double calcTax() {
                return getSalary() * 0.1 + getBonus() * 0.15;
            }
        };
        double tax = calaculator.calcTax();
        System.out.println(tax);
    }
}

 

上面这个其实就是相当于用Thread类重写run方法,并没有用到runnable接口。

下面我们开始用

package com.wangwenjun.concurrency.chapter2;

/**
 * @author YCKJ-GaoJT
 * @create 2020-11-20 10:59
 **/
public class TaxCalaculator {
    private final double salary;
    private final double bonus;
    private CalculatorStrategy calculatorStrategy;
    public TaxCalaculator(double salary, double bonus) {
        this.salary = salary;
        this.bonus = bonus;
    }
    protected double calcTax() {
        return calculatorStrategy.calculate(salary,bonus);
    }
    public double calculate() {
        return this.calcTax();
    }

    public double getSalary() {
        return salary;
    }

    public double getBonus() {
        return bonus;
    }

    public void setCalculatorStrategy(CalculatorStrategy calculatorStrategy) {
        this.calculatorStrategy = calculatorStrategy;
    }
}

 

接口定义:

package com.wangwenjun.concurrency.chapter2;
@FunctionalInterface
public interface CalculatorStrategy {
    double calculate(double salary, double bonus);
}

 

接口实现:

package com.wangwenjun.concurrency.chapter2;
public class SimpleCalculatorStrategy implements  CalculatorStrategy {
    private static final double SALARY_PATE = 0.1;
    private static final double BONUS_RATE = 0.1;

    @Override
    public double calculate(double salary, double bonus) {
        return salary * SALARY_PATE + bonus * BONUS_RATE;
    }
}

 

调用:

package com.wangwenjun.concurrency.chapter2;
public class TaxCalculatorMain {
    public static void main(String[] args) {
        TaxCalaculator calaculator = new TaxCalaculator(10000d, 2000d);
        SimpleCalculatorStrategy strategy = new SimpleCalculatorStrategy();
        calaculator.setCalculatorStrategy(strategy);
        System.out.println(calaculator.calculate());
    }
}

 

其实上面我们可以用lamda简化,还可以进一步简化,在第一个类中,不用设置set接口字段,直接在构造方法中传入,此时调用时再配合lambda表达式就更加方便了。

5、Thread API详细介绍

 

6、线程同步,锁技术

 

7、如何优雅的停止线程

 

8、线程间通讯

 

9、线程组详细介绍

 

10、线程池原理以及实现一个简单的线程池

 

11、线程异常捕获以及线程堆栈信息详细讲解

 

12、FIFO队列以及线程环境下的运行

 

13、BoolenLock锁实现

 

14、常用设计模式在多线程环境下的使用

 

15、查缺补漏

 

posted @ 2020-11-19 10:59  峡谷挨打记  阅读(108)  评论(0编辑  收藏  举报