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();
}
}