第十四章 多线程的常用练习

1、利用多线程复制文件,并对比单线程和多线程的性能

CopyFilesByRunnableDemo

package com.bennett.test1012;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.CountDownLatch;

/**
 * @version:
 * @Description:拷贝文件
 * @author gxd
 * @date: 2021年10月12日 下午3:43:50
 */ 
public class CopyFilesByRunnableDemo extends Thread {
    private final int NUMBER = 8192;//8kb
    //等待线程
    CountDownLatch downLatch;

    RandomAccessFile oldFile;
    RandomAccessFile newFile;
    String oldSrc;
    String newSrc;
    long start;
    long end;
//	构造方法
    public CopyFilesByRunnableDemo(String oldSrc, String newSrc, long start, long end, CountDownLatch downLatch) {
        this.oldSrc = oldSrc;
        this.newSrc = newSrc;
        this.start = start;
        this.end = end;
        this.downLatch = downLatch;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "线程启动了...");
            //需要复制的长度
            long len = end - start;
            //已经复制的长度
            long length = 0;
            //需要复制的资源
            oldFile = new RandomAccessFile(oldSrc, "rw");
            newFile = new RandomAccessFile(newSrc, "rw");

            oldFile.seek(start);//跳过的字节
            newFile.seek(start);
            byte[] bytes = new byte[NUMBER];
            while (true) {
                if ((len - length) <= NUMBER) {
                    oldFile.read(bytes, 0, (int) (len - length));
                    newFile.write(bytes, 0, (int) (len - length));
                    break;
                } else {
                    oldFile.read(bytes);
                    newFile.write(bytes);
                    length += NUMBER;
                }
            }
            oldFile.close();
            newFile.close();
            downLatch.countDown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



CopyFilesTest

package com.bennett.test1012;

import java.io.File;
import java.util.concurrent.CountDownLatch;

/**
 * @version:
 * @Description:文件拷贝测试类
 * @author gxd
 * @date: 2021年10月12日 下午4:02:33
 */ 
public class CopyFilesTest {
	 public static void main(String[] args) {

	        String oldURL = "E:/BaiduNetdiskDownload/exercise_calculator.zip";
	        String newURL = "E:/execise.zip";
	        File oldFile = new File(oldURL);
	        //获取文件的长度
	        long lengthFile = oldFile.length();
	        //要开启的线程数
	        int threadCount = 10;
	        //每个线程需要的平均长度
	        long meanLen = lengthFile / threadCount;
	        //开始时间
	        long start;
	        //结束时间
	        long end;

	        //记录时间
	        long startCurrent = System.currentTimeMillis();

	        CountDownLatch downLatch = new CountDownLatch(threadCount);

	        for (int i = 0; i < threadCount; i++) {
	            if (i == threadCount - 1L) {
	                start = i * meanLen;
	                end = lengthFile;
	                new CopyFilesByRunnableDemo(oldURL, newURL, start, end, downLatch).start();
	            } else {
	                start = i * meanLen;
	                end = start + meanLen - 1L;
	                new CopyFilesByRunnableDemo(oldURL, newURL, start, end, downLatch).start();
	            }
	        }
	        try {
	            //等待线程结束
	            downLatch.await();//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        long entCurrent = System.currentTimeMillis();
	        System.out.println("文件拷贝使用时间(毫秒):"+(entCurrent - startCurrent));
	    }
}

运行过程控制台显示提示

Thread-1线程启动了...
Thread-6线程启动了...
Thread-9线程启动了...
Thread-5线程启动了...
Thread-0线程启动了...
Thread-4线程启动了...
Thread-2线程启动了...
Thread-3线程启动了...
Thread-8线程启动了...
Thread-7线程启动了...
文件拷贝使用时间(毫秒):40156

countDownLatch

1.背景:
countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。
存在于java.util.cucurrent包下。
2.概念
countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
3.源码
countDownLatch类中只提供了一个构造器:

//参数count为计数值
public CountDownLatch(int count) {  };  

类中有三个方法是最重要的:

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

4.示例
普通示例:

public class CountDownLatchTest {

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(2);
        System.out.println("主线程开始执行…… ……");
        //第一个子线程执行
        ExecutorService es1 = Executors.newSingleThreadExecutor();
        es1.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }
        });
        es1.shutdown();

        //第二个子线程执行
        ExecutorService es2 = Executors.newSingleThreadExecutor();
        es2.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                latch.countDown();
            }
        });
        es2.shutdown();
        System.out.println("等待两个线程执行完毕…… ……");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("两个子线程都执行完毕,继续执行主线程");
    }
}

