文章目录
- 多个atomic类连续调用能否构成原子性
- 实现一个容器,提供两个方法,add,size
- 写一个固定容量的同步容器,有put和get方法,以及getCount方法,能够支持两个生产者线程以及10个消费者线程的阻塞调用
- wait和notify的理解与使用
- wait和sleep的区别
- Java锁消除和锁粗化
- jvm对锁的优化
- 并发要满足的三大特性
- 并发的风险
- 为什么CAS能保证并发安全
- CAS性能优化
- 共享变量
- synchronized和lock的区别
- synchronized是乐观锁还是悲观锁
- 别再和面试官说Synchronized轻量级锁自旋了,错了!
- synchronized和reentrantLock的区别,哪个性能好一点
- 为什么Synchronized不可中断ReentranLock可以?
多个atomic类连续调用能否构成原子性
不能。打印出的值会大于1000 。多个原子性操作组合在一起并不能保证原子性。
实现一个容器,提供两个方法,add,size
package com.luban.demo13;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 一道面试题:实现一个容器,提供两个方法,add,size
* 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,
* 当个数到5个时,线程2给出提示并结束
*
* CountDownLatch
* 使用await和countdown方法替代wait和notify
* CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
* 相当于是发令枪,运动员线程调用await等待,计数到0开始运行
* 当不涉及同步,只是涉及线程通信的时候,用synchronized加wait,notify就显得太重了
*/
public class Container5 {
volatile List lists = new ArrayList();
public void add(Object o){
lists.add(o);
}
public int size(){
return lists.size();
}
public static void main(String[] args) {
Container5 c = new Container5();
CountDownLatch latch = new CountDownLatch(5);
new Thread(()->{
System.out.println("t2启动");
if (c.size() != 5) {
try {
latch.await();//准备
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t2结束");
}
}," t2").start();
new Thread(()->{
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "t1").start();
}
}
package hashmap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.*;
public class Demo {
List list=new ArrayList<Integer>(13);
public void add(int i)
{
list.add(i);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
Demo container=new Demo();
CountDownLatch latch = new CountDownLatch(5);
new Thread(()->{
System.out.println("t2 start");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (container.size()==5) {
System.out.println("The size of container is 5.");
}
System.out.println("t2 end");
}).start();
new Thread(()->{
System.out.println("t1 start");
for(int i=1;i<=10;i++){
container.add(i);
System.out.println("add:" + i);
latch.countDown();
if(i==5){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("t1 end");
}).start();
}
}
写一个固定容量的同步容器,有put和get方法,以及getCount方法,能够支持两个生产者线程以及10个消费者线程的阻塞调用
package com.luban.demo14;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* 使用Lock和Condition来实现
* condition就是在什么条件下怎么做
* Condition的方式可以更加精确的指定哪些线程被唤醒
*
*/
public class Container2<T> {
private final LinkedList<T> lists = new LinkedList<>();
private final int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(T t){
try {
lock.lock();
while (lists.size() == MAX) {
producer.await();
}
lists.add(t);
++count;
consumer.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get(){
T t = null;
try {
lock.lock();
while (lists.size() == 0) {
consumer.await();
}
t = lists.removeFirst();
count --;
producer.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
Container2<String> c = new Container2<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
for (int j = 0; j < 5; j++) {
System.out.println(c.get());
}
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
for (int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}
wait和notify的理解与使用
https://blog.csdn.net/coding_1994/article/details/80634792
notify
/**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the {@code wait} methods.
* <p>
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened thread will
* compete in the usual manner with any other threads that might be
* actively competing to synchronize on this object; for example, the
* awakened thread enjoys no reliable privilege or disadvantage in being
* the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. A thread becomes the owner of the
* object's monitor in one of three ways:
* <ul>
* <li>By executing a synchronized instance method of that object.
* <li>By executing the body of a {@code synchronized} statement
* that synchronizes on the object.
* <li>For objects of type {@code Class,} by executing a
* synchronized static method of that class.
* </ul>
* <p>
* Only one thread at a time can own an object's monitor.
上面的意思是notify唤醒线程任意的,但是也依赖于具体的实现,对于hotspot来说,唤醒是有序的,即等待队列中的第一个线程。
https://www.jianshu.com/p/99f73827c616
wait和sleep的区别
https://blog.csdn.net/qiuchaoxi/article/details/79837568
Java锁消除和锁粗化
https://blog.csdn.net/qq_26222859/article/details/80546917
jvm对锁的优化
https://www.jianshu.com/p/b5a031195202
并发要满足的三大特性
原子性,有序性,可见性
并发的风险
1、性能(线程上下文切换,保存现场、恢复现场)
2、活跃性问题(饥饿,死锁,活锁)
3、线程安全 解决:加锁,三大特性
为什么CAS能保证并发安全
假如说有3个线程并发的要修改一个AtomicInteger的值,他们底层的机制如下:
首先,每个线程都会先获取当前的值,接着走一个原子的CAS操作,原子的意思就是这个CAS操作一定是自己完整执行完的,不会被别人打断。这里可以说一下CAS的底层原理。
然后CAS操作里,会比较一下,现在你的值是不是刚才我获取到的那个值啊?
如果是的话,OK!说明没人改过这个值,那你给我设置成累加1之后的一个值!
同理,如果有人在执行CAS的时候,发现自己之前获取的值跟当前的值不一样,会导致CAS失败,失败之后,进入一个无限循环,再次获取值,接着执行CAS操作!
CAS性能优化
大量的线程同时并发修改一个AtomicInteger,可能有很多线程会不停的自旋,进入一个无限重复的循环中。
这些线程不停地获取值,然后发起CAS操作,但是发现这个值被别人改过了,于是再次进入下一个循环,获取值,发起CAS操作又失败了,再次进入下一个循环。
在大量线程高并发更新AtomicInteger的时候,这种问题可能会比较明显,导致大量线程空循环,自旋转,性能和效率都不是特别好,这个问题说到这里,恭喜你,你已经击败了80%的对手了大兄嘚!!!那么如何优化呢?
Java 8有一个新的类,LongAdder,他就是尝试使用分段CAS以及自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能,这个类具体是如何优化性能的呢?咱们看图说话:
LongAdder核心思想就是热点分离,这一点和ConcurrentHashMap的设计思想。就是将value值分离成一个数组,当多线程访问时,通过hash算法映射到其中的一个数字进行计数。而最终的结果,就是这些数组的求和累加。这样一来,就减小了锁的粒度
LongAddr的兄弟类如下:
共享变量
共享变量的意思就是每个线程都可以访问到的变量,比如:成员变量.
成员变量:
如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的。
局部变量:
如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝。他们之间的局部变量互不影响。
synchronized和lock的区别
https://zhuanlan.zhihu.com/p/180469207
synchronized是乐观锁还是悲观锁
https://blog.csdn.net/qq_42773863/article/details/107915065
别再和面试官说Synchronized轻量级锁自旋了,错了!
synchronized和reentrantLock的区别,哪个性能好一点
https://blog.csdn.net/xindoo/article/details/108185347