多线程开发,是属于Java语言特点之一,因为Java实现多线程非常的容易。

一、进程与线程,并行与并发与串行

无标题.png

1.1 进程与线程

进程

运行在操作系统上的程序,没有运行的程序不算进程。进程是计算机内存分配的最小单位。进程是程序的一种动态体现
进程:开启的浏览器,开启的Eclipse,开启的录屏……
进程要运行,一定需要CPU给它提供支持,支持它允许程序的代码
进程之间的切换,代价相对比较高!!!

线程

线程就是进程中最小任务执行单位,线程在进程内部,一般来讲,任何一个进程都需要包含至少一个以上的线程。
结合前面栈的概念,需要注意的:每个线程都有它自己的线程栈
回顾下:
JVM中有5大内存区间:栈,堆,元空间,PC寄存器,本地方法栈
线程所独有:栈,PC寄存器,本地方法栈
线程所共有:堆,元空间

1.2 并行与并发与串行

并行:同时执行 例如:大扫除时,在同一时刻,有人可以搽玻璃,有人可以扫地
只有进程之间才可以做到,比如:操作系统上,同时打开qq,微信,浏览器,Eclipse……
并发:交替执行 例如:红绿灯,在同一时刻,要么是红灯,要么是黄灯,要么是绿灯
只在进程内部才可以做到,比如:QQ上开启多个聊天窗口,但是你只能在同一时刻,与一个人聊天
串行:顺序执行 例如:单窗口排队买票,必须前一个人买完,下一个才能买

二、多线程

2.1 多线程的使用场景

要和硬件进行交互,例如:儿童手表,美团电动车,Hello单车,车载系统……
未来3年会有更多和硬件交互的东西,原因是:5G来了……物联网应用多了……
聊天室,多并发的系统(淘宝,京东……)

2.2 线程创建方式

2.2.1 继承Thread线程类

  1. package com.woniuxy.java24.study.thread;
  2. /**
  3. * 擦玻璃任务
  4. * @author Administrator
  5. *
  6. */
  7. public class ChaboliThread extends Thread{
  8. @Override
  9. public void run() {
  10. // TODO Auto-generated method stub
  11. System.out.println("进行擦玻璃的任务!!!!");
  12. }
  13. }
  1. package com.woniuxy.java24.study.thread;
  2. /**
  3. * 扫地任务
  4. * @author Administrator
  5. *
  6. */
  7. public class SaodiThread extends Thread{
  8. @Override
  9. public void run() {
  10. // TODO Auto-generated method stub
  11. System.out.println("扫地的任务!!!!");
  12. }
  13. }
  1. import com.woniuxy.java24.study.thread.ChaboliThread;
  2. import com.woniuxy.java24.study.thread.SaodiThread;
  3. public class MainEnter {
  4. /**
  5. * 主函数
  6. * @param args
  7. */
  8. public static void main(String[] args) {
  9. // TODO Auto-generated method stub
  10. System.out.println("主线程开启");
  11. // study01();
  12. study02();
  13. System.out.println("主线程结束");
  14. }
  15. /**
  16. * Thread子类方式
  17. */
  18. private static void study02() {
  19. // TODO Auto-generated method stub
  20. //定义实例
  21. Thread t1 = new SaodiThread();
  22. Thread t2 = new ChaboliThread();
  23. //开启线程
  24. t1.start();
  25. t2.start();
  26. }
  27. /**
  28. * 匿名内部类方案
  29. */
  30. private static void study01() {
  31. // TODO Auto-generated method stub
  32. //默认创建一个线程(匿名内部类)
  33. Thread t1 = new Thread() {
  34. @Override
  35. public void run() {
  36. // TODO Auto-generated method stub
  37. System.out.println("输出" + this.getName());
  38. }
  39. };
  40. Thread t2 = new Thread() {
  41. @Override
  42. public void run() {
  43. // TODO Auto-generated method stub
  44. System.out.println("输出" + this.getName());
  45. }
  46. };
  47. //启动线程
  48. t1.start();
  49. t2.start();
  50. }
  51. }

