美文网首页
JAVA多线程与并发(基础)

JAVA多线程与并发(基础)

作者: 8090的大叔 | 来源:发表于2020-04-14 01:50 被阅读0次

    了解多线程之前,对一些基本概念进行理解。


    共享资源

    进程:系统进行资源分配和调度的一个独立单位,是代码在一个数据集合上的一次运行活动。我们启动的Java程序,就会产生一个java.exe的进程。
    线程:系统能够进行运算调度的最小单位,包含在进程中,一个进程可以并发多个线程,每个线程可以执行不同的任务。线程本身只拥有的资源很少(计数器、寄存器、栈),同一线程中多个进程共享进程的资源。

    并行:同一时间内多个任务在多个CPU上同时执行,真正意义上的同时执行。
    并发:通过CPU调度算法,让用户感觉同一时间段内多个任务都在执行,且并没有执行结束。以单CPU为例,为多个线程分配CPU时间片运行,实际上CPU操作层面上并不是真正的同时执行,而是快速轮换执行。

    线程安全性:并发情况下,线程调度顺序不影响程序执行结果,即为线程安全的;
    并发情况下,线程调度顺序影响了执行结果,即为线程不安全的;

    同步:排队,并发情况下,对于共享资源的读写访问时需要排队访问,才能保证其线程安全性。(synchronized、volatile、Reentrantlock)

    一、多线程实现方式

    多线程有两种实现方式,继承Thread类和实现Runnable / Callable 接口,通过源码我们可以看到Thread也是实现了Rannable接口,两者的区别就在于,使用Thread类方式创建新的线程,就不支持多继承了。本质上没有其他区别。

    具体区别
    Thread: 不支持多继承;代码不能被共享;
    Runnable: 避免了单继承的局限性;适合多个相同个程序代码的县城区处理同一资源情况;不能返回值
    Callable: 避免了单继承的局限性;可返回值(阻塞)

    线程的几种状态

    线程状态图,摘自博客园

    NEW:初始状态,刚被创建,但还未启动(未start);
    RUNNABLE:线程正常运行中,当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等;
    BLOCKED:阻塞状态,等待另一个线程的synchronized块的执行释放,也就是线程在等待进入的临界区;
    WAITING: 等待状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify;
    TIMED-WAITING:有时限的等待,例如sleep(10000),这10秒都为这个状态;
    TERMINATED:线程run()方法执行完毕;

    1.Thread类 (java.lang 包)

    以下代码使用Thread类实现多线程,职位展示部分状态信息。

    public class AppThreads extends Thread {
        @Override
        public void run() {
            super.run();
            //打印线程基本信息 Thread[Name 线程名,Priority 优先级,group 线程组]
            System.out.println(this+";线程ID:"+getId());
            System.out.println("【状态】"+getState()+";接下来10秒进入休眠;执行时间:"+new Date());
                try {
                    sleep(10000);   //休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            System.out.println("appThreads1线程执行结束"+";执行时间:"+new Date());
        }
        public static void main(String[] args) throws Exception {
            AppThreads appThreads1 = new AppThreads();  //创建线程类
            System.out.println("【状态】"+appThreads1.getState()+";执行时间:"+new Date());
            appThreads1.start();    //启动线程
            sleep(5000);   //main方法等待5秒
            System.out.println("第6秒线程【状态】"+appThreads1.getState()+";执行时间:"+new Date());
            System.out.println("main主线程等待appThreads1线程结束"+";执行时间:"+new Date());
            appThreads1.join(); //main主线程调用 appThreads1 线程的 join()方法,当前线程进入阻塞状态,直到 appThreads1 线程结束才回复运行
            System.out.println("main主线程结束"+";执行时间:"+new Date());
            System.out.println("【状态】"+appThreads1.getState()+";执行时间:"+new Date());
        }
    }
    

    控制台打印结果
    Thread[Thread-0,5,main];线程ID:12
    main执行时间:Mon Apr 13 23:46:22 CST 2020;【状态】NEW
    run 执行时间:Mon Apr 13 23:46:22 CST 2020;【状态】RUNNABLE;接下来10秒进入休眠
    main执行时间:Mon Apr 13 23:46:27 CST 2020;第5秒线程【状态】TIMED_WAITING
    main执行时间:Mon Apr 13 23:46:27 CST 2020;main主线程等待appThreads1线程结束
    run 执行时间:Mon Apr 13 23:46:32 CST 2020;appThreads1线程执行结束
    main执行时间:Mon Apr 13 23:46:32 CST 2020;main主线程结束
    main执行时间:Mon Apr 13 23:46:32 CST 2020;【状态】TERMINATED

    2.Runnable类 (java.lang 包)

    模拟小明、小红、小蓝三人一起购买商品,并发情况下商品库存为10个。

    package Test;
    import java.util.Date;
    class BuyGoods implements Runnable{
        static int goodsNum = 10;     //初始化10件商品
        private String personName;  //购买人
        private int buyNum = 0; //购买数量
        public BuyGoods(Person person){
            this.personName = person.name;
            this.buyNum = person.buyNum;
        }
        //获取当前库存
        static int getGoodsNum(){
            return goodsNum;
        }
        //购买商品
        synchronized static void buyGoods(String threadName,int num) {
            System.out.println("执行时间:"+new Date()+";"+threadName+" 准备购买 "+num+" 件商品,当前库存:"+getGoodsNum());
            if(goodsNum==0){    //商品买完时不再出售
                throw new NullPointerException();
            }
            goodsNum--;
            System.out.println("执行时间:"+new Date()+";"+threadName+" 开始购买第 "+num+" 件商品");
            System.out.println("执行时间:"+new Date()+";"+threadName+"购买第 "+num+" 件商品完成,当前库存:"+getGoodsNum());
            System.out.println();
        }
        @Override
        public void run() {
            Thread thread = Thread.currentThread();   //Thread.currentThread() 获取当前对象线程的引用
    
            for(int i=1;i<=this.buyNum;i++){
                try{
                    buyGoods(this.personName,i);
                }catch (NullPointerException e){
                    System.out.println("商品已经售空,"+this.personName+",没有购买到第 "+i+" 件商品");
                    break;
                }
            }
        }
    }
    
    /**
     * 购买人
     */
    class Person{
        String name;    //姓名
        int buyNum;     //购买数量
        public Person(String name,int buyNum){
            this.name = name;
            this.buyNum = buyNum;
        }
    }
    
    public class AppRunnable{
        public static void main(String[] args) throws Exception{
            //创建三个购买人
            Person personA = new Person("小明",5);
            Person personB = new Person("小红",2);
            Person personC = new Person("小蓝",6);
            Thread thread1 = new Thread(new BuyGoods(personA));
            Thread thread2 = new Thread(new BuyGoods(personB));
            Thread thread3 = new Thread(new BuyGoods(personC));
            thread1.start();
            thread2.start();
            thread3.start();
            thread1.join();     //等待购买完成
            thread2.join();     //等待购买完成
            thread3.join();     //等待购买完成
            System.out.println("所有人购买完成,当前库存:"+ BuyGoods.getGoodsNum());
        }
    }
    

    控制台打印结果
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 1 件商品,当前库存:10
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【开始购买】第 1 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【购买完成】第 1 件商品,当前库存:9
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 1 件商品,当前库存:9
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 1 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 1 件商品,当前库存:8
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 2 件商品,当前库存:8
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 2 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 2 件商品,当前库存:7
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 3 件商品,当前库存:7
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 3 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 3 件商品,当前库存:6
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 4 件商品,当前库存:6
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 4 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 4 件商品,当前库存:5
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 5 件商品,当前库存:5
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 5 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 5 件商品,当前库存:4
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 6 件商品,当前库存:4
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 6 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 6 件商品,当前库存:3
    执行时间:Tue Apr 14 11:29:30 CST 2020;小红【准备购买】 1 件商品,当前库存:3
    执行时间:Tue Apr 14 11:29:30 CST 2020;小红【开始购买】第 1 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小红【购买完成】第 1 件商品,当前库存:2
    执行时间:Tue Apr 14 11:29:30 CST 2020;小红【准备购买】 2 件商品,当前库存:2
    执行时间:Tue Apr 14 11:29:30 CST 2020;小红【开始购买】第 2 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小红【购买完成】第 2 件商品,当前库存:1
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 2 件商品,当前库存:1
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【开始购买】第 2 件商品
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【购买完成】第 2 件商品,当前库存:0
    执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 3 件商品,当前库存:0
    商品已经售空,小明,没有购买到第 3 件商品
    所有人购买完成,当前库存:0

    3.Callable类 (java.util.concurrent 包)

    同样以购买商品为例,Callable可以通过Future.get()方法获取call方法返回值,并且call()方法中的异常,我们可以向外抛出,而不是像Runnable中的run()方法,自行处理。

    package MyCode;
    
    import java.util.Date;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    class BuyGoods2 implements Callable {
        static int goodsNum = 10;     //初始化10件商品
        private String personName;  //购买人
        private int buyNum = 0; //购买数量
        public BuyGoods2(Person2 person){
            this.personName = person.name;
            this.buyNum = person.buyNum;
        }
        //获取当前库存
        static int getGoodsNum(){
            return goodsNum;
        }
        //购买商品
        synchronized static void buyGoods(String threadName,int num) {
            System.out.println("执行时间:"+new Date()+";"+threadName+"【准备购买】 "+num+" 件商品,当前库存:"+getGoodsNum());
    
            if(goodsNum==0){    //商品买完时不再出售
                throw new NullPointerException();
            }
            System.out.println("执行时间:"+new Date()+";"+threadName+"【开始购买】第 "+num+" 件商品");
            goodsNum--;
            System.out.println("执行时间:"+new Date()+";"+threadName+"【购买完成】第 "+num+" 件商品,当前库存:"+getGoodsNum());
        }
        @Override
        public Object call() {
            int i = 0;
            for(i=1;i<=this.buyNum;i++){
                try{
                    buyGoods(this.personName,i);
                }catch (NullPointerException e){
                    System.out.println("商品已经售空,"+this.personName+",没有购买到第 "+i+" 件商品");
                    return ""+this.personName+" 购买了 " + (i-1) + "件商品";
                }
                try {
                    Thread.sleep(100);  //休息一下
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return this.personName+" 购买了 " + (i-1) + "件商品";
        }
    }
    
    /**
     * 购买人
     */
    class Person2{
        String name;    //姓名
        int buyNum;     //购买数量
        public Person2(String name,int buyNum){
            this.name = name;
            this.buyNum = buyNum;
        }
    }
    
    public class AppCallable{
        public static void main(String[] args) throws Exception{
            //创建三个购买人
            Person2 personA = new Person2("小明",4);
            Person2 personB = new Person2("小红",2);
            Person2 personC = new Person2("小蓝",5);
            BuyGoods2 buyGoods1 = new BuyGoods2(personA);
            BuyGoods2 buyGoods2 = new BuyGoods2(personB);
            BuyGoods2 buyGoods3 = new BuyGoods2(personC);
            // 创建一个执行任务的服务
            ExecutorService es = Executors.newFixedThreadPool(3);
            Future future1 = es.submit(buyGoods1);
            Future future2 = es.submit(buyGoods2);
            Future future3 = es.submit(buyGoods3);
            // 如果调用get方法,当前线程会等待任务执行完毕后才往下执行
            System.out.println("buyGoods1: " + future1.get());
            System.out.println("buyGoods2: " + future2.get());
            System.out.println("buyGoods3: " + future3.get());
            System.out.println("所有人购买完成,当前库存:"+BuyGoods2.getGoodsNum());
            // 停止任务执行服务
            es.shutdownNow();
        }
    }
    

    控制台执行结果
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 1 件商品,当前库存:10
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 1 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 1 件商品,当前库存:9
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 1 件商品,当前库存:9
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 1 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 1 件商品,当前库存:8
    执行时间:Tue Apr 14 11:50:25 CST 2020;小红【准备购买】 1 件商品,当前库存:8
    执行时间:Tue Apr 14 11:50:25 CST 2020;小红【开始购买】第 1 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小红【购买完成】第 1 件商品,当前库存:7
    执行时间:Tue Apr 14 11:50:25 CST 2020;小红【准备购买】 2 件商品,当前库存:7
    执行时间:Tue Apr 14 11:50:25 CST 2020;小红【开始购买】第 2 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小红【购买完成】第 2 件商品,当前库存:6
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 2 件商品,当前库存:6
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 2 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 2 件商品,当前库存:5
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 2 件商品,当前库存:5
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 2 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 2 件商品,当前库存:4
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 3 件商品,当前库存:4
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 3 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 3 件商品,当前库存:3
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 3 件商品,当前库存:3
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 3 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 3 件商品,当前库存:2
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 4 件商品,当前库存:2
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 4 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 4 件商品,当前库存:1
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 4 件商品,当前库存:1
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 4 件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 4 件商品,当前库存:0
    buyGoods1: 小明 购买了 4件商品
    buyGoods2: 小红 购买了 2件商品
    执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 5 件商品,当前库存:0
    商品已经售空,小蓝,没有购买到第 5 件商品
    buyGoods3: 小蓝 购买了 4件商品
    所有人购买完成,当前库存:0

    3.线程安全性

    上面程序中,使用了synchronized进行方法同步,来保证线程是安全的,同时间只能有一个人进行购买操作。那就线程安全性问题,记录一些知识点。

    三个特性

    原子性:不可分(同生共死);互斥访问,同一时刻只能有一个线程进行操作。(atomic,synchronized)
    可见性:一个线程修改了共享变量后,其他线程可以即时观察到。(synchroinzed,volatile)
    有序性:一个线程观察另一个线程的执行顺序时,由于指令重排序,观察结果是无序的。

    volatile 和 synchronized、ReentrantLock

    特性的比较
    volatile:具有有序性、可见性,不具有原子性,锁变量;
    synchronized:具有原子性、有序性、可见性,锁对象和类;
    ReentrantLock:具有原子性、有序性、可见性;

    可重入锁:可重复可递归调用,线程获取了对象锁之后,可以再次获取对象的锁,儿其他线程不可以(ReentrantLock、syntronized)。通过计数器实现,调用一次计数器+1,释放一次计数器-1。
    不可重入锁:不可递归调用,调用就死锁。

    synchronized锁
    对象锁:一个线程得到对象锁,其他线程可以访问没有进行同步的方法或者代码,同步方法和非同步方法互不影响。
    类锁:一个线程得到了类锁,其他线程调用该类就会被阻塞。

    ReentrantLock: ReentrantLock构造器提供了一个boolean值,来选择是一个公平锁(fair)还是不公平锁(unfair)。公平锁按照线程请求锁的顺序一次获得锁。
    需要手动声明来加锁和释放。(finally中声明释放锁)
    synchronized:不公平锁,不保证下一个次是哪个线程获得锁,并且由编译器去保证加锁和释放动作。

    相关文章

      网友评论

          本文标题:JAVA多线程与并发(基础)

          本文链接:https://www.haomeiwen.com/subject/jsvdmhtx.html