Java中线程的学习(四)——线程的进阶应用(涉及锁、经典卖票案列)

下面进行线程的进阶应用

①需求:计算任务,一个包含了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张,就会出现卖重票的情况。而对于锁,我们需要用一个不变的对象当做锁的对象。关于锁,可以参考其它文章。

    原文作者:java锁
    原文地址: https://blog.csdn.net/qq_41061437/article/details/81987191
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