一、集合安全问题
1.1 ArrayList
- 空的集合初始值为10
- object类型的数组
- 扩容Arrays.copyOf 原始大小的一倍
- 线程不安全
1.1.1 不安全
java.util.concurrentModificationException
- Vector加了锁保证了数据一致性,但是并发性急剧下降,所以很少用!
- ArrayList牺牲了线程安全从而保证并发性
1.1.2 如何解决ArrayList线程不安全问题
1.new Vector<>() 2.Collection与Collections
- Collection为集合类的父接口
- Collections为辅助类来解决ArrayList线程不安全问题
List<String> list = Collections.synchronizedList(new ArrayList<>());
3.CopyOnWriteArrayList<>()类 写时复制 读写分离的思想
List<String> list = new CopyOnWriteArrayList<>();
private tranisent volatile [].........
1.2 HashSet
底层:HashMap 初始值16 负载因子0.75
线程不安全解决的问题与上面雷同
解决办法一Collections.synchronizedSet():
解决办法二CopyOnWriteArraySet<>():
1.3 HashMap
演示错误 java.util.concurrentModificationException
解决办法一:
Map<String,String> map = new ConcurrentHashMap<>();
解决办法二:
Collections.synchronizedMap();
二、JAVA锁机制
公平锁:多个新线程按照申请顺序来获取锁,先到先得 非 非公平锁:多个线程并不是按照申请的顺序,有可能造成优先级反转或者饥饿现象。
2.1 可重入锁【递归锁】
ReentrantLock 线程可以进入任何一个它已经拥有的锁同步着的代码块 通过构造函数制定该锁是否为公平锁,默认是非公平锁。 非公平锁的优势在于吞吐量比较大 对于Synchronized而言,也是一种非公平锁 作用:避免死锁
2.2 自旋锁
是指尝试获取锁的线程不会阻塞,而是采用循环的方式来尝试乎获取锁,这样的好处就是减少线程上下文的切换消耗,缺点是循环会消耗CPU.
do while()
CAS
期望值与工作区间的值比较
2.2.1 自旋锁代码
public class SpinLock{
//原子引用线程
AtomicReference<Thread> ar = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"come in");
while(!ar.compareAndSet(null,thread)){
}
}
public void myUnLock(){
Thread thread = Thread.currentThread();
ar.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"unlock");
}
public static void main(String[] args){
SpinLock sl = new SpinLock();
new Thread(()-> {
// 加锁
sl.myLock();
// 暂停一会
try{TimeUnit.SECONDS.sleep(5);}catch(...){}
// 解锁
sl.myUnLock();
},"AA").start();
try{TimeUnit.SECONDS.sleep(1);}catch(...){}
new Thread(()-> {
// 加锁
sl.myLock();
try{TimeUnit.SECONDS.sleep(1);}catch(...){}
// 解锁
sl.myUnLock();
},"BB").start();
}
}
2.3 独占锁(写锁)/共享锁(读锁)
独占锁:指该锁一次只能被一个线程所持有的。 ReentrantLock Synchronized 都是独占锁 共享锁:该锁可以被多个线程所持有 ReentrantReadWriteLock为共享锁,写锁为独占锁 读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程都是互斥的。
一个线程去写【原子+独占】绝对不可以被阻断,多个线程可以读 【问题描述如下:】
class MyCache{//缓存资源类
//volatile 可见性 不保证原子性 禁止指令重排
private volatile Map<String,Object> map = new HashMap<>();
//解决问题 原子性
//private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock ();
public void put(String key,Object value){
//加写锁
rwLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"正在写入"+key);
try{Time.MILLSECONDS.sleep(300);}catch(){};
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}catch(Exception e){
}finally{
rwLock.writeLock().unlock();
}
}
public void get(String key,Object value){
//加读锁
rwLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"正在读取"+key);
try{Time.MILLSECONDS.sleep(300);}catch(){};
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成"+result);
}catch(Exception e){
}finally{
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo{
public static void main(String[] args){
MyCache myCache = new MyCache();
//写
for(int i = 1;i <= 5;i++){
new Thread(() -> {
final int tempInt = i;
myCache.put(tempInt+"",tempInt+"");
},String.valueOf(i)).start();
}
//读
for(int i = 1;i <= 5;i++){
new Thread(() -> {
final int tempInt = i;
myCache.get(tempInt+"");
},String.valueOf(i)).start();
}
}
}
这样既保证了数据一致性,有保证了并发性,读写分离。 Synchronized太重量。
三、CountDownLatch【线程做减法倒计时】
3.1 离开教室锁门问题产生!
public class CountDownLatchDemo{
public static void main(String[] args){
for(int i = 1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
},String.valueOf(i)).start();
}
System.out,println(Thread.currentThread().getName()+"班长最后关门走人");
}
}
3.2 解决问题:CountDownLatch
public class CountDownLatchDemo{
public static void main(String[] args){
// 计数
CountDownLatch countDownLatch = new CountDownLatch(6);
for(int i = 1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
countDownLatch.countDown();//减1操作
},String.valueOf(i)).start();
}
// 主线程等待
countDownLatch.await();
System.out,println(Thread.currentThread().getName()+"班长锁门,最后关门走人");
}
}
四、CyclicBarrier【加法】
加法 与CountDownLatch【减法】相反 加到一定的数值然后做事
最后一个线程到达屏障时候才会进行
public class CountDownLatchDemo{
public static void main(String[] args){
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("***召唤神龙***");});
for(int i = 1;i<=7;i++){
final int tempInt = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集到第"+tempInt +"颗龙珠");
cyclicBarrier.await();
},String.valueOf(i)).start();
}
}
}
五、Semaphore【信号量】
多个共享资源的互斥使用 并发线程数量的控制
public class CountDownLatchDemo{
public static void main(String[] args){
// 模拟3个停车位
Semaphore semaphore = new Semaphore(3);
for(int i = 1; i <= 6;i++){
new Thread(()->{
try{
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"停车3S后,离开车位");
}catch(...){
}finally{
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
六、阻塞队列【MQ核心】
6.1 阻塞队列ArrayBlockingQueue<>()
报异常
没有异常,直接返回布尔类型false
一直阻塞,取出用take方法
过时不候
6.2 阻塞队列 SynchronousQueue<>()
不消费,不会继续插下一个,会卡在 put(1)
6.3 生产者-消费者案例【新方式】
案例:一个初始值为0的变量,两个线程交替操作,一个加一,一个减一,来5轮
class SahreData{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 加法
public void increament(){
lock.lock{
try{
//1.判断
while(number != 0){
condition.await();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName()+ number);
//3.通知唤醒
condition.signalAll();
}catch(...){
}finally{
lock.unlock();
}
}
}
// 减法
public void decreament(){
lock.lock{
try{
//1.判断
while(number == 0){
condition.await();
}
//2.干活
number--;
System.out.println(Thread.currentThread().getName()+ number);
//3.通知唤醒
condition.signalAll();
}catch(...){
}finally{
lock.unlock();
}
}
}
}
public class ProdConsumer{
public static void main(String[] args){
ShareData shareData = new ShareData();
new Thread(()->{
for(int i = 1;i<=5;i++){
shareData.increament();
}
},"A").start();
new Thread(()->{
for(int i = 1;i<=5;i++){
shareData.decreament();
}
},"B").start();
}
}
6.4 虚假唤醒
防止虚假唤醒 一定要用while 不要用if 6.3 中的代码换成If,多添加几个线程就会出现问题! 会出现结果 1 2 -1 0等等,并没有控制住结果。
七、Synchrinized与Lock的区别
1.前者JVM层面,是Java的关键字,后者是API层面,java5以后的出现的。 2. synchronized不可以中断 3. Reentranrlock可以中断,设置超时,或者中断方法 4.synchronized默认非公平锁 5.Reentranrlock可以分组唤醒,精确唤醒 6.synchronized要么随即唤醒一个,要么唤醒全部notify() notifyAll() 实现案例:
多线程之间要按照顺序调用,实现A-B-C三个线程启动: AA打印5次,BB打印10次,CC打印15次 然后 AA打印5次,BB打印10次,CC打印15次 ... 循环10次
7.1 打印案例【新的Lock版本】
class ShareResource{
private int number = 1;//A1 B2 C3
private Lock lock = new ReentrantLock();
private Condition c1 = new lock.newCondition();
private Condition c2 = new lock.newCondition();
private Condition c3 = new lock.newCondition();
public void prints5(){
lock.lock();
try{
//1.判断
while(number != 1){
c1.await();
}
//2.干活
for(int i = 1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//3.通知2
number = 2;
c2.signal();
}catch(){}finally{
lock.unlock();
}
}
public void prints10(){
lock.lock();
try{
//1.判断
while(number != 2){
c2.await();
}
//2.干活
for(int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//3.通知2
number = 3;
c3.signal();
}catch(){}finally{
lock.unlock();
}
}
public void prints15(){
lock.lock();
try{
//1.判断
while(number != 3){
c3.await();
}
//2.干活
for(int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//3.通知2
number = 1;
c1.signal();
}catch(){}finally{
lock.unlock();
}
}
}
public class SyncAndReentrantLockDemo{
ShareSource shareSource = new ShareSource();
new Thread(()->{
for(int i=0;i<=10;i++){
shareSource.prints5();
}
},"A").start();
new Thread(()->{
for(int i=0;i<=10;i++){
shareSource.prints10();
}
},"B").start();
new Thread(()->{
for(int i=0;i<=10;i++){
shareSource.prints15();
}
},"C").start();
}
7.2 生产消费案例【阻塞队列版本】高并发
class MyResource{
private volatile boolean FLAG = true;//默认开启,生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue){
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd(){
String data = null;
boolean retValue;
while(FLAG){
data = atomInteger.incrementAndGet()+"";
retValue = blockingQueue.offer(data,2L,TimeUnit.SECONDS);
if(retValue){
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"成功");
}else{
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"失败");
}
TimeUint.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName()+"\t 生产叫停,false 生产结束");
}
public void MyConsumer(){
String result = null;
while(FLAG){
result = blockingQueue.poll(2L,TimeUnit.SECONDS);
if(null == result || result.equalsIngoreCase("")){
FLAG = false;
System.out.println(Thread.currentThread().getName()+"\t 超过2S没有消费,消费退出");
return;
}
System.out.println(Thread.currentThread().getName()+"\t 消费队列"+result+"成功");
}
}
public void stop(){
this.FLAG = fasle;
}
}
public class ProdConsumer_BlockQueueDemo{
public static void main(String[] args){
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+ "\t 生产线成启动");
myResource.myProd();
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+ "\t 消费线成启动");
myResource.myConsumer();
},"Consumer").start();
//暂停一会
try{TimeUnit.SECONDS.sleep(5);catchh(...){}}
System.out.println("5S结束,大老板叫停,活动结束");
myResource.stop();
}
}
八、线程池
8.1 Runnable与 Callable
class MyThread implements Runnable{
@Override
public void run(){
}
}
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception{
System.out.println("进来了");
return 1024;
}
}
public class CallableDemo{
public static void main(String[] args){
FuterTask<Integer> futerTask = new FuterTask<>(new MyThread);
Thread t1 = new Thread(futerTask,"AAA");
t1.start();
int result01 = 100;
int result02 = futerTask.get();
System.out.println(result01+result02);
}
}
结果:1124
8.2 线程池的优势
Executor顶级接口和工具包Executors
底层:阻塞队列
1.降低资源消耗 2.提高响应速度 3.提高线程的可管理性 // 拓展工具类 // Array Arrays // Collection Collections // Executor Executors
8.3 线程池实现的方式【3种核心】
工作中你用那个??? 哪个都不用的
阿里巴巴开发手册:不允许使用Executors区创建,而是使用ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,避免资源耗尽。
但是也要学习!!!!!!如下:
第一种【重要】:
public class MyThreadPoolDemo{
public static void main(String[] args){
// 1个线程池 5个线程
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try{
for(int i = 1;i<=10;i++){//10个用户
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
thread.shutdown();
}
}
}
第二种:
public class MyThreadPoolDemo{
public static void main(String[] args){
// 1个线程池 1个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try{
for(int i = 1;i<=10;i++){//10个用户
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
thread.shutdown();
}
}
}
第三种:
public class MyThreadPoolDemo{
public static void main(String[] args){
// 1个线程池 不定线程
ExecutorService threadPool = Executors.newCacgedThreadPool();
try{
for(int i = 1;i<=10;i++){//10个用户
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
thread.shutdown();
}
}
}
8.4 线程池7大参数
8.5 线程池拒绝策略
等待队列已经满了,再也塞不下新任务了
线程池中的max线程也达到了最大,无法继续为新任务服务
8.6 手写线程池【大厂工作核心--7大参数】
public class MyThreadPoolDemo{
public static void main(String[] args){
ExecutorService threadPool = new ThreadPoolExecutor(
2,//核心数
5,//最大线程数
1L,//活跃时间
TimeUint.SECONDS,//单位
new LinkedBlockingQueue<Runnable>(3),//阻塞队列大小个数
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.AbortPolicy());//拒绝策略,会抛异常
try{
for(int i = 1;i<=5;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
threadPool.shutdown();
}
}
}
8.7 如何合理配置线程的数量呢?
回答:
1.CPU密集型? 2.IO密集型?
1.获取CPU密集型 Runtime.getRuntime().getProcessors() 一般为:CPU核数+1个线程
2.IO密集型 io密集型的任务并不是一直在执行任务,应该配置尽可能多的线程 一般为:CPU核数*2
评论