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协议
|
|
|
|