一起重新开始学大数据-java篇-DAY23-多线程(2)资源共享、模拟hadoop案例

DAY23-多线程(2)资源共享、模拟hadoop案例

在这里插入图片描述

2.什么是资源共享

2.1卖票案例

需求:

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

思路:

  • ①定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;

  • ②在SellTicket类中重写run0方法实现卖票,代码步骤如下
    A:判断票数大于0,就卖票,并告知是哪个窗口卖的
    B:卖了票之后,总票数要减1
    C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行

  • ③定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    A:创建SellTicket类的对象
    B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    C:启动线程

SellTicket.java

package javaDemo.ThreadDemo.SellingTicketsDemo;
//①定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
public class SellTicket implements Runnable{
    private  int tickets=100;
    //②在SellTicket类中重写run0方法实现卖票,代码步骤如下
    @Override
    public void run() {
//        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
//        B:卖了票之后,总票数要减1
//        C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
        while(true){
            if (tickets>0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
            tickets--;
                }
        }
    }
}

SellTicketDemo.java

package javaDemo.ThreadDemo.SellingTicketsDemo;
/*
    ①定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
    ②在SellTicket类中重写run0方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:卖了票之后,总票数要减1
        C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
③定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
        A:创建SellTicket类的对象
        B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程
*/
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}

在这里插入图片描述

2.1卖票案例的思考

刚才讲解了电影院卖票程序,好像没有什么问题。但是在实际生活中,售票时出票也是需要时间的所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:每次出票时间100毫秒,用sleep()方法实现

卖票出现了问题

  • 相同的票出现了多次

  • 出现了负数

出现问题的原因:

  • 线程执行的随机性导致的

SellTicket.java

package javaDemo.ThreadDemo.SellingTicketsDemo2;
public class SellTicket implements Runnable{
    private  int tickets=100;
    @Override
    public void run() {
//        相同的票出现了多次
        while(true){
//            tickets=100;
//            t1,t2,t3
//            假设ti线程强盗了cpu的执行权
            if (tickets>0){
                //通过sleep()方法来模拟出票时间
                try {
                    Thread.sleep(100);
//                    ti线程休息100毫秒
//                    t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
//                    t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//              假设线程按顺序醒过来
//                t1抢到了Cpu的执行权,在控制台输出,窗口1正在出售100张票
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
//                t2抢到了Cpu的执行权,在控制台输出,窗口2正在出售第100张票
                // t3抢到了Cpu的执行权,在控制台输出,窗口3正在出售第100张票
            tickets--;
//            如果这三个线程还是按照顺序来,这里就执行了3次--操作,最终就变成了97
                }
        }
    }
}

SellTicketDemo.java

package javaDemo.ThreadDemo.SellingTicketsDemo2;
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}
错误一:

在这里插入图片描述

错误二:

在这里插入图片描述

2.2卖票案例数据安全问题的解决

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

  • 是否是多线程环境

  • 是否有共享数据

  • 是否有多条语句操作共享数据

如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境。。(破坏上述三个条件之一)

怎么实现呢?

  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
  • java提供了同步代码块

2.3同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

格式:

synchronized(任意对象){
多条语句操作共享数据的代码
)
  • synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端:

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

SellTicket.java

