一、多线程火车售票案例
票
/**
* 车票
*/
public class Ticket {
private Integer ticketNum; //票号
private String trainNum; //车次
private String Origin; //始发地
private String destination; //目的地
@Override
public String toString() {
return "票号=" + ticketNum +
", 车次=" + trainNum +
"," + Origin +" ---> " + destination;
}
public Ticket() {
}
public Ticket(Integer ticketNum, String trainNum, String origin, String destination) {
this.ticketNum = ticketNum;
this.trainNum = trainNum;
Origin = origin;
this.destination = destination;
}
//get和set省略..
}
售票员类(多线程)
/**
* 售票员线程
* 目的:多个售票员同时售票
*/
public class Ticketer extends Thread{
private List<Ticket> tickets; //票库(多个售票员同一个票库)
//卖票方法(从集合中删除一张票)
public void sellingTickets(){
//从票库删除一张票
Ticket ticket = this.tickets.remove(0);
System.out.println(this.getName()+"卖了一张:"+ticket);
}
@Override
public void run() {
//实现多线程卖票,“卖完为止”
while(true){
//如果剩余有票,则调用卖票方法
if(this.tickets.size()>0){
sellingTickets();//调用卖票的方法
}else{
//否则,票已售完
System.out.println(this.getName()+":票已售罄!");
break;
}
}
}
//get和set省略..
}
测试并发卖票
//票库
List<Ticket> tickets = new ArrayList<>();
for (int i = 1; i <= 20 ; i++) {
Ticket ticket = new Ticket(i,"G2022","郑州","重庆");
tickets.add(ticket);
}
Ticketer t1 = new Ticketer();
t1.setName("小红");
Ticketer t2 = new Ticketer();
t2.setName("小丽");
//给两个售票员设置同一个票库
t1.setTickets(tickets);
t2.setTickets(tickets);
//开始子线程进行售票
t1.start();
t2.start();
结果示例
小红卖了一张:票号=1, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=1, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=3, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=5, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=4, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=7, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=6, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=8, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=9, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=10, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=11, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=12, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=13, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=14, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=15, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=16, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=17, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=18, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=19, 车次=G2022,郑州 ---> 重庆
小丽卖了一张:票号=20, 车次=G2022,郑州 ---> 重庆
小红卖了一张:票号=20, 车次=G2022,郑州 ---> 重庆
小红:票已售罄!
小丽:票已售罄!
解决多卖问题
可以发现,程序设计有同一张票被卖了多次的问题!!
两个线程同时拿到一张票,同时去卖
synchronized,线程同步,可以给线程加锁,只要一个线程有锁,另一个线程不能访问(换言之:同一CPU时刻,只能由一个线程去操作票库[线程共享的数据])
方式一:可以用synchronized修饰方法
方式二:synchronized代码块(建议使用这一种)
synchronized(锁对象){//共享的数据
拿到锁才能进行的操作
}
改变run方法
@Override
public void run() {
//实现多线程卖票,“卖完为止”
while(true){
synchronized (this.tickets){
//如果剩余有票,则调用卖票方法
if(this.tickets.size()>0){
sellingTickets();//调用卖票的方法
}else{
//否则,票已售完
System.out.println(this.getName()+":票已售罄!");
break;
}
}
}
}
二、断点续传
下载文件时,可以多线程分段下载,每个线程都负责下载一部分
断点续传测试地址:
http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe
服务器响应码
- 500: 服务器异常
- 404: 路径找不到
- 400: 参数类型错误
- 200: OK
- 206: 部分数据已OK
RandomAccessFile用来下载
/**
* 直接下载
* http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe
*/
public class Demo01 {
public static void main(String[] args) {
String path = "http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe";
try {
//创建一个URL对象
URL url = new URL(path);
//打开连接,获取了url请求的连接对象conn
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//请求设置
conn.setConnectTimeout(5000);//请求的超时时间
conn.setReadTimeout(5000);//读取超时时间
conn.setRequestMethod("GET");//设置请求提交的方法Get Post
//获取响应码
int responseCode = conn.getResponseCode();//获取响应码
System.out.println("responseCode = " + responseCode);
//如果连接ok 200
if(responseCode==200){
//下载?
InputStream in = conn.getInputStream();//获取用于读取网络资源的输入流
//下载的流
String fileName = conn.getHeaderField("Content-Disposition");//获取响应的头部文件(包含由文件名)
fileName = fileName.split(";")[1];
fileName = fileName.substring(fileName.indexOf("\"")+1,fileName.lastIndexOf("\""));
//System.out.println(fileName);//成功获取文件名
File file = new File(fileName);
RandomAccessFile raf = new RandomAccessFile(file,"rwd");//可读、可写,有权限
//读一组,下载一组
byte[]bytes=new byte[1024];
int len = 0;
while((len=in.read(bytes))!=-1){
raf.write(bytes,0,len);
}
//关闭流释放资源
raf.close();
in.close();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
断点续传 核心代码代码
//设置请求头:断点续传的范围
conn.setRequestProperty("Range","bytes="+start+"-"+end);
//设置Range范围后,响应码为206(表示部分数据ok)
if(conn.getResponseCode()==206){
//statr-end这部分数据的下载代码
//可参考课堂案例的下载代码
//设置本次下载的部分数据从哪个位置开始
raf.seek(start);//【实现断点续传的核心方法】
}
伪代码提示:
一个程序入口类,开启下载
String path = "http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe";
try {
//创建一个URL对象
URL url = new URL(path);
//打开连接,获取了url请求的连接对象conn
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//请求设置
conn.setConnectTimeout(5000);//请求的超时时间
conn.setReadTimeout(5000);//读取超时时间
conn.setRequestMethod("GET");//设置请求提交的方法Get Post
//获取响应码
int responseCode = conn.getResponseCode();//获取响应码
System.out.println("responseCode = " + responseCode);
//如果连接ok 200
if(responseCode==200){
//设计分段下载(如分成3次下载)
//要下载的文件大小
int contentLength = conn.getContentLength();
//每个线程下载的大小
int size = contentLength/3;
//3.开启线程执行下载
for (int i = 0; i < 3; i++) {
//创建断点续传线程对象(下载一部分)
//设置下载哪一部分(start,end)
//线程对象.start();
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
断点续传线程类
public class DownLoad extends Thread{
start;//起始位置
end;//结束位置
@Override
public void run() {
//创建一个URL对象
//打开连接,获取了url请求的连接对象conn
//请求设置
//请求的超时时间
//读取超时时间
//设置请求提交的方法Get Post
//设置线程本次断点续传文件范围
conn.setRequestProperty("Range","bytes="+start+"-"+end);//获取响应码
//获取响应码
if(响应码==206){//由于是部分数据 206就表示ok
//下载本段文件(参考课堂案例下载代码)
//...
//设置本次下载的部分数据从哪个位置开始RandomAccessFile的seek方法
//....
}
}
//get和set省略...
}
stProperty(“Range”,“bytes=”+start+“-”+end);//获取响应码
//获取响应码
if(响应码==206){//由于是部分数据 206就表示ok
//下载本段文件(参考课堂案例下载代码)
//...
//设置本次下载的部分数据从哪个位置开始RandomAccessFile的seek方法
//....
}
}
//get和set省略...
}