首页 > 其他 > 详细

2.JUC案例实战篇(1-5)

时间:2021-01-27 09:55:22      阅读:22      评论:0      收藏:0      [点我收藏+]

2 JUC案例实战篇

在学习之前,让我们先回忆一下什么是进程/线程?

进程:进行是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元

线程: 通常在一个进程中科院包含若干个线程,当然一个进程至少有一个线程,不然没有存在意义了。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常把进程作为分配资源基本单位,而吧线程作为独立运行和独立调度基本单位,由于线程比进程小,基本上不拥有系统资源,故,对他的调度所付出的开销就会小很多,能更高效的提高系统多个程序间并发执行的程度。

2.1卖票复习

题目描述:

题目: 三个售票员  卖出 30张票
*  要求: 如何编写企业级多线程代码
* 1.在高内聚低耦合的情况, 线程 操作 资源类

1)创建一个资源类

class Ticket{//资源类
    //票
    private int number = 30;

    public synchronized void saleTicket(){
        if (number > 0){
            System.out.println(Thread.currentThread().getName()+"\t卖出第:"+(number--)+"\t还剩下:"+number);
        }
    }
}
  1. 编写销售代码(初学者写法)
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 40; i++) {
                    ticket.saleTicket();
                }
            }
        },"A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 40; i++) {
                    ticket.saleTicket();
                }
            }
        },"B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 40; i++) {
                    ticket.saleTicket();
                }
            }
        },"C").start();
    }
}

运行结果:

技术分享图片

通过结果可以发现,程序是正常运行的,因为我们在资源类Ticket中的方法添加了synchronized关键字,若去掉关键字,则会出现剩余票数负数的情况。


通过上面,我们也可以发现,这是初级入门选手所写的,下面我将为大家介绍一种新的方法,不采用synchronized关键字实现,并发操作。

资源类

//资源类 = 实例变量 + 实例方法
class Ticket{
    //票
    private int number = 30;
    Lock lock  = new ReentrantLock(); // 可重入锁,具体理论在第一章节介绍过