package javaDemo.ThreadDemo.SellingTicketsDemo3;
public class SellTicket implements Runnable{
    private  int tickets=100;
    private Object obj=new Object();
    @Override
    public void run() {
        while(true){
            /*tickets=100;
            t1,t2,t3
            假设t1抢到了cpu的执行权
            (解锁后)假设t2抢到了cpu的执行权
            */
            synchronized (obj) {
                //t1进来后就会把这段代码给锁起来
                //t2进来后也把这段代码锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
//            t1出来了,这段代码的锁就被释放了
        }
    }
}

SellTicketDemo.java

package javaDemo.ThreadDemo.SellingTicketsDemo3;
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

2.4同步方法

同步方法:

就是把synchronized关键字加到方法上

格式:

修饰符synchronized返回值类型方法名(方法参数){}

同步方法的锁对象是什么呢?

this

同步静态方法:就是把synchronized关键字加到静态方法上

格式:
修饰符static synchronized返回值类型方法名(方法参数){}

同步静态方法的锁对象是什么呢?

类名.class

SellTicket.java

package javaDemo.ThreadDemo.SellingTicketsDemo4;
public class SellTicket implements Runnable{
//    private  int tickets=100;
    private static int tickets=100;
    private Object obj=new Object();
    private int x=0;
    @Override
    public void run() {
        while(true){
            if (x%2==0) {
//                synchronized (obj) {
//                synchronized (this) {
                synchronized (SellTicket.class) {//字节码文件对象
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                }
            }else {
//                synchronized (obj) {
//                if (tickets > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
//                    tickets--;
//                }
//            }
                sellTicket();
            }
            x++;
        }
    }

//    private void sellTicket() {
        synchronized (obj) {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
//
private static synchronized void  sellTicket() {//这个的锁是不同的
    if (tickets > 0) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
        tickets--;
    }

}
}

SellTicketDemo.java

package javaDemo.ThreadDemo.SellingTicketsDemo4;

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}

案例:hadoop模拟

1.把student.txt 数据放大->BigStudents.txt
2.大文件拆分,拆分8个小文件,小文件存储在目录split下格式为split----0.txt,1,2,3
3.每个文件开始分组聚合,聚合的结果存储在目录part下格式为part----0,1,2,3
4.多线程同时执行8个文件分组聚合
5.结果汇总

Demo01BigStudents.java

package com.shujia.day23Runnable.demo4Task;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class Demo01BigStudents {
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new FileReader("E:\\ideaFile\\BigData\\data\\students.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\ideaFile\\BigData\\data\\BigStudents.txt"));

        String line;
        while ((line=br.readLine())!=null){
            for (int i = 1; i <=10000 ; i++) {
                bw.write(line);
                bw.newLine();
            }
        }
        bw.close();
        br.close();
    }
}

Demo02ClazzSum.java

package com.shujia.day23Runnable.demo4Task;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo02ClazzSum {
    public static void main(String[] args) throws Exception{
        long start = System.currentTimeMillis();
        HashMap<String, Integer> map = new HashMap<>();
        BufferedReader br = new BufferedReader(new FileReader("E:\\ideaFile\\BigData\\data\\BigStudents.txt"));
        String line;
        while ((line=br.readLine())!=null){
            String[] split = line.split(",");
            String clazz = split[4];
            if(!map.containsKey(clazz)){
                map.put(clazz,1);
            }else {
                map.put(clazz,map.get(clazz)+1);
            }
        }
        br.close();

        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\ideaFile\\BigData\\data\\clazzSum.txt"));
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            String str=key+","+value;
            bw.write(str);
            bw.newLine();
        }

        bw.close();
        long end = System.currentTimeMillis();
        System.out.println(end-start);//6179
    }
}

Demo03SplitFile.java