2.2.2 实现Runable接口

  1. package com.woniuxy.java24.study.thread;
  2. /**
  3. * 擦玻璃任务
  4. * @author Administrator
  5. *
  6. */
  7. public class ChaboliTask implements Runnable{
  8. @Override
  9. public void run() {
  10. // TODO Auto-generated method stub
  11. System.out.println("进行擦玻璃的任务!!!!");
  12. }
  13. }
    package com.woniuxy.java24.study.thread;
    /**
     * 扫地任务
     * @author Administrator
     *
     */
    public class SaodiTask implements Runnable{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            System.out.println("扫地的任务!!!!");
        }
    }
    package com.woniuxy.java24.study;
    import com.woniuxy.java24.study.thread.ChaboliTask;
    import com.woniuxy.java24.study.thread.SaodiTask;
    public class MainEnter {
        /**
         * 主函数
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("主线程开启");
    //        study01();
            study02();
            System.out.println("主线程结束");
        }
        /**
         * Thread子类方式
         */
        private static void study02() {
            // TODO Auto-generated method stub
            //定义实例(方式一)
    //        Thread t1 = new Thread(new ChaboliTask());
    //        Thread t2 = new Thread(new SaodiTask());
            //定义实例(方式一)
            Thread t1 = new Thread(() ->{ 
                System.out.println("开启扫地任务");
            });
            Thread t2 = new Thread(() ->{ 
                System.out.println("开启搽玻璃任务");
            });
            //开启线程
            t1.start();
            t2.start();
        }
        /**
         * 匿名内部类方案
         */
        private static void study01() {
            // TODO Auto-generated method stub
            //默认创建一个线程(匿名内部类)
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("输出" + this.getName());
                }
            };
            Thread t2 = new Thread() {
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("输出" + this.getName());
                }
            };
            //启动线程
            t1.start();
            t2.start();
        }
    }

2.2.3 继承Thread 和实现Runnable接口的比较

1、Runnable 接口的这种方式,可以有效的将线程 和任务 进行分离,就可以更精准的关注自身
2、Runnable 可以有效的避免Java单继承的局限型,因为Java只能单继承
工作中,肯定Runnable使用的较多!!!

2.3 线程的优先级

每个线程都有默认的优先级(5), 最小取值范围:1 最大取值范围为:10
线程的优先级,我们可以设置,但是设置了只是增大了,或者缩小了线程被CPU,所选中概率