结果集:

主线程开始执行…… ……
等待两个线程执行完毕…… ……
子线程:pool-1-thread-1执行
子线程:pool-2-thread-1执行
两个子线程都执行完毕,继续执行主线程

模拟并发示例:

public class Parallellimit {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        CountDownLatch cdl = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            CountRunnable runnable = new CountRunnable(cdl);
            pool.execute(runnable);
        }
    }
}

 class CountRunnable implements Runnable {
    private CountDownLatch countDownLatch;
    public CountRunnable(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        try {
            synchronized (countDownLatch) {
                /*** 每次减少一个容量*/
                countDownLatch.countDown();
                System.out.println("thread counts = " + (countDownLatch.getCount()));
            }
            countDownLatch.await();
            System.out.println("concurrency counts = " + (100 - countDownLatch.getCount()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

CountDownLatch和CyclicBarrier区别:
1.countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
2.CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

RandomAccessFile

介绍

RandomAccessFile既可以读取文件内容,也可以向文件输出数据。同时,RandomAccessFile支持“随机访问”的方式,程序快可以直接跳转到文件的任意地方来读写数据。

由于RandomAccessFile可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用RandomAccessFile将是更好的选择。

与OutputStream、Writer等输出流不同的是,RandomAccessFile允许自由定义文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。

RandomAccessFile的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他IO节点。

RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传。

RandomAccessFile中的方法

1.RandomAccessFile的构造函数
RandomAccessFile类有两个构造函数,其实这两个构造函数基本相同,只不过是指定文件的形式不同——一个需要使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定RandomAccessFile的访问模式,一共有4种模式。

**"r" : ** 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw": 打开以便读取和写入。
"rws": 打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
"rwd" : 打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。

2.RandomAccessFile的重要方法
RandomAccessFile既可以读文件,也可以写文件,所以类似于InputStream的read()方法,以及类似于OutputStream的write()方法,RandomAccessFile都具备。除此之外,RandomAccessFile具备两个特有的方法,来支持其随机访问的特性。

RandomAccessFile对象包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件指针记录位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会后移n个字节。除此之外,RandomAccessFile还可以自由移动该记录指针。下面就是RandomAccessFile具有的两个特殊方法,来操作记录指针,实现随机访问:

long getFilePointer( ):返回文件记录指针的当前位置
void seek(long pos ):将文件指针定位到pos位置

三、RandomAccessFile的使用
利用RandomAccessFile实现文件的多线程下载,即多线程下载一个文件时,将文件分成几块,每块用不同的线程进行下载。下面是一个利用多线程在写文件时的例子,其中预先分配文件所需要的空间,然后在所分配的空间中进行分块,然后写入:

/** 
 * 测试利用多线程进行文件的写操作 
 */  
public class Test {  
  
    public static void main(String[] args) throws Exception {  
        // 预分配文件所占的磁盘空间,磁盘中会创建一个指定大小的文件  
        RandomAccessFile raf = new RandomAccessFile("D://abc.txt", "rw");  
        raf.setLength(1024*1024); // 预分配 1M 的文件空间  
        raf.close();  
          
        // 所要写入的文件内容  
        String s1 = "第一个字符串";  
        String s2 = "第二个字符串";  
        String s3 = "第三个字符串";  
        String s4 = "第四个字符串";  
        String s5 = "第五个字符串";  
          
        // 利用多线程同时写入一个文件  
        new FileWriteThread(1024*1,s1.getBytes()).start(); // 从文件的1024字节之后开始写入数据  
        new FileWriteThread(1024*2,s2.getBytes()).start(); // 从文件的2048字节之后开始写入数据  
        new FileWriteThread(1024*3,s3.getBytes()).start(); // 从文件的3072字节之后开始写入数据  
        new FileWriteThread(1024*4,s4.getBytes()).start(); // 从文件的4096字节之后开始写入数据  
        new FileWriteThread(1024*5,s5.getBytes()).start(); // 从文件的5120字节之后开始写入数据  
    }  
      
    // 利用线程在文件的指定位置写入指定数据  
    static class FileWriteThread extends Thread{  
        private int skip;  
        private byte[] content;  
          
        public FileWriteThread(int skip,byte[] content){  
            this.skip = skip;  
            this.content = content;  
        }  
          
        public void run(){  
            RandomAccessFile raf = null;  
            try {  
                raf = new RandomAccessFile("D://abc.txt", "rw");  
                raf.seek(skip);  
                raf.write(content);  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            } finally {  
                try {  
                    raf.close();  
                } catch (Exception e) {  
                }  
            }  
        }  
    }  
}  

当RandomAccessFile向指定文件中插入内容时,将会覆盖掉原有内容。如果不想覆盖掉,则需要将原有内容先读取出来,然后先把插入内容插入后再把原有内容追加到插入内容后。

2、线程同步

1)关键字synchronized ,作用于对象。

package com.bennett.test1013;

public class ThreadTest4 {
	private  int i=1;
	static class MyRunner1 implements Runnable{
//		定义类对象
		private ThreadTest4 obj;
		
		public MyRunner1(ThreadTest4 obj) {
			this.obj = obj;
		}

		@Override
		public void run() {
			synchronized (obj) { //同步体里包含所有同步的代码,保证在同一范围只能有1个线程能在该区域运行
								//同步体线程运行到该区域,尝试加锁
//									如果没有锁,直接加锁
//									没有加锁成功,尝试加锁的线程,就进入阻塞状态
//									直到运行的线程离开同步体,阻塞才能解除,以前通过操作系统的核心态的锁机制
//								离开同步体,释放锁
//								synchronized是用来控制使用同步对象的线程。
				obj.i++;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("线程1:" + obj.i);
			}
		}
	}
	static class MyRunner2 implements Runnable{
		private ThreadTest4 obj;
		
		public MyRunner2(ThreadTest4 obj) {
			this.obj = obj;
		}
		
		@Override
		public  void run() {
			synchronized (obj) { //同步体里包含所有同步的代码
				obj.i++;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("线程2:" + obj.i);
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		ThreadTest4 test4=new ThreadTest4();
		new Thread(new MyRunner1(test4)).start();
		new Thread(new MyRunner2(test4)).start();
	}
}

2)关键字作用于方法

package com.bennett.test1013;

public class ThreadTest5 {
	private  int i=1;
	static class MyRunner1 implements Runnable{
		private ThreadTest5 obj;
		
		public MyRunner1(ThreadTest5 obj) {
			this.obj = obj;
		}
//		synchronizedg关键字作用于方法
		@Override
		public synchronized void run() {
			obj.i++;
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+":" + obj.i);
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		ThreadTest5 test4=new ThreadTest5();
		MyRunner1 target = new MyRunner1(test4);
//		创建两个线程
		new Thread(target).start();
		new Thread(target).start();
	}
}
package com.bennett.test1013;

public class ThreadTest6 {
	private static int i=1;
	
	public static void main(String[] args) throws InterruptedException {
		Thread thread1=new Thread(()->{
			synchronized (ThreadTest6.class) {
				i++;
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("线程1:"+i);
			}
			
		}) ; 
		Thread thread2=new Thread(()->{
			synchronized (ThreadTest6.class) {
				i++;
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("线程2:"+i);
			}
		}) ; 
		thread1.start();
		Thread.sleep(15);
		thread2.start();
//		test1();
	}

	private synchronized  static void test1() {
//		synchronized(ThreadTest6.class) {
			i++;
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("线程1:"+i);
//		}
	}
	private synchronized  static void test2() {
//		synchronized(ThreadTest6.class) {
		i++;
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("线程2:"+i);
//		}
	}
}
package com.bennett.test1013;

/**
 * @version:
 * @Description:使用lambda表示实现线程同步
 * @author gxd
 * @date: 2021年10月13日 下午6:39:32
 */ 
public class ThreadTest7 {
	private static int i=1;
	
	public static void main(String[] args) throws InterruptedException {
		
		Thread thread1=new Thread(()->{
			test1();
		}) ; 
		Thread thread2=new Thread(()->{
			test2();
		}) ; 
		thread1.start();
		Thread.sleep(15);
		thread2.start();
	}

	private synchronized  static void test1() {
//		synchronized(ThreadTest6.class) {
			i++;
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("线程1:"+i);
//		}
	}
	private synchronized  static void test2() {
//		synchronized(ThreadTest6.class) {
		i++;
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("线程2:"+i);
//		}
	}
}
package com.bennett.test1013;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @version:
 * @Description:Lock类实现线程同步
 * 可重入锁就是该线程结束之后可以重新进入
 * @author gxd
 * @date: 2021年10月13日 下午6:41:51
 */ 
public class ThreadTest8 {
	private  int i=1;
	static class MyRunner1 implements Runnable{
		private ThreadTest8 obj;
		private Lock lock=new ReentrantLock();//可重入锁
		

		public MyRunner1(ThreadTest8 obj, Lock lock) {
			this.obj = obj;
			this.lock = lock;
		}

		@Override
		public void run() {
//			if(!lock.tryLock()) {
//				System.out.println("我还没被阻塞");
//			}
			lock.lock();
//			System.out.println("我还没被阻塞");
			obj.i++;
			lock.lock();
			lock.unlock();
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+":" + obj.i);
			lock.unlock();
		}
	}
	
	
	
	public static void main(String[] args) throws InterruptedException {
		ThreadTest8 test4=new ThreadTest8();
		Lock lock=new ReentrantLock();
		MyRunner1 target = new MyRunner1(test4,lock);
		new Thread(target).start();
		new Thread(target).start();
	}
}
package com.bennett.test1013;

public class DeadLockTest {
	static class MyTheadA extends Thread{
		private Object a;
		private Object b;
		
		public MyTheadA(Object a, Object b) {
			this.a = a;
			this.b = b;
		}

		@Override
		public void run() {
			synchronized (a) {
				System.out.println("线程MyTheadA成功锁定资源a");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (b) {
					System.out.println("线程MyTheadA成功锁定资源b");
				}
			}
		}
	}
	static class MyTheadB extends Thread{
		private Object a;
		private Object b;
		
		public MyTheadB(Object a, Object b) {
			this.a = a;
			this.b = b;
		}
		
		@Override
		public void run() {
			synchronized (b) {
				System.out.println("线程MyTheadA成功锁定资源b");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (a) {
					System.out.println("线程MyTheadA成功锁定资源a");
				}
			}
		}
	}
	public static void main(String[] args) {
		Object a=new Object();
		Object b=new Object();
		new MyTheadA(a, b).start();
		new MyTheadB(a, b).start();
	}
}
package com.bennett.test1013;

public class DeadLockTest2 {
	static class ObjectC{
		private Object a;
		private Object b;
		public ObjectC(Object a, Object b) {
			this.a = a;
			this.b = b;
		}
		public Object getA() {
			return a;
		}
		public void setA(Object a) {
			this.a = a;
		}
		public Object getB() {
			return b;
		}
		public void setB(Object b) {
			this.b = b;
		}
		
	}
	
	static class MyTheadA extends Thread{
		private ObjectC c;

		public MyTheadA(ObjectC c) {
			this.c = c;
		}

		@Override
		public void run() {
			synchronized (c) {
				System.out.println("线程MyTheadA成功锁定资源a");
				System.out.println("线程MyTheadA成功锁定资源b");
			}
		}
	}
	static class MyTheadB extends Thread{
		private Object a;
		private Object b;
		
		public MyTheadB(Object a, Object b) {
			this.a = a;
			this.b = b;
		}
		
		@Override
		public void run() {
			synchronized (b) {
				System.out.println("线程MyTheadB成功锁定资源b");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (a) {
					System.out.println("线程MyTheadB成功锁定资源a");
				}
			}
		}
	}
	public static void main(String[] args) {
		Object a=new Object();
		Object b=new Object();
//		new MyTheadA(a, b).start();
		new MyTheadB(a, b).start();
	}
}

使用两个线程打印“1a2b3c”

package com.bennett.test1013;

import java.util.Random;

public class ConcurretPrintTest {
	static class ThreadA extends Thread {
		private Object flag;

		public ThreadA(Object flag) {
			this.flag = flag;
		}

		private String[] array = "1,2,3".split(",");

		@Override
		public void run() {
			synchronized (flag) {
				for (String string : array) {
					System.out.print(string + ",");
					try {
						Thread.sleep(new Random().nextInt(50));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					// 通知线程B工作
//					flag.notify();// 随机唤醒对象监视器(阻塞的线程池)中的任意1个线程
//					System.out.println("ThreadA唤醒其它线程");
					 flag.notifyAll();//唤醒对象监视器(阻塞的线程池)中所有线程
					// 自己停止
					try {
						flag.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	static class ThreadB extends Thread{
		private Object flag;
		private String[] array="a,b,c".split(",");
		
		public ThreadB(Object flag) {
			this.flag = flag;
		}

		@Override
		public void run() {
			synchronized(flag) {
				try {
//					System.out.println("ThreadB锁定");
					flag.wait();//让使用这个信号灯线程等待
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				for (String string : array) {
					System.out.print(string+",");
					try {
						Thread.sleep(new Random().nextInt(50));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
//				通知线程A工作
					flag.notify();
//				自己停止
					try {
						flag.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	public static void main(String[] args) {
		Object xiHaoDeng=new Object();
		new ThreadB(xiHaoDeng).start();
		new ThreadA(xiHaoDeng).start();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值