package com.shujia.day23Runnable.demo4Task;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class Demo03SplitFile {
    public static void main(String[] args) throws Exception {
        long sumLine = sumLine();
        int size=8;//切分多少个文件
        long fileLine = sumLine / 8;// 求每个文件存多少行数据
        /**
         * 开始拆分文件
         * 拆8个
         * 每个文件的内容为fileLine行
         */
        int flagFile=0;//标记写到底几个文件,也用作与文件名称
        BufferedReader br = new BufferedReader(new FileReader("E:\\ideaFile\\BigData\\data\\BigStudents.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\ideaFile\\BigData\\split\\split----" + flagFile + ".txt"));
        int flagLine=0;//统计一个文件中写了多少行,flagLine如果等于fileLine开始切换下一个文件
        String line;
        while ((line=br.readLine())!=null){//读取所有数据
            // 如何切分文件
            if(flagLine<fileLine){// 一个文件切分的内容没有写满,向文件中继续写数据
                bw.write(line);
                bw.newLine();
                flagLine++;
            }else {// 切换下一个文件
                bw.flush();// 把上一个文件中的内容 刷到文件里
                flagFile++;// 开始下一文件的标记
                flagLine=0;//开始下一个文件,统计一个文件中写了多少行归0
                bw=new BufferedWriter(new FileWriter("E:\\ideaFile\\BigData\\split\\split----" + flagFile + ".txt"));
            }
        }

        bw.flush();
        br.close();
        bw.close();
    }

    /**
     * 求大文件有多少行
     * @return 返回行数
     * @throws Exception
     */
    public static long sumLine()throws Exception{
        long sum=0;
        BufferedReader br = new BufferedReader(new FileReader("E:\\ideaFile\\BigData\\data\\BigStudents.txt"));
        String line;
        while ((line=br.readLine())!=null){
            sum++;
        }
        br.close();
        return sum;
    }

}

Demo04Map.java

package com.shujia.day23Runnable.demo4Task;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 *  需要同时启动8个线程
 *      每个线程都是对一个小文件,进行分组聚合,聚合的结果写入到part下 part---0,1,2,3....
 */
public class Demo04Map {
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        // 需要创建多个线程时,每次常见一个非常消耗时间,一次性创建多个线程
        // 线程池
        // 线程池启动线程非常快
        ExecutorService pool = Executors.newFixedThreadPool(8);
        File file = new File("E:\\ideaFile\\BigData\\split");
        File[] files = file.listFiles();
        int index=0;
        for (File f : files) {//循环几个 创建几个线程
            //需要为每个文件启动一个线程
            MapTask mapTask = new MapTask(f, index);
            pool.submit(mapTask);
            index++;//下一个文件的编号
        }
        //关闭线程池
        pool.shutdown();
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}

Demo05Reduce.java

package com.shujia.day23Runnable.demo4Task;

import java.io.*;
import java.util.HashMap;

public class Demo05Reduce {
    public static void main(String[] args) throws Exception{
        HashMap<String, Integer> map = new HashMap<>();
        File file = new File("E:\\ideaFile\\BigData\\part");
        File[] files = file.listFiles();
        for (File f : files) {
            BufferedReader br = new BufferedReader(new FileReader(f));
            String line;
            while ((line=br.readLine())!=null){
                String[] split = line.split(",");
                String k = split[0];
                int v = Integer.parseInt(split[1]);
                if(!map.containsKey(k)){
                    map.put(k,v);
                }else {
                    map.put(k,map.get(k)+v);
                }
            }
            br.close();
        }
        System.out.println(map);
    }
}

MapTask.java

