void 取款(){
int oldBalance =balance
if(!CAS(balance,oldBalance,oldBalance-500)){
}
}
在这个线程中如果变成了这样
void 取款(){
int oldBalance =balance
void 取款(){
int oldBalance =balance
if(!CAS(balance,oldBalance,oldBalance-500)){
}有人转账发生了500->1000。
}
if(!CAS(balance,oldBalance,oldBalance-500)){
}
}
这两个东西不相等,就会产生bug,上述的过程就属于ABA问题的典型bug场景是非常高极端的情况
如何避免ABA问题呢??
核心思路是,使用账户余额判定,本身就不太科学.账户余额本身就属于"能加也能减”,就容易出现ABA问题,
引入"版本号”,约定版本号,只能加,不能减~~每次操作一次余额,版本号都要+1通过CAS判定版本号,就可以进一步的来完成上述的操作~~
一个版本号走一步
JUC中的常见类
Callable接口
public class Test {
private static int sum=0;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
int result=0;
for (int i = 0; i <=1000 ; i++) {
result+=i;
}
sum=result;
}
});
t.start();
t.join();
System.out.println(sum);
}
}
Thread t=new Thread(callable)
Callable不能直接填写到Thread构造方法中
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int result=0;
for (int i = 0; i <10000 ; i++) {
result+=i;
}
return result;
}
};
//这是FutureTask的包装类
FutureTask<Integer>futureTask=new FutureTask<>(callable);
Thread thread=new Thread(futureTask);
thread.start();
thread.join();
System.out.println(futureTask.get());
}
}
FutureTask就像中间上来赚取差价。靠他们中间。
线程创建的方式:
1)继承Thread
2)使用Runnable
3)使用lambda
4)使用线程池/ThreadFactory
5)使用Callable
ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
}finally {
lock.unlock();
}
}
}
1.ReentrantLock提供了公平锁的实现synchronized只是非公平锁。
ReentrantLock lock = new ReentrantLock(true);
2.ReentrantLock提供tryLock操作.
给加锁提供了更多的课操作空间尝试加锁,如果锁已经被获取到了,直接返回失败,而不会继续等待。
Synchronized都是遇到锁竞争,就阻塞等待.(死等)
tryLock除了直接返回这种形态之外还有一个版本,可以指定等待超时时间
3.synchronized是搭配waitnotify等待通知机制
ReentrantLock是搭配Condition类完成等待通知Condition要比waitnotify更强一点(多个线程wait,notify是唤醒随机的一个.Condition指定线程唤醒)
信号量Semaphore
信号量就是一个计数器,描述了可用资源的个数
围绕信号量有两个基本操作
1.P操作.计数器-1.申请资源
2.V操作.计数器+1.释放资源
通过Semaphore完成
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
private final static Semaphore semaphore = new Semaphore(1);
public static void main(String[] args){
ExecutorService pools = Executors.newCachedThreadPool();
for (int i=0 ; i < 10;i++){
final int index = i;
Runnable run = new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//使用完成释放锁
semaphore.release();
System.out.println("锁释放");
}
}
};
pools.execute(run);
}
pools.shutdown();
}
}
CountDownLatch
多线程下载.
把一个大的文件,拆分成多个部分.比如20个部分
每个线程都独立和人家服务器建立连接,分20个连接进行下载
等所有线程下载完毕之后,再结果进行合并.
CountDownLatch
import java.util.concurrent.CountDownLatch;
public class Test3 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
latch.countDown();
latch.await();//await会阻塞等待,一直到countDown调用的次数,和构造方法指定的次数一致的时候,,await才会返回
}
}
Vector自带了synchronized加锁不能保证线程一定安全
Stack继承自Vector,也自带了synchronized不加锁也不能确定线程一定不安全
Hashtable也是自带synchronized一定要具体代码具体分析~~
Vector<String>vector=new Vector<>();
void func(){
if(vector.size()>0){
System.out.println(vector.get(0));
}
}
比如t1纟线程执行func到if之后t2线程把巴vector清空了回到t1继续执行,get(0)就出问题了,
Collections.synchronizedList(new ArrayList)
给ArrayList这些集合类,套一层壳
壳上是给关键方法都加了synchronized就可以使ArrayList达到类似于Vector效果。
CopyOnWriteArrayList
写时拷贝
修改的同时,很多线程在读呢如果直接修改,不加任何处理意味着有的线程可能会读到200,3这种“中间情况。
写完之后,用新的数组的引用,代替旧的数组的引用(3|用赋值操作,是原子的)上述过程,没有任何加锁和阻塞等待,也能确保读线程不会读出“错误的数据旧的空间就可以释放了。
多线程使用队列
直接使用BlockingQueue即可.
HashMap是线程不安全的Hashtable是带锁的,是否就线程更安全呢??但是这个并不推荐使用
Hashtable加锁就是简单粗暴给每个方法加了synchronized.就相当于是针对this加锁.只要针对Hashtable上的元素进行操作,就都会涉及到锁冲突,
ConcurrentHashMap做出了优化
1.使用“锁桶”的方式,来代替“一把全局锁”,有效降低锁冲突的概率
另一方面,看起来锁对象多了,实际上也不会产生更多的额外开销Java中每个对象都可以作为锁对象就只需要把synchronized加到链表
一个hash表,上面的hash桶的个数是非常多~~大部分的操作,都没有锁冲突了,(synchronized,如果不产生锁冲突,就是个偏向锁)
2.像hash表的size,即使你插入的元素是不同的链表上的元素,也会涉及到多线程修改同一个变量
3.针对扩容操作做了特殊优化
如果发现负载因子太大了,就需要扩容扩容是一个比较重量比较低效的操作。
化整为零.ConcurrentHashMap会在扩容的时候,搞两份空间.
一份是之前扩容之前的空间一份是扩容之后的空间接下来每次进行hash表的基本操作,都会把一部分数据从I旧空间搬运到新空间
搬的过程中:1.插入=>插入到新的上面2.删除=>新的日的都要删除3.查找=>新的日的都要查找
java8之前,ConcurrentHashMap基于分段锁的方式实现的,
引l入若干个锁对象,每个锁对象管理若干个hash桶相比于Hashtable是进化,但是不如现在直接锁桶代码写起来更复杂