上午学习了一些关于算法之类的题目,突然想到io下面有个File类就有穿插着复习了一下,复习了昨天的内容。下午继续学习基础知识。知识点好多,感觉头发保不住了。明天继续。
关于java.io.File的方法
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CatalogTest {
public static void main(String[] args) {
File file = new File("C:\\Program Files");
//判断目录是否存在
/*System.out.println(file.exists());
if (!file.exists()){
//创建多重目录
file.mkdirs();
}*/
//文件的绝对路径
// System.out.println(file.getAbsolutePath());
//文件下的子目录
/*File[] files = file.listFiles();
for (File file1 :files){
System.out.println(file1.getName());
System.out.println(file1.getAbsolutePath());
}*/
//获取文件名
System.out.println(file.getName());
//判断是否是文件
System.out.println(file.isFile());
//文件的最后一次修改时间
//返回的时从1970年到现在的总毫秒数
long l = file.lastModified();
Date date = new Date(l);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String format = sdf.format(date);
System.out.println(format);
二分法查找
时间复杂度为O(log(n)
public class BinarySearch {
public static void main(String[] args) {
int[] a={1,3,5,7,9,11,12,15,16,17};
int i = binarySearch(17,a);
System.out.println(i);
int i1 = binarySearch1(17, a, 0, a.length-1);
System.out.println(i1);
}
//迭代
public static int binarySearch(int val,int[] a){
int min = 0;
int max = a.length-1;
while (min<=max){
int mid=(min+max)/2;
if (val<a[mid]){
max=max-1;
}else if (val>a[mid]){
min=min+1;
}else{
return mid;
}
}
return -1;
}
//递归
public static int binarySearch1(int val,int[] a,int left,int right){
int min = left;
int max = right;
int mid = (min+max)/2;
if (min<=max){
if (val==a[mid]){
return mid;
}else if(val<a[mid]){
return binarySearch1(val,a,min,mid-1);
}else{
return binarySearch1(val,a,mid+1,max);
}
}
return -1;
}
}
字符串的反转
public class ReserveString {
public static void main(String[] args) {
//定义一个字符串
String s = "dasdasdasdas";
StringBuffer result = reserve(s);
System.out.println(result);
}
public static StringBuffer reserve(String s){
//创建长度为s.length()的StringBuffer对象
StringBuffer stringBuffer = new StringBuffer(s.length());
for (int i = s.length()-1;i>=0;i--){
//循环追加到stringBuffer中
stringBuffer.append(s.charAt(i));
}
return stringBuffer;
}
}
集合
集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue)和 map(映射)。
- Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
- Iterator:迭代器,可以通过迭代器遍历集合中的数据
- Map:是映射表的基础接口
List.
Java 的 List 是非常常用的数据类型。List 是有序的 Collection。Java List 一共三个实现类:
分别是 ArrayList、Vector 和 LinkedList。
ArraryList(数组)
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除
LinkedList(双向链表)
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆
栈、队列和双向队列使用。
Vector(数组实现、线程同步)
Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢
Set
Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等性本质是对象 hashCode 值(java 是依据对象的内存地址算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode 方法和 equals 方法。
HashSet(Hash 表)
哈希表边存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。
TreeSet(二叉树)
TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。 Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用
Map
HashMap(数组+链表+红黑树)
HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑
树 组成
ConcurrentHashMap
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁
ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承
ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。在java8中对COncurrentHashMap使用了cas算法和sychrionaze实现。
HashTable(线程安全)o
HashTable相当于加了锁的hashmaop.任意的时间只有一个线程写入hashtable中。并发不如ConcurrentHashMap,在并发的环境下使用ConcurrentHashMap,在线程安全的环境下可以使用hashmap代替。
TreeMap(可排序)
TreeMap 实现 SortedMap 接口,底层是红黑树实现的,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器。
创建线程的四种方式
继承Thread
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
实现Runnable接口,重写run()方法
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个Runnable 接口
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
实现Callable接口,重写call()方法
当我们需要线程的返回值时,可以使用此方法。但时实现Callable方法会阻塞主线程的进行。所以慎重使用。
//实现Calla接口的方法会使主线程堵塞
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建未来任务类,实现Callable接口
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
Thread.sleep(2000);
int a = 10 ;
int b= 20 ;
return a+b;
}
});
Thread thread = new Thread(task);
//开启线程
thread.start();
//获取返回的值
Object o = task.get();
System.out.println(o);
System.out.println("main方法结束");
}
}
线程池创建线程
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
public class ExecutorTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池接收返回值
ExecutorService pool = Executors.newFixedThreadPool(10);
FutureTask task = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum =0;
for (int i = 0; i<=100;i++){
sum+=i;
}
return sum;
}
});
//为线程池分配任务
pool.submit(task);
//关闭线程池
pool.shutdown();
System.out.println(task.get());
//实现Runnable方法
/*for (int i = 0 ;i<10;i++){
pool.submit(new Runnable() {
@Override
public void run() {
int sum =0;
for (int i = 0; i<=100;i++){
sum+=i;
}
System.out.println(Thread.currentThread().getName()+":"+sum);
}
});
}*/
//关闭线程池
//pool.shutdown();
}
}
终止线程的方式
正常运行结束
程序运行结束,线程自动结束
使用退出标志退出线程
public class TheadTest {
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
new Thread(thread1).start();
Thread.sleep(5000);
//改变flag的值,结束线程
thread1.flag=false;
}
}
class Thread1 implements Runnable{
//做标记
public boolean flag = true;
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
if (flag){
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
//一秒输出一次
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//终止线程,结束run()方法
return;
}
}
}
}
stop 方法终止线程(线程不安全)
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。
4.1.6. sleep 与 wait 区别
1.对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。
2.sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用 sleep()方法的过程中,线程不会释放对象锁。
3而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才准备获取对象锁进入运行状态。
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作。
java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java中的悲观锁就是Synchronized
自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。自jdk1.7之后最大等待时间由jvm控制
如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。
Synchronized 同步锁
synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重
入锁
Synchronized 作用范围
- 作用于方法时,锁住的是对象的实例(this);
- 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
- synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
死锁
出现死锁不会出错,也不会报异常,比较难以调试。所以一班不要把Synchronized嵌套使用
public class DeadLock {
public static void main(String[] args) {
Object o1=new Object() ;
Object o2=new Object() ;
//o1 o2被两个线程所共享
new Thread(new MyThread(o1,o2),"t1").start();
new Thread(new MyThread1(o1,o2),"t2").start();
}
}
class MyThread implements Runnable{
private Object o1;
private Object o2;
public MyThread(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread1 implements Runnable{
private Object o1;
private Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
synchronized和Lock锁的区别
1、synchronized是Java内置的关键字;Lock锁是Java的一个类
2、synchronized无法获取到锁的状态;Lock锁可以判断是否获取到了锁
isLock()
3、synchronized会自动的释放锁;Lock必须要手动的去释放锁,如果不释放,就会造成死锁
5、synchronized适合锁少量的同步代码;Lock锁适合锁大两的同步代码
6、synchronized有代码块锁和方法锁;Lock只有代码块锁
7、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(拥有更多的子类)———————————————— 版权声明:本文为CSDN博主「默辨」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44377709/article/details/105845427
Condition控制线程的通信
Condition实例实质上时绑定到一个锁上,为特定的Lock实例获得Condition实例,在Condition实例中,与wait,notify,notifyall方法对应的时await,single,singleall.
公平锁与非公平锁
公平锁(Fair)
加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair)
加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
- 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列
- Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁。
锁优化
1.减少锁持有时间
只用在有线程安全要求的程序上加锁
2.减小锁粒度
将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是ConcurrentHashMap。