package com.shujia.day23Runnable.demo4Task;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapTask implements Runnable{
    private File file;// 每个线程 需要处理的小文件
    private int index;// 结果保存的文件编号

    public MapTask() {
    }

    public MapTask(File file, int index) {
        this.file = file;
        this.index = index;
    }

    @Override
    public void run() {
        HashMap<String, Integer> map = new HashMap<>();
        try {
            /**
             * 小文件的分组 聚合
             */
            BufferedReader br = new BufferedReader(new FileReader(file));
            String line;
            while ((line=br.readLine())!=null){
                String[] split = line.split(",");
                String clazz = split[4];
                if(!map.containsKey(clazz)){
                    map.put(clazz,1);
                }else {
                    map.put(clazz,map.get(clazz)+1);
                }
            }
            br.close();
            /**
             * 结果存储到文件中
             */
            BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\ideaFile\\BigData\\part\\part---" + index));
            Set<Map.Entry<String, Integer>> entries = map.entrySet();
            for (Map.Entry<String, Integer> entry : entries) {
                String key = entry.getKey();
                Integer value = entry.getValue();
                String str=key+","+value;
                bw.write(str);
                bw.newLine();
            }
            bw.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

小练习:

1.编写两个线程,一个线程打印1-52的整数,另一个线程打印字母A-Z。打印顺序为12A34B56C….5152Z。即按照整数和字母的顺序从小到大打印,并且每打印两个整数后,打印一个字母,交替循环打印,直到打印到整数52和字母Z结束。
要求:
1)编写打印类Printer,声明私有属性index,初始值为1,用来表示是第几次打印。
2)在打印类Printer中编写打印数字的方法print(int i),3的倍数就使用wait()方法等待,否则就输出i,使用notifyAll()进行唤醒其它线程。
3)在打印类Printer中编写打印字母的方法print(char c),不是3的倍数就等待,否则就打印输出字母c,使用notifyAll()进行唤醒其它线程。
4)编写打印数字的线程NumberPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出数字的方法。
5)编写打印字母的线程LetterPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出字母的方法。
6)编写测试类Test,创建打印类对象,创建两个线程类对象,启动线程。

答案:

Printer.java

package javaDemo.ThreadDemo.homework;

public class Printer {
    private int index=1;//用来表示第几次打印

    public synchronized void print(int i) throws Exception {
        while (index%3==0){
            wait();
        }
        System.out.print(i);
        index++;
        this.notifyAll();
        }



    public synchronized void print(char c) throws Exception {
        while (index%3!=0){
            wait();
        }System.out.print(c);
        index++;
        this.notifyAll();
    }


    public Printer() {
        super();
    }

    public Printer(int index) {
        this.index = index;
    }


}

NumberPrinter.java

package javaDemo.ThreadDemo.homework;

public class NumberPrinter extends Thread {
    private Printer p;

    public NumberPrinter(Printer p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=52; i++) {
            try {
                p.print(i);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

LetterPrinter.java

package javaDemo.ThreadDemo.homework;

public class LetterPrinter extends Thread{
    private Printer p;

    public LetterPrinter(Printer p) {
        super();
        this.p = p;
    }
    @Override
    public void run() {
        for (char i = 'A'; i <='Z' ; i++) {
            try {
                p.print(i);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Test.java

package javaDemo.ThreadDemo.homework;
//2.编写两个线程,一个线程打印1-52的整数,另一个线程打印字母A-Z。打印顺序为12A34B56C….5152Z。即按照整数和字母的顺序从小到大打印,并且每打印两个整数后,打印一个字母,交替循环打印,直到打印到整数52和字母Z结束。
//要求:
//1)编写打印类Printer,声明私有属性index,初始值为1,用来表示是第几次打印。
//2)在打印类Printer中编写打印数字的方法print(int i),3的倍数就使用wait()方法等待,否则就输出i,使用notifyAll()进行唤醒其它线程。
//3)在打印类Printer中编写打印字母的方法print(char c),不是3的倍数就等待,否则就打印输出字母c,使用notifyAll()进行唤醒其它线程。
//4)编写打印数字的线程NumberPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出数字的方法。
//5)编写打印字母的线程LetterPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出字母的方法。
//6)编写测试类Test,创建打印类对象,创建两个线程类对象,启动线程。

public class Test {
    public static void main(String[] args)throws Exception {
        Printer pr = new Printer();
        NumberPrinter num = new NumberPrinter(pr);
        LetterPrinter lp = new LetterPrinter(pr);
        num.start();
        lp.start();

    }
}

|
|
|
|
|
|
|
|

上一章节-java篇-DAY22-多线程(1)Thread&runnable

下一章节-java篇-DAY23-网络编程入门、UDP协议

在这里插入图片描述
|
|
|
|

听说看完点赞👍👍👍一个月内直接暴富¥¥下😎😎😎😎😎😎$$$$

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你的动作太慢了!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值