赞
踩
本文是对Java基础的一些个人总结,适合小白在进行基本的学习后所复习的总结文档,授人以渔不如授之以渔,在Java的学习中需要多敲多理解,所以本文中代码数量占少,所需要理解的地方居多。本文为个人理解,如有需要订正之处,敬请指出,本人邮箱1209110434@qq.com,在之后也会更新Spring系列等个人总结。最后,希望各位在Java之路越走越远。Java之路道阻且长,最后一句话:Java之路为者长存,行者长至–送给你也送给我
缺点:不利于扩展
class myThread extends Thread{
public static void main(String[] args) {
Thread mythread = new Thread();
mythread.start();
System.out.println("主线程");
}
@Override
public void run() {
System.out.println("线程启动成功");
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}
Thread thread = new Thread(() ->System.out.println("ss"));
thread.start();
}
缺点:重写的run方法没有返回值
创建实现Callable接口的类myCallable
以myCallable为参数创建FutureTask对象
将FutureTask作为参数创建Thread对象
调用线程对象的start()方法
public class CallableTest {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
try {
Thread.sleep(1000);
System.out.println("返回结果 " + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}
}
使用 Executors 工具类创建线程池
Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool,后续详细介绍这四种线程池
方法一:同步代码块
锁对象只要对于同时操作的锁唯一(静态方法用类目.class,实例方法用this)
ctrl alt t
方法二:在方法上加上synchronized+ 操作共享资源的代码
方法三:lock锁
线程池:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
}
//线程中的常用方法 /* 1.start --启动一个新线程,让线程进入就绪状态等待jvm的调用 2.启动后调用run方法(不start 直接调用会在主线程中跑,不会发挥多线程的作用) 3.join --等待线程结束 (可设置等待时间) 4.set,getname 5.get set priority (1-10)设置优先级 6 getstate--获取线程运行的状态 7.isinterrupted ---布尔值判断是否被打断 interrupt 打断线程(如果线程正在执行其他操作如sleep wait join 则会抛出异常,正常打断会设置打断标记) 8.isalive --判断线程是否还存活 9.currentthread --获取当前正在运行的线程(静态) 10.sleep 使线程休眠让出cpu 的使用权(静态)(睡眠被打断会抛出异常,睡眠结束后未必会立刻执行) 11。 yield --提示让出cpu主要是为了测试和调试 @Test void contextLoads() throws Exception { //线程创建方式一 Thread thread1 = new Thread("t1") { @Override public void run() { System.out.println("线程一被启动"); } }; thread1.start(); System.out.println(thread1.getState()); //线程创建方式二 Thread thread2 = new Thread(() -> System.out.println("线程二被执行")); thread2.start(); //1.第一种睡眠 thread2.sleep(20); //第二种 SECONDS.sleep(1); //线程创建方式三 FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("线程三被执行"); return 11; } }); Thread thread3 = new Thread(integerFutureTask); thread3.start(); System.out.println(integerFutureTask.get()); //线程创建方式四:利用线程池创建线程 /* 1.所有线程类都直接或者间接继承ExecutorServise但是顶级接口都继承于Executor 2.线程池有两种创建方式1.通过工具类Executors创建 2.1newFixedthreadpool(int nThread )线程池中含有固定数目的线程,空线程会一直保留,等待队列是无限的linkedMQ 2.2newSingleThreadExecutor--线程池中只有一个线程,它依次执行某些任务,任务排队是无限的 2.3Executors.newCachedThreadPool();,核心线程是0有任务时才创建线程,且全部是救急线程,并可以无限创建空闲线程被保留60秒 2.4Executors.newScheduledThreadPool();线程池能够按照计划执行任务 corepoolsize是线程池中的最小数目,任务较多时会创建更多的工作线程来执行任务 2.5newSingleThreadScheduledExecutor--线程池中只有一个任务,而且会按照时间来执行任务 3.手动创建线程池对象(推荐)方便自己定义参数 int corePoolSize,--核心线程数 int maximumPoolSize,--最大线程数 long keepAliveTime,--救急线程存活时间 TimeUnit unit,--救急线程时间单位 BlockingQueue<Runnable> workQueue,--任务队列 1.ArrayBlockingQueue基于数组结构的有界阻塞队列(先进先出) 2.LinkedArrayBlockingQueue--基于链表的有界阻塞队列吞吐量高于第一个,先进先出---上fixedThreadpool运用了此策略 3.priorityArrayBlockingQueue---具有优先级别的队列 4.synchronousQueue--一个不存储元素的阻塞队列,每次插入操作必须等待另一个线程操作,可以视为只有一个元素的队 ThreadFactory threadFactory,--用于创建线程的工厂 RejectedExecutionHandler handler--饱和策略 Abortpolicy:默认策略,线程池满了抛出异常 DiscardOldestpolicy:丢弃队列里执行最久的 CallerRunsPolicy:线程池满了会丢弃新加入的任务并且不会报异常 */ ExecutorService executorService1 = Executors.newFixedThreadPool(5); ExecutorService executorService2 = Executors.newSingleThreadExecutor(); ExecutorService executorService3 = Executors.newCachedThreadPool(); ExecutorService executorService4 = Executors.newScheduledThreadPool(5); ExecutorService executorService5 = Executors.newSingleThreadScheduledExecutor(); //ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 10, SECONDS, 10);/* 线程运行原理: 线程在启动后栈区会分配内存给线程 线程的上下切换,1.使用cpu的时间片用完了2.垃圾回收暂停所有线程3.有更高级的线程需要运行4.线程自己调用了sleep, yield,join,park ,synchronize,lock 等方法 利用程序计数器记录下一条JVM指令的执行地址,并且是线程私有的 线程的生命周期 1.new 初始状态--值、指线程的创建,创建线程后jvm 会为其分配空间 2.runnable可运行状态--对象调用strat方法后进入就绪状态,虚拟机会为其创建程序计数器和本地方法栈 3.running 运行状态--处于执行状态指该线程获得了cpu开始执行run方法,该线程则处于运行状态 4.block:阻塞状态 指因为某种原因放弃了cpu使用,让出cpu 暂停运行 阻塞分为三种 4.1.等待阻塞--运行执行线程中的o.wait方法 ,jvm会把该线程放在等待队列 4.2.同步阻塞 5.deda 死亡状态 线程已经执行完毕,不会再转为其他状态 */
1.多个线程去读取共享资源,一个代码块内对多个共享资源多线程进行读写操作叫做临界区
2.由于代码的执行序列不同导致结果无法预测,叫做境界条件
解决多线程临界区的竞态条件产生的方案
阻塞式方案 1 .synchronize保护临界区
1.1 即使锁内的第一个线程时间片用完了被踢出去但是们还是锁住的2线程同样进不来,等到了1线程的时间片才继续运行执行完后锁才会让出来
1.2关键字加在成员方法上 锁住的是this 加在静态方法上锁住的是类对象
2.lock
成员变量和静态变量安全性:
被共享了变量只有读操作线程安全,如果有读写操作则需要考虑线程安全性问题
局部变量: 局部变量是线程安全的,引用类型的对象局部变量是安全的(对象没有逃离方法范围,Return 后线程不安全)
继承后线程共享资源局部变量引用会造成多线程问题(可以用final ,priviate 增强线程的安全性)
线程安全多个线程调用他们同一个实例的某个方法时是线程安全的
String ,包装类,Stringbuffer,random,vector,hashtable ,java concurrent 下的包,他们的每个方法都是原子的,但是组合在一起 不一定是线程安全的(只是指方法内的执行是线程安全的)
String 底层是对原有的对象进行一层复制再进行其他的操作,只是创建了新的字符串对象,再赋值给原字符串对象
实现线程安全成员变量条件:成员变量不可变则是线程安全的
Java对象底层结构:synchronize关键字绑定锁的对象结构
synchronize(lock)//lock结构包括klassword(指类对象)和markword
关键字使用
总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
Moniter又名监视器和管程 ,加锁时该对象会关联一个moniter对象通过markword创建指针指向monitor
markword格式
状态
01 默认值,没有关联Monitor对象
00 轻量级锁被启用
10 重量级锁 对象markword通过指针关联moniter后 存储锁住对象的地址
Moniter结构
获得锁进入ownner ,没有获得到锁是进入entrylist进入阻塞状态后等获得锁线程释放锁时进行竞争,对象总是与锁关联
注意:synchronize 必须是进入同一个对象才有以上效果,加synchronize才会关联监视器
弊端:这种锁耗费资源过大
轻量级锁:适用访问时间错开没有竞争,在加入synchronize修饰时初始锁位轻量级锁
轻量级锁原理:
线程在运行栈帧时会加载锁记录,锁记录里lockrecord会与锁对象mark word进行一次(CAS操作)
缺点:每次重入都需要进行一次CAS 操作)
解锁时利用CAS 将mark word依次值恢复给对象头
锁膨胀:
CAS操作无法成功会进行锁膨胀,将轻量级变为重量级1.申请moniter 锁后两位变成10后同上
自旋优化:
重量级锁让线程2多进行几次自循循环避免线程进行阻塞(适合多核CPU)
自旋失败则会进入阻塞
偏向锁:
只有第一次将线程ID设置到mark word头,同意一个线程多次获取相同的锁,且访问时不用重新CAS该对象归该线程所有即为偏向锁
偏向锁:如果开启了偏向锁,对象创建后三位为101(biased_lock为1),它的thread,epoch,age都是0,加锁时会加入进去偏向锁默认时是延迟的(可以对vm配置进行修改)
撤销可偏向:对象竞争或有第二个线程尝试获取锁
禁用偏向级锁,在测试代码机上VM参数 –usebiaselocking
可偏向状态调用hashcode后会撤撤销偏向状态
批量重偏向:偏向锁的偏向可以改变,当撤销偏向锁阀超过20次后,会重新进行偏向
批量撤销:当撤销偏向锁阀值超过40次时,会撤销偏向状态变成不可偏向
public class guardobject { private Object response; public Object getResponse(long timeout) { long begin= System.currentTimeMillis(); long passtime = 0; synchronized (this){ while (response==null){ if (passtime >= timeout){ break;} try { this.wait(timeout - passtime);//避免虚假唤醒时等待的时长 } catch (InterruptedException e) { e.printStackTrace();} } passtime= System.currentTimeMillis()-begin; return response;} } public void compeletget(Object response){ synchronized (this){ this.response = response; this.notify();}}}
wait -notify原理:
wait¬ify是Object类的方法且调用wait 方法时会释放锁
wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用,必须先获得锁对象,再使用锁对象调用
synchronize(lock){
wait()
wait(long time)
lock.notify//(随机唤醒一个正在等待的线程)
lock.notifyAll//(唤醒所有在waitset的线程重新进入entrylist)
}
注意:使用wait方法需要避免错误警报和虚假唤醒,可利用死循环判断条件是否成立
synchronized (lock) {
// 判断条件谓词是否得到满足
while(!false) {
// 等待唤醒
monitor.wait();
}
// 处理其他的业务逻辑
}
sleep和wait区别
指一个线程等待一个线程的结束 ,应用保护性暂停设计模式。(等待谁就调用谁)
class mesageque { private LinkedList<message> list = new LinkedList(); private int capacity; public message take (){ synchronized (list) { while (list.isEmpty()) { try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } message message = list.removeFirst(); list.notifyAll(); return message; } } //存入消息 public void put(message msg){ synchronized (list){ while (list.size() == capacity){ try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.addLast(msg); list.notify(); } } } final class message{ private int id; private Object value ; public int getId() { return id; } public void setId(int id) { this.id = id; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } public message(int id, Object value) { this.id = id; this.value = value; } @Override public String toString() { return "message{" + "id=" + id + ", value=" + value + '}'; } }
是使用locksupport类中的方法locksupport.park暂停线程,unpark恢复暂停线程对象
特点:
底层原理:每个线程都有自己的一个parker对象由_counter,_cond,_mute组成
counter状态有:0和1,0代表线程不可park,1代表线程可park
调用park对象:
调用unpark
新建(new):新创建了一个线程对象。
可运行(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
阻塞的情况分三种:
死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
线程调用start方法时
线程用synchronized(obj) 获取了对象锁后
t线程用synchronized(obj) 获取了对象锁后
调用obj.wait(1ong n)方法时,t 线程从RUNNABLE ==> TIMED_ WAITING
t线程等待时间超过了n毫秒,或调用obj .notify(), obj. notifyA1l(), t. interrupt()时
当前线程调用t.join(1ong n)方法时,当前线程从RUNNABLE ==> TIMED_ WAITING注意是当前线程在线程对象的监视器上等待
当前线程等待时间超过了n毫秒,或t线程运行结束,或调用了当前线程的interrupt()时,当前线程从TIMED_ WAITING ==> RUNNABLE
当前线程调用Thread.sleep(1ong n),当前线程从RUNNABLE => TIMED_ WAITING
当前线程等待时间超过了n毫秒,当前线程从TIMED_ WAITING --> RUNNABLE
线程调用了LockSupport.parknaos(long naos)或LockSupport.parkuntil(long millis)t 线程从RUNNABLE ==> TIMED_ WAITING
调用unpark或者线程的interrupt方法或者超时会使线程变成TIMED_ WAITING --> RUNNABLE
在并发中可以设置多把锁来提高并发量,但是容易发生死锁
**死锁概念:**当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
产生死锁的条件:
互斥条件:所谓互斥就是进程在某一时间内独占资源。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
防止死锁:
补充:
定位死锁–利用jconsole ,利用jps, jstack进程ID
活锁:两个线程互相改变对方的结束条件,导致循环得不到结束,任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
饥饿:线程优先级太低,始终得不到CPU调度执行
public class park { static Thread t1;static Thread t2;static Thread t3; public static void main(String[] args) { parkunpark p = new parkunpark(5); t1 = new Thread(()->{ p.print("a",t2); }); t2 = new Thread(()->{ p.print("b",t3); }); t3 = new Thread(()->{ p.print("c",t1); }); t1.start();t2.start();t3.start(); LockSupport.unpark(t1); }} class parkunpark{ private int loopnumber; public parkunpark(int loopnumber){ this.loopnumber = loopnumber; } public void print(String str, Thread next){ for (int i = 0; i < loopnumber; i++) { LockSupport.park(); System.out.print(str); LockSupport.unpark(next); }}}
运用reentred
public class test12 { public static void main(String[] args) throws InterruptedException { reentred re = new reentred(5); Condition condition1 = re.newCondition(); Condition condition2 = re.newCondition(); Condition condition3 = re.newCondition(); new Thread(() -> { re.print("a", condition1, condition2); }).start(); new Thread(() -> { re.print("b", condition2, condition3); }).start(); new Thread(() -> { re.print("c", condition3, condition3); }).start(); Thread.sleep(1000); re.lock(); try { condition1.signal(); }finally { re.unlock(); } } } class reentred extends ReentrantLock { private int numbers; public void print( String str,Condition conn ,Condition nextcondition){ for (int i = 0; i < numbers; i++) { try { conn.await(); System.out.print(str); nextcondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { unlock(); } } lock(); } public reentred(int numbers) { this.numbers = numbers; } }
volatile可以修饰成员变量和静态成员变量,他可以避免线程从自己工作的缓存中查找变量的值,必须到主内存中获取他的值,线程操作volatile 都是直接操作主存。synchronize 也可以保证此效果。
volatile不能保证原子性,适合一个或多个线程是读取变量,另一个线程是修改变量,不适合于指令交错的场景。
jvm会在不影响正确性的前提下,可以调整语句的执行顺序会自动进行指令重排序,加volatile关键字会避免在多线程下的指令重排序,可以保证在之前的变量不会重排序
底层原理:volatile 底层原理是内存屏障
对volatile变量的写指令会加入写屏障–保证在该屏障之前,对共享变量的改动,都同步到主内存中–保证可见性,保证之间的代码不会进行指令重排–保证有序性
对volatile 变量的读指令会加入读屏障,在读取操作之后的代码,都会同步更新到主内存–保证可见性,在读屏障之后的情况都不会读到屏障之前。
dcl(double-check-locking)问题分析:
最初始:两次加锁 解决:–在变量上加volatile
synchronize–在区域范围内保证原子性,可见性,有序性
volatile–被修饰的变量写会同步到主存中,读操作才可见
线程start之前操作的写,对该线程操作之后的读可见
线程结束前对变量的写入,对其他线程得知它结束后的读可见
线程一打断线程二前对变量的写,对于其他线程得知线程二被打断后的变量的的读可见。
对变量默认值的写,对其他线程对该变量的读可见
乐观指不怕别人修改,改了再重试
CAS(compare and set//compare and swap)–保证原子性
每次操作线程时都会进行比较,比较成功后才会进行设置
底层为了保证原子性和可见性底层运用了volatile
性能分析:synchronize于无锁性能分析:synchronize容易进行上下文切换,无锁会减少上下文切换,锁效率会高(线程数小于cpu时)
原子整数:AutomaticInteger(加减法)–getAndadd,inctrmentandget
updateandget(x->x*10)–更新同时设置
原子引用:
AtomicReference
AtomicMarkbleReference(判断是否被更改过)
AtomicStampeReference(判断版本号可以解决ABA问题)
解决ABA问题
(线程修改没有被线程察觉到)–AtomicStampeReference(根据版本号判断是否被修改过)AtomicMarkbleReference–只关注有没有被更改过
atomicIntegerarray
atomiclongarray
atomicreferencearray;(保护数组的元素)
字段更新器:
是基于反射的工具类,用来将指定类型的指定的volatile引用字段进行原子更新,对应的原子引用字段不能是private的。通常一个类volatile成员属性获取值、设定为某个值两个操作时非原子的,若想将其变为原子的,则可通过AtomicReferenceFieldUpdater来实现
atomicreferencefieldupdate,
atomicintegerfieldupdate,
atomiclongfieldupdate保护对象里的属性,保护成员变量的安全性(属性必须是volatile)
public class AtomicReferTest { public static void main(String[] args) throws Exception { AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dry.class,String.class,"name"); Dry dry=new Dry(); System.out.println(updater.compareAndSet(dry,"dry","compareAndSet")); System.out.println(dry.name); System.out.println(updater.getAndSet(dry, "getAndSet")); System.out.println(dry1.name); } } class Dog { volatile String name="dry1"; }
原子累加器:
jdk1.8 longadder(notremmber)–重要
LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。–获取最终结果通过 sum 方法,将各个累加单元的值加起来就得到了总的结果
不可变类设计:
成员变量都是不可变的,类对象也需要加上final,保证不可以被继承。String等不可变类都是保护性拷贝(底层赋值)。
tips:单个方法是不可变的
享元设计模式(适用重用数量有限的同一类对象)包装类
final变量原理:final在设置之后会加入写屏障
ForkJoin是JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的cpu密集型运算
所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解
Fork/Join在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进-步提升了运算效率
Fork/Join默认会创建与cpu核心数大小相同的线程池
使用:需要继承Recursivetask(有结果),RecursiveAction
1.创建线程池对象(forkjoin线程池)
2.执行任务
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
特点:
用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何
获取锁和释放锁 getState -获取state状态 setState .设置state状态
compareAndSetState - cas机制设置state状态
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源提供了基于FIFO的等待队列,类似于Monitor的EntyList条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet
AQS 对资源的共享方式
AQS定义两种资源共享方式
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
跟synchronize相比:
用法: 创建对象
可打断:其他线程可以用interrupt打断该线程(指打断无止境的等待锁)
锁超时:获取锁的线程等待不到超过了时间则放弃锁等待trylock
公平性:reentrantlock默认时不公平锁(可设置为公平锁)设置fair性 先入先得
条件变量:支持多个条件变量,有多个休息室
Condition condition1 = reentrantLock.newCondition();
Condition condition2 = reentrantLock.newCondition();
reentrantLock.lockInterruptibly();
try{
condition1.await();//进入休息室
condition1.signal();//唤醒
condition1.signalAll();//唤醒所有
}
finally {
reentrantLock.unlock();
}
/*await前需要获得锁
await执行后, 会释放锁,进入conditionObject等待
await的线程被唤醒(或打断、或超时)取重新竞争lock锁
竞争lock锁成功后,从await后继续执行*/
原理:
加锁
通过CAS操作将state从0改到1并获得线程对象(exclusiveOwnerThread)
竞争出现时
释放锁:
释放锁进入进入TryReleaser如果成功
可重入原理:同步器会判断该线程是否是已获得锁的线程
可重入后释放:多次释放
可打断原理(默认不可被打断)
非公平锁:不会去检查AQS队列,直接抢夺线程
条件变量原理:
await原理
持有锁线程调用await,进入ConditionObject的addConditionwait的双向链表
创建新的Node并设置为-2
进入fullyrelease释放同步器上的锁
unpark AQS队列中的下一个节点,竞争锁
singal
读读不互斥可并发,读写,写写互斥
注意事项:
原理(后看)
进一步优化性能特点是在使用读写锁是都必须配合戳使用
乐观读(tryOptimistcRead)在读取完毕之后进行一次戳校验(校验是否有写操作,失败后锁会升级)
信号量,用来限制能够同时访问共享资源的线程数
原理:(tryRelease)
释放:(tryrelease)
原理:
用来进行线程同步协作,等待其他 线程完成倒计时(等待线程运行结束)
await用来等待计数归零,countDown()用来让计数减一
循环栅栏,用完后会立即恢复。线程池线程数要跟cyclicbarrier
Blocking类:大部分实现基于锁,并提供用来阻塞的方法
Copyonwrite:采用拷贝方式(底层拷贝)适用读多写少的形式
concurrent:内部应用CAS优化,提高吞吐量
遍历时弱一致性
为什么使用CurrentHashMap。
在多线程环境中使用HashMap的put方法有可能导致程序死循环,因为多线程可能会导致HashMap形成环形链表,即链表的一个节点的next节点永不为null,就会产生死循环。这时,CPU的利用率接近100%,所以并发情况下不能使用HashMap。
HashTable通过使用synchronized保证线程安全,但在线程竞争激烈的情况下效率低下。因为当一个线程访问HashTable的同步方法时,其他线程只能阻塞等待占用线程操作完毕。
ConcurrentHashMap使用分段锁的思想,对于不同的数据段使用不同的锁,可以支持多个线程同时访问不同的数据段,这样线程之间就不存在锁竞争,从而提高了并发效率。
原理:
sizeCtl:默认值为0,初始化时值为-1,扩容时为-(1+扩容数)
Node:本质是一个map,是哈希链表结构的最小单元,包括键值,哈希值,next
ForwardingNode:表示在扩容中表示该链表被处理过即处理完毕的节点
TreeBIn:作为红黑树的头节点储存root和first(如果哈希表长度大于64时)
TreeNode:作为红黑树的节点(储存parent,left,right)
构造器:
jdk8时实现懒惰初始化,等到真正用时才去创建
get:
put:
扩容 计数addcount(利用Long adder)
jdk7:
移位计算segement(继承rentrantlock)下表
put:
**get:**用unsafe方法保证可见性
CopyOnWriteArrayList 是一个并发容器,非复合场景下操作它是线程安全的。
CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出 ConcurrentModificationException。
在CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。合适读多写少的场景。
缺点:
CopyOnWriteArrayList 的设计思想
读写分离,读和写分开
最终一致性
使用另外开辟空间的思路,来解决并发冲突
感谢在学习途中乐于分享的每一位码员,有了你们的分享才让学习者变得更轻松
断是否有头节点
- 利用CAS创建头节点
扩容 计数addcount(利用Long adder)
jdk7:
移位计算segement(继承rentrantlock)下表
[外链图片转存中…(img-eZlodthb-1647538724745)]
put:
**get:**用unsafe方法保证可见性
CopyOnWriteArrayList 是一个并发容器,非复合场景下操作它是线程安全的。
CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出 ConcurrentModificationException。
在CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。合适读多写少的场景。
缺点:
CopyOnWriteArrayList 的设计思想
读写分离,读和写分开
最终一致性
使用另外开辟空间的思路,来解决并发冲突
感谢在学习途中乐于分享的每一位码员,有了你们的分享才让学习者变得更轻松
作者:Yang11
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。