    public void sale(){
        lock.lock(); //上锁
        try {
            if (number > 0){
                System.out.println(Thread.currentThread().getName()+"\t卖出第:"+(number--)+"\t还剩下:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock(); // 解锁
        }
    }
}

编写主程序,采用Lambda表达式实现。

public class SaleTicketDemo1 {
    //主线程,一切程序的入口
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(()->{for (int i = 1; i <= 40; i++) ticket.sale();},"A").start();
        new Thread(()->{for (int i = 1; i <= 40; i++) ticket.sale();},"B").start();
        new Thread(()->{for (int i = 1; i <= 40; i++) ticket.sale();},"C").start();
    }
}

最终是实现结果与方法一实现的结果是一致的,下面我们先来看看,为什么

new Thread()可以这么写

技术分享图片

通过查看Runnable接口,我们发现它是一个函数式接口,因此可以使用lambda表达式,后续我们会介绍函数接口

多线程的实现主要有四种 (具体参考我另外一篇博客《Java多线程编程核心技术》第一章节

  • 继承Thread类
  • 实现Runnable接口
  • 使用Callable接口,需要采用FutureTask类实现中间传递功能,默认带返回值
  • 线程池实现

2.2LambdaExpress

下面我们来简单学习一下,Lambda简单的函数式编程方法

1. 函数式编程
 *int age =23;
 *   法则: 拷贝小号,写死右箭头,落地大括号
 * 2. @FunctionalInterface : 函数式接口标志
 * 3. default :
 * 4. static

下面用案例说明

案例一:

资源类

interface Foo{
    public String sayHello(String name);
}

实现方法

public class LambdaExpressDemo02 {
    public static void main (String[] args) {
        Foo f =new Foo() {
            @Override
            public void sayHello () {
                System.out.println("****Hello word");
            }
        };
        f.sayHello();
        
        // Lambda 表达 
        Foo foo1 =()->{System.out.println("****Hello Lambda");};
        foo1.sayHello();
    }
}

输出结果

hello
hello Lambda

案例二: 自己写函数式接口

资源类,相较于原来,添加了一个add方法

interface Foo{
    public String sayHello(String name);
    public int add(int x,int y);
}

运行类

public class LambdaExpressDemo02 {
    public static void main (String[] args) {
        Foo f =new Foo() {
            @Override
            public String sayHello (String name) {
                System.out.println("你好,我是"+name);
                return "";
            }
            @Override
            public int add (int x, int y) {
                return 0;
            }
        };
        f.sayHello("Hugo");
        //lambda
        Foo foo =(String str) ->{System.out.println("hello word"+str); return str;}
    }
}

最终代码发生异常,如:

技术分享图片

这是什么原因呢? 这是因为我们前面提到了lambda表达里面只能有一个方法!!

但是如果我们需要写多个方法该怎么办呢?

那就需要修改我们的资源类了,

//显示声明函数接口
@FunctionalInterface //在资源类上面,添加一个函数式接口名字
interface Foo{
    public String sayHello(String name);
    public default int add(int x,int y){
        return x+y;
    }
}

技术分享图片

对代码进行如上操作【1,添加声明函数接口,2,方法添加default 关键字】以后,将可以直接使用sayHello方法。

技术分享图片

注: 若需要对add()方法进行操作,直接Foo.add(1,2)就可以实现了

2.3生产者消费者

问题描述:

/**
 * 题目:现在两个线程,可以操作初始值为零的一个变量,
 *      实现一个线程对该变量加1,一个线程对该变量-1,
 *               实现交替,来10轮,变量初始值为0.
 * 要求  1. 高内聚地耦合前提下, 线程操作资源类
 *      2. 判断/干活/通知 模板记忆
 *      3. 在多线程的世界里面,防止多线程的虚假唤醒 ,不能用  if(number!=0){this.wait(); }
 *      而是用 while(number!=0){this.wait(); }
 */

初级程序员写法:

资源类

class Aircondition{ // 资源类
    private int number = 0;

    //老版写法
    public synchronized void increment() throws Exception{
        //1.判断
        while (number != 0){
            this.wait(); //wait要和synchronized一起用
        }
        //2.干活
        number++;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //3通知
        this.notifyAll();
    }
    public synchronized void decrement() throws Exception{
        //1.判断
        while (number == 0){
            this.wait();
        }
        //2.干活
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //3通知
        this.notifyAll();
    }
}

运行类

public class ProdConsumerDemo4 {
    public static void main(String[] args) {
        Aircondition aircondition = new Aircondition();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

运行结果

技术分享图片

A生产一个,B马上消费一个,如此轮循,可以实现程序间的顺序执行!!

高级程序员写法:

资源类: 采用可重入锁来实现

class Aircondition{
    private int number = 0;
    private Lock lock = new ReentrantLock();//可重入,非公平的递归锁
    private Condition condition = lock.newCondition();

    //新版写法
    public void increment() throws Exception{
        lock.lock();
        try {
            //1.判断
            while (number != 0){
                condition.await(); //this.wait();
            }
            //2.干活
            number++;
            System.out.println(Thread.currentThread().getName()+"\t"+number);
            //3通知
            condition.signalAll(); //this.notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws Exception{
        lock.lock();
        try {
            //1.判断
            while (number == 0){
                condition.await(); //this.wait();
            }
            //2.干活
            number--;
            System.out.println(Thread.currentThread().getName()+"\t"+number);
            //3通知
            condition.signalAll(); //this.notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行类

public class ProdConsumerDemo4 {
    public static void main(String[] args) {
        Aircondition aircondition = new Aircondition();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    aircondition.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

知识小总结:多线程编程套路+while判断+新版写法(可重入锁Lock)

若用if判断会出现问题,需要用while判断来实现

2.4 信号量机制-精确通知

问题描述

多线程之间按顺序调用,实现A->B->C
 * 三个线程启动,要求如下:
 * AA打印5次,BB打印10次,CC打印15次
 * 接着
 * AA打印5次,BB打印10次,CC打印15次
 * 来10轮
 要求:
 *	   1.高内聚低耦合前提下,线程操作资源类
 *      2.判断/干活/通知
 *      3.多线程交互中,防止虚假唤醒(判断只能用while,不能用if)
 *      4.标志位

资源类:

class AirConditionPlus{
    private int number =0;
    private Lock lock =new ReentrantLock();//可重入,非公平的递归锁
    Condition condition = lock.newCondition();
    public  void increment() throws Exception{

        lock.lock();
        try{
            while(number!=0){
                condition.await();//this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+","+number);
            condition.notifyAll();//this.notifyAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    public  void decrement() throws Exception{
        //判断
        while (number ==0) {
            condition.await();//this.wait();
        }
        // 操作
        number--;
        System.out.println(Thread.currentThread().getName()+","+number);
        //通知
        condition.notifyAll();//this.notifyAll();
    }

}

运行类

/**
 * @author Hugo
 * @time 2021/1/1
 * 题目: 现在有两个线程,可以操作初始值为0的一个变量
 * 实现一个线程对该变量+1,一个线程对该变量-1
 * 实现交替,来10轮,保证初始值为0
 *
 *      1. 高内聚地耦合前提下, 线程操作资源类
 *      2. 判断/干活/通知 模板记忆
 *      3. 在多线程的世界里面,防止多线程的虚假唤醒 ,不能用  if(number!=0){this.wait(); }
 *      而是用 while(number!=0){this.wait(); }
 * 知识小结  = 多线程变成套路+while判断+新版写法(使用可重入锁Lock)
 *
 */
public class ConditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                shareData.printc1();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                shareData.printc2();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                shareData.printc3();
            }
        },"C").start();
    }
}

运行结果:

技术分享图片

2.5 八锁理论

问题描述: 八锁问题 内容过于冗余,建议放在IDEA中查看整个代码块

import java.util.concurrent.TimeUnit;

class Phone {
    public  static  synchronized void sendEmail () throws Exception {
       TimeUnit.SECONDS.sleep(4);
       System.out.println("**********sendEmail");
    }

    public  synchronized void sendSMS () throws Exception {
        System.out.println("**********sendSMS");
    }

    public  void sayHello () throws Exception {
        System.out.println("**********sayHello");
    }
}


/**
 * @author Hugo
 * @time 2020/12/31
 *    8lock problem
 *    1. 标准访问 先打印邮件还是打印短信  同一个手机 email
 *    2. 暂停4s再邮件方法,先打印邮件还是短信 email
 *          笔记:
 *          一个对象里面如果又多个synchronized方法,某一个时刻内,只要一个线程去调用其中一个synchronized方法了
 *          其他线程只能等待,换句话说,某一个时刻,只能有个唯一一个线程去访问这些synchronized方法
 *          线程先访问了资源类里面任何一个同步块,锁的不是该方法,而是整个Phone资源对象
 *          ==锁的是当前对象this,被锁定以后,其他线程都无法进入该对象的其他synchronized方法中
 *
 *    3. 新增普通sayHello 方法,先打印邮件还是Hello hello
 *          笔记:
 *          加一个普通方法,与同步锁无关了,则正常输出
 *    4. 两部手机,先打印邮件还是短信 SMS
 *          笔记:
 *          换成两个对象以后,不是同一把锁了,synchronized锁的是对象,但是两个对象都不一样了,所以锁不了了
 *
 *    5. 两个静态同步方法,同一手机,先打印邮件还是短信 email ,静态同步块只有一种,已经被锁了,必须等他用完才可以给其他用的
 *
 *    6. 两个静态同步方法,两个手机,先打印邮件还是短信 email
 *       笔记: static 锁的是整个类,锁的是全局 (假设,拥有两个手机就又两个this手机对象实例,当用static锁的时候,锁的不仅仅是this这两个对象,而是直接锁手机运行商,让所有手机都用不了)
 *          synchronized实现同步的基础,Java中的每一个对象都可以作为锁
 *          具体又三种形式:
 *             对于普通方法而言,锁的是当前实例对象 , 小入口
 *                  (链接上面)对于同步方法块而言,锁的是synchronized括号里面的对象,如:synchronized(this){}  和 synchronized void sendSms()一样的功能
 *             对于静态同步方法,锁的是当前类的Class对象
 *    7. 一个静态同步方法,1个给普通方法,同一手机 先打印邮件还是短信 SMS
 *        笔记: static静态同步方法 锁的是当前类的Class类,大门,
 *              而普通方法,锁的只是当前实例的对象,是一个小门
 *              两者不冲突,所以先是普通方法输出。
 *    8.  一个静态同步方法,1个给普通方法, 两个手机 先打印邮件还是短信 SMS、
 *        笔记:当一个线程试图访问同步块的时候,它必须首先得到锁,退出或者抛出异常时必须释放锁
 *           也就是说如果一个实例对象的非静态同步方法获取锁以后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁  后才能获取锁
 *           可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,
 *           所以不需要等待该实例对象已获锁的非静态同步方法释放锁 就可以获取他们自己的锁
 *
 *           所有静态同步方法用的是同一把锁,Class类本身
 *           这两把锁锁的是不同的对象,所以静态同步方法与非静态同步方法之间 不会又竞争关系
 *           但是一旦一个静态同步方法获取锁以后,其他的静态同步方法都必须等待该方法释放锁以后才能获取锁
 *           而且 不管是同一个实例对象的静态同步方法直接,还是不同的实例对象的静态同步方法直接,只有他们是同一个类的实例对象
 */
public class Lock8Demo04 {

    public static void main (String[] args) throws Exception {
        Phone phone = new Phone();
        Phone phoneB =new Phone();
        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        Thread.sleep(100);

        new Thread(() -> {
            try {
                phoneB.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

    }

}

截图看着IDEA笔记舒服些

技术分享图片

2.JUC案例实战篇(1-5)

原文:https://www.cnblogs.com/blogger-Li/p/14333183.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!