CountDownLatch
将一个大任务分成多个子任务,使用多线程执行子任务,从而提高效率。
那么如何衡量这些子任务都完成了呢?这就要用到CountDownLatch
CountDownLatch
1:指定指定的参数,描述拆分的任务数量
2:每个线程都执行完毕后,都执行一次CountDownLatch,当执行次数等于指定的参数时候,确认执行完毕。
public class Demo45 {public static void main(String[] args) throws InterruptedException {//将任务拆分成十个独立部分执行,每个任务都视为一个子任务//我们可以安排十个线程,也可以使用线程池CountDownLatch latch = new CountDownLatch(10);//十个任务个数ExecutorService executor = Executors.newFixedThreadPool(4);//线程池数量为4for(int i = 0;i <10;i++){int id = i;executor.submit(()->{System.out.println("子任务开始执行:" + id);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("子任务执行结束:" + id);latch.countDown();//执行完毕一次子任务就CountDownLatch一次});}latch.await();//阻塞等待所有任务完毕System.out.println("执行完毕");executor.shutdown();//结束线程池}
}
多线程下ArrayList的使用
1:自行加锁,使用synchronized和ReentrantLock
2:使用Collections.synchronizedList();,返回的方法是带有synchronization封装的。
3:使用CopyOnWriteArrayList。CopyOnWriteArrayList不会加锁。
这是一种编程常见的思想方法:写时拷贝,在修改不同变量和读变量不会出现线程安全。
在多线程进行写修改的时候,会将原来的数据进行复制,在复制过程中,有其他线程在读,会读取旧的数据。随后将引用指向复制后的数据,进行修改。
CopyOnWriteArrayList在进行复制操作并不是原子性,会导致会消耗一定的时间,但不影响线程的读取。在复制完毕之后,引用指向复制后的数据进行修改。
这样确使读操作要么是新数据要么是旧数据,不会出现读取一半新数据一半旧数据的情况,确保了“原子性”。
CopyOnWriteArrayList在这过程中是没有加锁的,所以不会产生阻塞。
CopyOnWriteArrayList缺点
1:因为需要复制,效率会比较低效,不适合较大的数组。
2:多个线程同时修改,容易出问题。
适用场所
服务器重新加载。在服务器还在运行的时候,更改配置文件,我们一般需要重写启动服务器才会生效。原因是:配置其实是读取服务器中的内存,以哈希/数组的方式存储,服务器其他的线程会读取这些哈希/数组。
此时,我们手动更改配置并使用,服务器就会加载新的哈希/数组来加载新的配置。在加载完毕后,就会使用新的配置取代旧的配置。
多线程中哈希表的使用
1:HashMap 线程不安全,不使用
2:Hashtable 线程安全,但不推荐使用,因为有更好的替代品(给public方法都加上了synchronization)
3:ConcurrentHashMap:按照桶级级别加锁,而不是给一个整个哈希表加全局锁,降低锁冲突的概率。
Hashtable其实是给this加锁。我们可以发现,如果修改的元素属于两个链表上,是不会出现线程安全问题,如果修改同一个链表上,有可能会出现线程安全问题(链表上连续的两个元素,修改时会更改前一个元素该指向谁,会出现竞争)。
ConcurrentHashMap的做法是针对不同的锁对象(链表)加锁,在修改不同线程的时候是不会产生竞争,只有在同一个链表上修改的时候才会出现锁竞争。
哈希表里只有一个size,在同时插入元素的时候,发现上述操作没有给size加锁,似乎会出现线程安全问题。实际上,在ConcurrentHashMap使用了原子类,避免这种问题出现。
针对扩容,ConcurrentHashMap也做出了优化,在遇到争对同一个链表进行插入大量元素时候,会进行拆分操作,多次插入,确保每次加锁的时间不要太长。
文件操作和IO
我们想要找到一个文件,从树根开始到我们要找到的文件这期间,我们会点开很多文件夹(目录),我们把这些文件夹记录下来,就是路径,路径一般用“/”来分割。
特别提醒,Windows支持反斜杠,默认反斜杠。
绝对路径:从盘级开始,逐级表现出来。
相对路径:前面部分以“.”省略,但需要一个基准。
public class Demo {public static void main(String[] args) {String path = "./test.txt";}
}
在这,我们没有定义基准路径,所以默认执行的是项目目录的路径。
在那个目录下执行,那么这个目录就是基准路径。
"..": 返回上一级的路径。
操作系统会通过路径识别到具体的文件
文件的种类
二十一点文件都是由二进制构成,这是基本条件。
二进制文件与文本文件
二进制文件的数据恰好都能在表上查找到,并且翻译过来的字符都是能构成有意义的信息。
判断方法也简单,直接记事本打开,不是乱码出现有意义的信息就是了。像word、docx这些文件都是二进制文件;.c、.java、txt纯文本,都是典型文本文件。
Java提供的操作文件
1、文件系统操作:用于文件的创建、删除、重命名等。
2、文件内容操作:针对一个文件的读和写。
File
File(File parent,String child) //根据父目录和子目录路径创建一个新的File实列
File(String pathname) //根据文件路径创建一个新的File实例
File(String parent,String chile)//根据父目录和子目录路径创建一个新的File实列,父目录用路径表示。
public class Demo1 {public static void main(String[] args) throws IOException {
// File file = new File("D:/1234/111.txt");File file = new File("./111.txt");System.out.println(file.getParent());//返回file的父目录文件路径System.out.println(file.getName());//返回file对象的文件名System.out.println(file.getPath());//返回file对象的文件路径System.out.println(file.getAbsolutePath());//返回file对象的绝对路径System.out.println(file.getCanonicalPath());//返回file对象修饰过的绝对路径}
}
上面两种file的用法分别是绝对路径与相对路径。
这是./111.txt的执行结果(相对路径),在没有指定基准目录的情况下,默认用执行项目的路径。
这是D:/1234/111.txt的执行结果(绝对路径)
下面的方法可以检测文件是否存在、是否为目录、是否是文件,有则输出true,没有返回false
public class Demo2 {public static void main(String[] args) {File file = new File("./111.txt");System.out.println(file.exists());//判断对象是否存在System.out.println(file.isFile());//判断File对象代表的文件是否是个目录System.out.println(file.isDirectory());//判断File对象代表的文件是否是个普通文件}
}
我们可以手动找到路径创建文件/目录,也可以通过代码创建:
file.createNewFile();
这里就不放截图结果了,大家可以自己去试验一下。