zoukankan      html  css  js  c++  java
  • 阶段一: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、查缺补漏

  • 相关阅读:
    spider-抓取页面内容
    Zabbix监控
    时间戳转换
    计算机脱域
    查询指定时间内审核失败的事件日志
    spider-抓取网页内容(Beautiful soup)
    Queue
    spider-抓取网页内容
    MyEclipse+Tomcat配置
    Gradle Java Web应用程序并在Tomcat上运行
  • 原文地址:https://www.cnblogs.com/G-JT/p/14004243.html
Copyright © 2011-2022 走看看