一、多线程火车卖票
多个线程同时操作一个票库
票
/**
* 车票类
* 一个对象表示一张车票
*/
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, 班次:郑州 ---> 重庆
小丽卖了一张:票号:4, 车次:G2022, 班次:郑州 ---> 重庆
小红卖了一张:票号:5, 车次:G2022, 班次:郑州 ---> 重庆
小红卖了一张:票号:7, 车次:G2022, 班次:郑州 ---> 重庆
小红卖了一张:票号:8, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:6, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:10, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:11, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:12, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:13, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:14, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:15, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:16, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:17, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:18, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:19, 车次:G2022, 班次:郑州 ---> 重庆
小丽卖了一张:票号:20, 车次:G2022, 班次:郑州 ---> 重庆
小丽:票已售罄
小红卖了一张:票号:9, 车次:G2022, 班次:郑州 ---> 重庆
小红:票已售罄
票号为1的票被卖了两次!!!
这个就是多线程并发访问同一个数据时的,线程不同步问题(线程不安全)
解决多卖问题
synchronized同步,给线程共享数据加锁,如果线程1拿着锁操作,线程2不能操作
用法一:作为修饰符,修饰线程并发访问的方法
用法二:synchronized同步代码块(推荐)
synchronized(锁){
可能会发生线程不同步的代码
}
最终代码
@Override
public void run() {
//实现多线程卖票,“卖完为止”
while(true){
//同一CPU时刻,只有一个线程能拿到锁
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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mDThK5ow-1658669279916)(Img\image-20220721162101613.png)]
RandomAccessFile用来下载
/**
* 一次下载
* http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe
*/
public class Demo01 {
public static void main(String[] args) {
//需要请求的url
String path = "http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe";
try {
//创建URL对象
URL url = new URL(path);
//打开URL对应的服务器连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");//设置get提交
conn.setConnectTimeout(5000);//设置连接超时时间
conn.setReadTimeout(5000);//设置读取超时时间
//conn.setRequestProperty("Range","bytes=0-10");//设置下载的分段(从0-10)
//获取状态码
int responseCode = conn.getResponseCode();
System.out.println("responseCode = " + responseCode);
//如果正确响应 200
if(responseCode==200){
//下载...
InputStream in = conn.getInputStream();//输入流,用来读取网络资源
//从响应头获取要下载的文件名
String headerField = conn.getHeaderField("Content-Disposition");
String field = headerField.split(";")[1];
String fileName = field.substring(field.indexOf("\"")+1,field.lastIndexOf("\""));
//System.out.println(fileName);
File file = new File(fileName);
//下载,写入数据到文件
RandomAccessFile raf = new RandomAccessFile(file,"rwd");//可读可写有权限
//读一组,写一组
int len = 0;
byte[]bytes=new byte[1024];
while((len = in.read(bytes))!=-1){
raf.write(bytes,0,len);//读len个,写len个
}
//关闭流,释放资源
raf.close();
in.close();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
断点续传
将要下载的文件分成多段下载,每段使用子线程下载
/**
* 断点续传
* http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe
*/
public class Demo02 {
public static void main(String[] args) {
//需要请求的url
String path = "http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe";
//三个线程
int threadCount = 3;
try {
//创建URL对象
URL url = new URL(path);
//打开URL对应的服务器连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");//设置get提交
conn.setConnectTimeout(5000);//设置连接超时时间
conn.setReadTimeout(5000);//设置读取超时时间
//获取状态码
int responseCode = conn.getResponseCode();
System.out.println("responseCode = " + responseCode);
//如果正确响应 200
if(responseCode==200){
//获取文件的大小,为什么要获取文件的大小?假如三个线程
int contentLength = conn.getContentLength();
System.out.println("文件总大小 = " + contentLength);
//切割为三份,计算每份的大小
int size = contentLength/threadCount;
/*
假如 contentLength=10 zize=3
线程1:i*size-(i+1)*size 0-3
线程2:i*size-(i+1)*size 3-6
线程3:i*size-contentLenth 6-10
*/
for (int i = 0; i < threadCount; i++) {
//创建子线程,下载一段
DownloadThread dt = new DownloadThread();
dt.setName("线程"+(i+1));
dt.setPath(path);
dt.setStart(i*size);//设置此段开始位置
if(i==2){
dt.setEnd(contentLength);//设置此段结束位置
}else{
dt.setEnd((i+1)*size);//设置此段结束位置
}
//启动线程,开始下载
dt.start();
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 下载文件的一段的子线程
* 至少要有三个属性
* path
* start 文件分段的开始
* end 文件分段的结束
*
* 0123456789
* 012 345 6789 分成了三段
* 每段都有各自的开始和结束
*/
class DownloadThread extends Thread{
private String path; //网址
private long start; //开始的位置
private long end; //结束的位置
/**
* 下载一段
* 从start-end
*/
@Override
public void run() {
//需要请求的url
String path = "http://softforspeed.51xiazai.cn/down/BaiduNetdisk_6.9.7.4.exe";
try {
//创建URL对象
URL url = new URL(path);
//打开URL对应的服务器连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");//设置get提交
conn.setConnectTimeout(5000);//设置连接超时时间
conn.setReadTimeout(5000);//设置读取超时时间
conn.setRequestProperty("Range","bytes="+start+"-"+end);//设置下载的分段(从start-end)
//获取状态码
int responseCode = conn.getResponseCode();
System.out.println("responseCode = " + responseCode);
//分段下载,如果正确响应 206
if(responseCode==206){
//下载...
InputStream in = conn.getInputStream();//输入流,用来读取网络资源
//从响应头获取要下载的文件名
String headerField = conn.getHeaderField("Content-Disposition");
String field = headerField.split(";")[1];
String fileName = field.substring(field.indexOf("\"")+1,field.lastIndexOf("\""));
//System.out.println(fileName);
File file = new File(fileName);
//下载,写入数据到文件
RandomAccessFile raf = new RandomAccessFile(file,"rwd");//可读可写有权限
raf.seek(start);//【断点核心代码】设置从文件的什么位置开始写
//读一组,写一组
int len = 0;
byte[]bytes=new byte[1024];
while((len = in.read(bytes))!=-1){
raf.write(bytes,0,len);//读len个,写len个
}
//关闭流,释放资源
raf.close();
in.close();
System.out.println(this.getName()+":已经下载完毕..");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//get和set省略...
}
}
//关闭流,释放资源
raf.close();
in.close();
System.out.println(this.getName()+":已经下载完毕..");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//get和set省略...
}