1.并发.txt
UP 返回
【视频1】 并发
线程调用yeild方法,就是将当前线程空出CPU,进入等待状态,让CPU重新去选取需要被执行的线程。当前线程仍然有可能继续被选中执行。
线程1中调用线程2的join方法,就是等到线程2执行结束以后,再回到线程1的执行。所以join可以保证线程按顺序执行
线程的6种状态:(ready和running都输入runnable状态)
!!@@202212252.png_1045_571_1@@!!
synchronized的底层实现:
jdk早期都是重量级锁,后来改进有了锁升级的概念:sync(Object)时,对象的markword记录这个线程id,此时就是偏向锁。如果发生了线程争用,就升级为自旋锁,自旋锁占用CPU。自旋10次以后仍然获取不到,升级为重量级锁,即os锁
执行时间短(枷锁代码执行的时间),线程数少,适用于自旋锁;执行时间长,线程数多,适用于系统锁
synchronized不能锁string常量 Integer Long
线程的一种写法:new Thread(T::m, "t1").start() 相当于在线程的run方法中调用T的m方法
!!@@202212262.png_422_573_1@@!!
volatile 保证线程可见性,禁止指令重排序
堆内存的空间是共享的,但是每个线程也有自己的空间,对于一些变量时拷贝到线程空间使用的,那么在a空间中被修改的变量不一定可以及时被b空间的线程读到
对于单例的双重判断中,初始化A a = new A()中存在指令重排序:1.申请对象内存,此时对象属性都是默认值 2.对象属性赋值 3.对象地址赋值给变量。当a线程初始化单例时,如果重排序,1执行后直接执行3,那么b线程直接在外面的null判断中就会认为初始化已经完成,而将未完全赋值完的对象拿去使用
实现多线程累加同一个变量的方式:
1.synchronized加锁
2.AutomicLong.incrementAndGet 自增。多线程下优于1
3.LongAdder.increment 通过空间换时间的方式,维护一个base和cell数组,正常情况下累加到base上,当发生冲突时则加到cell上,最终将base和所有的cell累加获取最后的值
因为AutomicLong每次只有一个能够CAS成功,失败的则继续重试,所以性能会越来越差。在高争用情况下,LongAdder的预期吞吐量要高得多,代价是更高的空间消耗
需要注意最后的求和sum方法,返回值只是一个接近值,并不是一个准确值。它在计算总和时,并发的更新并不会被合并在内;如果业务场景追求高精度,高准确性,还是用AtomicLong 参https://www.cnblogs.com/kuotian/p/13583138.html
ReentrantLock和synchronized对比
Lock lock = new ReentrantLock()
lock.lock() 加锁
lock.unlock() 解锁
两者都是可重入的。ReentrantLock有自己的一些独特的方法
1. 可以尝试获取锁,技师获取失败也可以继续往后执行;而synchronized获取不到就会一直阻塞
locked = lcok.tryLock(5, TimeUnit.SECONDS); 尝试在5s内获取锁
!!@@202303081.png_707_242_1@@!!
2. 可以响应intterupt。如果使用lock,线程执行到这里获取不到锁会一直等待,而使用lockInterruptibly后,后面可以继续调用该线程的interrupt方法来打断等待
lock.lockInterruptibly()
!!@@202303082.png_570_355_1@@!!
3. 当初始化时给true,那么就是一个公平锁,而synchronized是非公平的。公平锁讲究先来后到,会维护一个等待队列,新来的线程如果发现队列里有就加入,没有才会去拿锁。但是公平锁并不保证一定公平,对于刚执行完的线程如果继续进入等待队列,有可能又能拿到锁,而等着的另一线程有就会继续等待
ReentrantLock lock = new ReentrantLock(true);
CountDownLatch 用法如下: 每一个线程结束以后latch.countDown()一下,直至所有线程结束,此时latch.await()阻塞结束;此效果等同于每一个线程join到主线程中(当然countDown可以主动多次调用,更灵活一些)
!!@@202304053.png_849_635_1@@!!
CyclicBarrier 所有的线程都在barrier.await(),直到满了20个,执行第二个参数的操作。第二个操作可不传,表示啥都不做
!!@@202304054.png_989_652_1@@!!
Phaser Phaser会将一个线程的执行分成好几段,我们创建自己的Phaser类,继承Phaser即可,此时需要重写他的onAdvance(int phase, int registeredParties)方法,第一个参数表示阶段值,当数字满了的时候,即累积的线程数=registeredParties时,阶段会自动向下一个阶段值进发
我们定义了多个Person类,这些类有四个方法,arrive eat leave hug,每一个方法中的phaser.arriveAndAwaitAdvance()表示线程到此进入计数阻塞,而phaser.arriveAndDeregister()则表示线程至此退出计数,相应的Phaser的registeredParties也会自动减小
在Person的run中顺序调用了四个方法,我们定义了一个自己Phaser,同时给他注册值registeredParties=7。后面创建5个普通人,一个新郎一个新娘,于是可以看到程序如下执行:
五个人arrive,到达arriveAndAwaitAdvance阻塞;新郎新娘也arrive,阻塞。阻塞达到了7个人,于是触发一次onAdvance
七个人均可以执行eat,同时每一个人eat以后阻塞。这样七个人都在eat阻塞满后触发onAdvance
相同的继续执行各自的leave,继续阻塞。阻塞满继续触发onAdvance
普通的的五个人执行hug方法,但是直接arriveAndDeregister,于是registeredParties减小到2;只有新郎新娘继续阻塞,同时线程数也达到了2,于是继续进入下一阶段(拥抱,最后婚礼结束)
!!@@202304066.png_863_642_1@@!! !!@@202304062.png_1072_703_1@@!! !!@@202304064.png_819_656_1@@!! !!@@202304065.png_774_652_1@@!!
ReadWriteLock ReadWriteLock实现了lock,通过readLock()获取读锁,writeLock获取写锁。读锁之间是不排斥的,可以同时读,写锁才是排它锁。
在下面这个例子中,如果读写都用普通的lock,那么每一个读都得sleep结束才能被其他线程获取锁,而如果我们在读线程中用read锁,写线程用write锁,那么所有的读就可以同时完成,大大提升效率
!!@@202304067.png_1046_607_1@@!! !!@@202304068.png_657_492_1@@!! !!@@202304069.png_964_448_1@@!!
Semaphore 信号量,初始化定义的数字即表示可以有几个线程同时运行,可以起到限流的作用 acquire方法请求运行,此时信号量数字-1,减到0以后就无法继续获取了;release方法释放
Semaphore的初始化可以塞第二个参数,表示是否是公平锁,即true时后面来的线程不会插队到前面等着的
!!@@202304091.png_1071_671_1@@!!
Exchanger 两个线程间可以交换数据,exchange(s)方法会阻塞,将值放入后等待另一个线程执行exchange方法,然后两个值交换返回
!!@@202304092.png_1261_696_1@@!!
锁的分类:
乐观锁 CAS
悲观锁 synchronized
自旋锁 CAS for循环
读写锁 读锁(共享锁) 写锁(排它锁)
分段锁 LongAdder ConcurrentHashMap
DOWN 返回