void 取款(){int oldBalance =balance
if(!CAS(balance,oldBalance,oldBalance-500)){}}
在这个线程中如果变成了这样
void 取款(){int oldBalance =balancevoid 取款(){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() {@Overridepublic 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>() {@Overridepublic 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() {@Overridepublic 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是进化,但是不如现在直接锁桶代码写起来更复杂