下面进行线程的进阶应用
①需求:计算任务,一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出计算的结果。
public class Count {
public static void main(String[] args) {
int[] is = new int[20000];
for (int i = 0; i < is.length; i++) {
is[i] = i+1;
}
countA a1 = new countA(is, 0, 5000);
countA a2 = new countA(is, 5000, 10000);
countA a3 = new countA(is, 10000, 15000);
countA a4 = new countA(is, 15000, 20000);
a1.start();
a2.start();
a3.start();
a4.start();
/*
//通过休眠处理
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
*/
/*
//通过Boolean处理
while(a1.isBool() || a2.isBool() || a3.isBool() || a4.isBool()){
}
*/
//通过join处理
try {
a1.join();
a2.join();
a3.join();
a4.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(a1.getSum()+a2.getSum()+a3.getSum()+a4.getSum());
}
}
class countA extends Thread{
private int[] is;
private int startIndex;
private int endIndex;
private int sum = 0;
private boolean bool = true;
public countA(int is[],int startIndex,int endIndex){
this.is = is;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = startIndex; i < endIndex; i++) {
sum = sum + is[i];
}
bool = false;
}
public boolean isBool(){
return bool;
}
public int getSum(){
return sum;
}
}
分析:这个题是将数组进行拆分计算,分为1、2、3、4个数组,然后将这4个数组放进四个线程去计算即可。这里需要注意的是,我们需要让每个数组计算完再计算下一个数组。这就涉及到资源的争抢了,我们来假设一下,比如有两个数组,分别为a1={1,2,3,4,5},a2={6,7,8,9,10},如果a1先抢到资源,它开始计算,当它计算完下标为2的时候,即3的时候,此时sum=6,如果此时它进入就绪状态,被a2抢到了资源,然后a2将6传进去,sum就等于12了,这样是不是计算结果就出错了?所以需要让线程一个一个运行完毕。
②采用多线程技术,实现断点续传,实现多线程断点续传,要求线程的数量可由客户端程序来设置
public class BreakpointResume {
public static void main(String[] args) throws Exception {
//原文件,即被传送的文件
File sourceFile = new File("H:\\javaio\\测试.avi");
//目标文件,即传送到目的地的文件,相当于下载下来的文件
File targetFile = new File("H:\\javaio\\copyBR.avi");
Scanner input = new Scanner(System.in);
System.out.println("请输入线程数:");
//线程数
int threadNum = input.nextInt();
//计算前threadNum-1个线程每个传送的大侠
long length = sourceFile.length()/threadNum;
//将前threadNum-1个线程开启,开始传送
for (int i = 0; i < threadNum-1; i++) {
new BRA(sourceFile, targetFile, length*i, length).start();
}
//最后一个线程需要传送的大小
long lastLength = sourceFile.length()/threadNum + sourceFile.length()%threadNum;
//开启最后一个线程
new BRA(sourceFile, targetFile, length*(threadNum-1), lastLength).start();
input.close();
}
}
class BRA extends Thread{
private RandomAccessFile r;
private RandomAccessFile w;
private long length;//每个流要拷贝的字节数
public BRA(File sourceFile, File targetFile, long pointer, long length) throws IOException {
this.r = new RandomAccessFile(sourceFile, "r");
this.w = new RandomAccessFile(targetFile, "rw");
this.length = length;
this.r.seek(pointer);
this.w.seek(pointer);
}
@Override
public void run() {
// TODO Auto-generated method stub
byte[] b = new byte[1024];
int len;
try {
long sum = 0;
while((len = r.read(b)) != -1){
w.write(b, 0, len);
sum += len;
//如果接收到的文件长度大于发送过来的文件长度,停止此线程
if(sum >= length){
break;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(r != null){
try {
r.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(w != null){
try {
w.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
分析:断点续传不再多讲,不知道的可以去加强一下io流的学习,这里主要讲通过多线程实现。一个文件通过多个线程来实现断点续传,就好比是之前那个数组分为多个线程来实现一样,我们需要弄清楚的是其中的逻辑关系。假设一个文件为1003个字节,分为4个线程来传。我们可以这样,前3个线程分别传前面3个250个字节,第4个就传最后的253个字节。计算怎么得到?1003/4=250,这是前三个的大小,1003/4 + 1003%4=253,这是最后一个的大小。
③需求:卖票任务,要求销售1000张票,要求有3个窗口来进行销售,编写多线程程序来模拟这个效果
这里分别使用Thread和Runnable来实现
Runnable实现:
public class TicketTestRunnable {
public static void main(String[] args) {
Task task = new Task();
Thread t1 = new Thread(task,"窗口1");
Thread t2 = new Thread(task,"窗口2");
Thread t3 = new Thread(task,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Task implements Runnable{
private int ticket = 1000;
private Object obj = new Object();
private Lock lock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
while(ticket > 0){
fun03();
}
}
private void fun03() {
lock.lock();
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticket +"张票!");
ticket--;
if(ticket == 0){
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}else{
System.out.println(Thread.currentThread().getName() + "已售完!");
}
lock.unlock();
}
private synchronized void fun02() {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticket +"张票!");
ticket--;
if(ticket == 0){
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}else{
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}
private void fun01() {
//任何一个唯一的对象都可以锁住
synchronized (this) {
//synchronized (obj) {
//synchronized (String.class) {
//synchronized ("啦啦") {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticket +"张票!");
ticket--;
if(ticket == 0){
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}else{
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}
}
}
Thread实现:
public class TicketTestThread {
public static void main(String[] args) {
new TaskThread("窗口1").start();
new TaskThread("窗口2").start();
new TaskThread("窗口3").start();
}
}
class TaskThread extends Thread{
private static int ticket = 1000;
private static Object obj = new Object();
private static Lock lock = new ReentrantLock();
public TaskThread(String name){
super(name);
}
@Override
public void run() {
// TODO Auto-generated method stub
while(ticket > 0){
fun03();
}
}
private void fun03() {
//创建锁
//加锁
lock.lock();
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在销售第" + ticket +"张票");
ticket--;
if(ticket == 0){
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}else{
System.out.println(Thread.currentThread().getName() + "已售完!");
}
lock.unlock();
}
//加了static方法,它的对象是字节码文件
private static synchronized void fun02() {
//同步方法中的对象为this,锁不住
//private synchronized void fun02() {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在销售第" + ticket +"张票");
ticket--;
if(ticket == 0){
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}else{
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}
private void fun01() {
//这里用this锁不住,如果obj不设置为static也锁不住
synchronized (obj) {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在销售第" + ticket +"张票");
ticket--;
if(ticket == 0){
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}else{
System.out.println(Thread.currentThread().getName() + "已售完!");
}
}
}
}
分析:对于卖票任务,是多个线程去执行同一个任务,以后只要涉及到多个线程执行同一个任务,我们直接可以用卖票去套。所以我们在创建测试的时候,是不是应该创建一个任务对象,然后new三个线程出来,将这一个任务对象放进三个线程里面。我们直接来说fun01,既然是卖票,那我们就直接while(ticket > 0),什么时候能卖?,肯定是当ticket(票)大于0的时候就能卖。每卖一张,我们就自减1,但是这里需要在fun01里面加锁,即在while(true)里面加锁。因为如果不加锁的话,当线程1进入fun01的时候,假设ticket(票)是第777张,此时是就是窗口1,突然资源被抢,线程2进入,那线程1进入就绪等待,此时线程2接收到的ticket(票)是第777张,然后线程2卖票,即窗口2卖第777,卖了之后,线程2就绪等待,线程1抢到资源,它接收的ticket是第777张,所以它卖的也是第777张,就会出现卖重票的情况。而对于锁,我们需要用一个不变的对象当做锁的对象。关于锁,可以参考其它文章。