2.4 Thread 中常用API

        private static void study04() {
            // TODO Auto-generated method stub
            //定义一个线程,任务:抽烟          线程的名字:蒲老师
            Thread t1 = new Thread(()->{
                System.out.println("正在执行的任务:抽烟");
            },"保安");
            //设置优先级
            t1.setPriority(8);
            System.out.println(t1.getPriority());
            //获取线程的ID
            System.out.println("线程ID:" + t1.getId());
            try {
                //休眠5秒钟(时间到了,自动苏醒)
                Thread.sleep(5000);//单位是毫秒
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //给线程定义一个名称
    //        t1.setName("蒲老师");
            System.out.println("线程名称:" + t1.getName());
            System.out.println("线程的状态:" + t1.getState());
            System.out.println("判断线程是否被激活:" + t1.isAlive());
            System.out.println("当前正在执行的线程:" + Thread.currentThread());
            //开启线程
            t1.start();
            System.out.println("线程的状态:" + t1.getState());
            //关闭线程
            t1.interrupt();//t1.stop(); t1.destroy();
            //判断线程是否被中断
            System.out.println(t1.interrupted());
            System.out.println("线程的状态:" + t1.getState());
        }

2.5 线程的状态(面试)

无标题.png
5个状态:
NEW 新建,线程刚刚创建起来
Runnable 就绪,线程执行的准备工作已经完成,(调用start())
Running 运行,线程被CPU选中中,可以执行内部任务 (注意:可能每次执行完毕后,CPU就选择其他线程,当前线程又出于Runnable状态)
Blocked 阻塞,当线程内部调用:sleep() 或join()方法,当前线程都会进入到阻塞状态
Dead 死亡,当线程任务执行完毕之后,线程就死亡

2.6 线程状态之间的转变

2.6.1 线程方法之一:join()

join() 它是一个线程固定抢占CPU的方法,线程只要调用该方法,如果该线程被CPU选中,那么在它的执行时间范围内,CPU都不能被其他线程所持有

    package com.woniuxy.java24.study.thread;
    public class StateThread extends Thread{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 1; i <= 10; i ++) {
                for(int j = 1; j <= 10; j ++) {
                    System.out.println(this.getName() + "输出:你好!" + i*j );
                }
            }
            System.out.println("子线程执行完毕!!!!");
        }
    }
    package com.woniuxy.java24.study;
    import com.woniuxy.java24.study.thread.ChaboliThread;
    import com.woniuxy.java24.study.thread.SaodiThread;
    import com.woniuxy.java24.study.thread.StateThread;
    /**
     * 
     * @author Administrator
     *
     */
    public class MainEnter {
        /**
         * 主函数
         * 
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("主线程开启");
            study06();
            System.out.println("主线程结束");
        }
        private static void study06() {
            // TODO Auto-generated method stub
            Thread t1 = new StateThread();
            Thread t2 = new StateThread();
            Thread t3 = new StateThread();
            //启动
            t1.start();
            try {
                //join()可以做到,让其他线程等待 被调用join()的线程,执行完毕之后,才可以继续机制
                t1.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            t2.start();
            try {
                //持久CPU的执行时间:为5秒钟
                t2.join(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            t3.start();
        }
    }

2.6.2 线程方法之二:sleep()

sleep() 方法是 使线程休眠的方法,它将会让线程由Running 变为Blocked 状态,而且它在休眠期间,不会释放任何的线程资源 当休眠时间完毕之后,状态重新变为Runnable

    package com.woniuxy.java24.study.thread;
    public class StateThread extends Thread{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 1; i <= 100; i ++) {
                for(int j = 1; j <= 10000; j ++) {
                    System.out.println(this.getName() + "输出:你好!" + i*j );
                }
                try {
                    //休眠3秒钟(被阻塞:线程的状态:由Running 变为Blocked)
                    this.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("子线程执行完毕!!!!");
        }
    }
        private static void study05() {
            // TODO Auto-generated method stub
            // 创建一个线程
            Thread t1 = new StateThread();
            // 此时都是出于New(新建)的状态
            System.out.println(t1.getName() + "当前线程的状态:" + t1.getState());
            t1.start();// 可以进入到Runnable(就绪)
            // 创建一个线程
            Thread t2 = new StateThread();
            // 此时都是出于New(新建)的状态
            System.out.println(t2.getName() + "当前线程的状态:" + t2.getState());
            t2.start();// 可以进入到Runnable(就绪)
        }

2.6.3 线程方法之三:yield()

yield()方法,是一个可以移交运行权限的方法,它移交运行权利之后,它的状态不像sleep()处于阻塞,它是直接由Running 变成Runnable() ,重新抢占CPU的执行权。
类似:心机婊

    package com.woniuxy.java24.study.thread;
    public class StateThread extends Thread{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 1; i <= 100; i ++) {
                for(int j = 1; j <= 10000; j ++) {
                    System.out.println(this.getName() + "输出:你好!" + i*j );
                    //移交当前线程的执行权(并立马可以抢回)
                    this.yield();
                }
            }
            System.out.println("子线程执行完毕!!!!");
        }
    }
        private static void study05() {
            // TODO Auto-generated method stub
            // 创建一个线程
            Thread t1 = new StateThread();
            // 此时都是出于New(新建)的状态
            System.out.println(t1.getName() + "当前线程的状态:" + t1.getState());
            t1.start();// 可以进入到Runnable(就绪)
            // 创建一个线程
            Thread t2 = new StateThread();
            // 此时都是出于New(新建)的状态
            System.out.println(t2.getName() + "当前线程的状态:" + t2.getState());
            t2.start();// 可以进入到Runnable(就绪)
        }

2.7 同步 与 异步

    package com.woniuxy.java24.bean;
    import java.io.Serializable;
    /**
     * 火车票
     * @author Administrator
     *
     */
    public class TrainTicket implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -8622387422479554333L;
        /**
         * 从哪儿出发
         */
        private String from;
        /**
         * 到哪儿去
         */
        private String to;
        /**
         * 火车票的张数
         */
        private int nums;
        public TrainTicket() {
            super();
            // TODO Auto-generated constructor stub
        }
        public TrainTicket(String from, String to, int nums) {
            super();
            this.from = from;
            this.to = to;
            this.nums = nums;
        }
        public String getFrom() {
            return from;
        }
        public void setFrom(String from) {
            this.from = from;
        }
        public String getTo() {
            return to;
        }
        public void setTo(String to) {
            this.to = to;
        }
        public int getNums() {
            return nums;
        }
        public void setNums(int nums) {
            this.nums = nums;
        }
        @Override
        public String toString() {
            return "TrainTicket [from=" + from + ", to=" + to + ", nums=" + nums + "]";
        }
    }
    package com.woniuxy.java24.study.thread;
    import java.io.Serializable;
    import com.woniuxy.java24.bean.TrainTicket;
    /**
     * 站台的窗口 卖票任务
     * 
     * @author Administrator
     *
     */
    public class WindowsTask implements Runnable, Serializable {
        /**
         * 
         */
        private static final long serialVersionUID = -552270909659932647L;
        /**
         * 窗体的名称
         */
        private String name;
        /**
         * 火车票
         */
        private TrainTicket tt;
        /**
         * 卖票任务
         */
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(tt.getNums() > 0) {
                System.out.println(name + "说:还有" + tt.getNums() + "张票!!!");
                tt.setNums(tt.getNums() - 1);
                try {
                    //每卖出一张,休眠一秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(tt.getNums() <= 0) {
                System.out.println(name + "说:售票结束!!!!");
            }
        }
        public WindowsTask(String name, TrainTicket tt) {
            super();
            this.name = name;
            this.tt = tt;
        }
        public WindowsTask() {
            super();
            // TODO Auto-generated constructor stub
        }
        public TrainTicket getTt() {
            return tt;
        }
        public void setTt(TrainTicket tt) {
            this.tt = tt;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "WindowsTask [name=" + name + ", tt=" + tt + "]";
        }
    }
        private static void study07() {
            // TODO Auto-generated method stub
            //火车站发布的票务信息
            TrainTicket tt = new TrainTicket("成都东", "东莞西", 200);
            //定义多个线程
            Thread t1 = new Thread(new WindowsTask("窗口1号", tt));
            Thread t2 = new Thread(new WindowsTask("窗口2号", tt));
            Thread t3 = new Thread(new WindowsTask("窗口3号", tt));
            //启动
            t1.start();
            t2.start();
            t3.start();
        }

执行结果:

    主线程开启
    窗口1号说:还有200张票!!!
    主线程结束
    窗口3号说:还有199张票!!!
    窗口2号说:还有200张票!!!
    窗口3号说:还有197张票!!!
    窗口1号说:还有196张票!!!
    窗口2号说:还有195张票!!!
    窗口3号说:还有194张票!!!
    窗口2号说:还有193张票!!!
    窗口1号说:还有192张票!!!
    窗口3号说:还有191张票!!!
    窗口2号说:还有190张票!!!
    窗口1号说:还有190张票!!!

分析: 由于多根线程同时操作,同一数据,又加上线程之间默认使用的是一种 异步通讯机制,这就导致有可能存在:线程拿到手上的数据,不准确的情况
这就可能危及到线程内部执行的任务,从而体现一种情况:线程不安全
解决方案
就是将异步,调整为同步。之后的效果:对于同一个数据,单位时间内,只能拥有1根线程操作该数据。

2.7.1 synchronized关键字

同步块
针对某一块 代码进行加锁处理,语法如下:

    synchronized (取得锁的对象){
    //要锁定的代码
    }

例如:

    package com.woniuxy.java24.study.thread;
    import java.io.Serializable;
    import com.woniuxy.java24.bean.TrainTicket;
    /**
     * 站台的窗口 卖票任务
     * 
     * @author Administrator
     *
     */
    public class WindowsTask implements Runnable, Serializable {
        /**
         * 
         */
        private static final long serialVersionUID = -552270909659932647L;
        /**
         * 窗体的名称
         */
        private String name;
        /**
         * 火车票
         */
        private TrainTicket tt;
        /**
         * 卖票任务
         */
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(tt.getNums() > 0) {
                /*同步块*/
                synchronized (tt) {
                    if(tt.getNums() > 0) {
                        //需要锁住的代码
                        System.out.println(Thread.currentThread().getName()  + "说:还有" + tt.getNums() + "张票!!!");
                        tt.setNums(tt.getNums() - 1);
                        System.out.println(Thread.currentThread().getName() + "说:卖出1张票!!!");
                        try {
                            //每卖出一张,休眠一秒
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
            if(tt.getNums() <= 0) {
                System.out.println(name + "说:售票结束!!!!");
            }
        }
        public WindowsTask(String name, TrainTicket tt) {
            super();
            this.name = name;
            this.tt = tt;
        }
        public WindowsTask() {
            super();
            // TODO Auto-generated constructor stub
        }
        public TrainTicket getTt() {
            return tt;
        }
        public void setTt(TrainTicket tt) {
            this.tt = tt;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "WindowsTask [name=" + name + ", tt=" + tt + "]";
        }
    }
    package com.woniuxy.java24.bean;
    import java.io.Serializable;
    /**
     * 火车票
     * @author Administrator
     *
     */
    public class TrainTicket implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -8622387422479554333L;
        /**
         * 从哪儿出发
         */
        private String from;
        /**
         * 到哪儿去
         */
        private String to;
        /**
         * 火车票的张数
         */
        private int nums;
        public TrainTicket() {
            super();
            // TODO Auto-generated constructor stub
        }
        public TrainTicket(String from, String to, int nums) {
            super();
            this.from = from;
            this.to = to;
            this.nums = nums;
        }
        public String getFrom() {
            return from;
        }
        public void setFrom(String from) {
            this.from = from;
        }
        public String getTo() {
            return to;
        }
        public void setTo(String to) {
            this.to = to;
        }
        public int getNums() {
            return nums;
        }
        public void setNums(int nums) {
            this.nums = nums;
        }
        @Override
        public String toString() {
            return "TrainTicket [from=" + from + ", to=" + to + ", nums=" + nums + "]";
        }
    }
    private static void study07() {
        // TODO Auto-generated method stub
        //火车站发布的票务信息
        TrainTicket tt = new TrainTicket("成都东", "东莞西", 20);
        //定义多个线程
        Thread t1 = new Thread(new WindowsTask("窗口1号", tt));
        Thread t2 = new Thread(new WindowsTask("窗口2号", tt));
        Thread t3 = new Thread(new WindowsTask("窗口3号", tt));
        //启动
        t1.start();
        t2.start();
        t3.start();
    }

代码的执行效果:

    主线程开启
    主线程结束
    Thread-0说:还有20张票!!!
    Thread-0说:卖出1张票!!!
    Thread-2说:还有19张票!!!
    Thread-2说:卖出1张票!!!
    Thread-2说:还有18张票!!!
    Thread-2说:卖出1张票!!!
    Thread-1说:还有17张票!!!
    Thread-1说:卖出1张票!!!
    Thread-1说:还有16张票!!!
    Thread-1说:卖出1张票!!!
    Thread-2说:还有15张票!!!
    Thread-2说:卖出1张票!!!
    Thread-0说:还有14张票!!!
    Thread-0说:卖出1张票!!!
    Thread-0说:还有13张票!!!
    Thread-0说:卖出1张票!!!
    Thread-0说:还有12张票!!!

2.7.2 ReentrantLock 上锁类

一般多线程开发中,如果需要针对相同的数据进行上锁,除了synchronized关键字以外,还有ReentrantLock 类的实例也可以

    package com.woniuxy.java24.study.thread;
    import java.util.Random;
    import java.util.concurrent.locks.ReentrantLock;
    import com.woniuxy.java24.bean.GoodsBean;
    public class BuyTask implements Runnable {
        /**
         * 定义一把锁
         */
        private ReentrantLock lock = new ReentrantLock();
        /**
         * 商品
         */
        private GoodsBean goodsBean;
        public BuyTask(GoodsBean goodsBean) {
            super();
            this.goodsBean = goodsBean;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            // 让后面的线程,可以参与购买
            Random random = new Random();
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 商品进行上锁
            lock.lock();
            try {
                System.out.println("客户说:当前还有" + goodsBean.getNums() + "件商品!");
                if (goodsBean.getNums() > 0) {
                    goodsBean.setNums(goodsBean.getNums() - 1);
                    System.out.println(Thread.currentThread().getName() + ": 我买了1件!!!");
                } else {
                    System.out.println("客户说:商品已售罄,请期待明天上午8:00 抢购活动!");
                }
            }catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }finally {
                //解锁代码
                lock.unlock();
            }
        }
    }

2.7.3 synchronized 与 lock 的区别(面试题)

1、synchronized 即可以写同步块,也可以写同步方法,但是lock只能适用用在同步块
2、synchronized 是一种重量级锁,它锁的底层CPU。lock 是相对较轻量级的一种锁,它锁的是对象(它是一种上层锁)
3、synchronized 不需要程序员手动解锁,而lock需要程序员适用unlock()进行手动解锁
synchronized 使用到了一种 概念(悲观锁)
lock 使用到了一种 概念(乐观锁)
悲观锁:每个线程,在操作数据时,都悲观的认为,有其他线程和它进行同时操作某一个数据,这时,为了保证线程的安全性,于是他就针对对象结合CPU针对底层进行上锁,而且它上的是一个 非公平锁(排他锁)
无标题.png
乐观锁:每个线程,在操作数据时,都相对乐观的认为,没有其他线程和它在同一时刻,操作同一数据,于是:它加了一个相对比较宽松的锁(锁:任何人都能访问,但是只能由1个人修改,要修改就需要保证自己手上数据是最新的)
无标题.png
当然工作上:菜鸟就是用synchronized,高手就使用lock(性能较高)
两种锁的具体应用场景
synchronized 应用于 系统的使用人数较少的系统,或者应用于非常重要的,而且不能让其他人同时操作的数据
lock 应用于系统的使用人数较多的系统,可让大量人的看,但是只能少量的人操作,而且每次操作都需要判断锁中的代码的变化情况
推荐:使用lock