JUC常用和jmm

注意!!!!

注解实现看这个文章:https://mp.weixin.qq.com/s/btD2NkmsWzs1cWHuxRrLyw

多线程去进行批量操作的时候,每个线程都是一个不同的sqlsession会话(一个会话中包含多个事务),mybatis的底层是用ThreadLocal去解决相互隔离,所以主线程和子线程;子线程和子线程都是互不干扰的,所以用事务个人觉得要小心操作去验证;比如将事务添加在安全集合中,在安全集合中决定是否提交;

手动式提交


   @Resource
    private TransactionTemplate transactionTemplate;

   @SneakyThrows
    @RequestMapping("/p")
    public String test1() {
        final CountDownLatch al = new CountDownLatch(2);
        final AtomicBoolean ab = new AtomicBoolean(false);


        new Thread(()->{
            transactionTemplate.execute((e) -> {
                try {
                    demoService.insert(1);
                    al.countDown();
                    al.await();
                    if (ab.get()) throw new RuntimeException("手动抛错");
                } catch (Exception exception) {
                    //事务回滚;
                    e.setRollbackOnly();
                    exception.printStackTrace();
                    ab.set(true);
                    al.countDown();
                    return false;
                }
                return true;
            });
        }).start();


        new Thread(()->{
            transactionTemplate.execute((e) -> {
                try {
                    demoService.insert(1);
                    al.countDown();
                    al.await();
                    if (ab.get()) throw new RuntimeException("手动抛错");
                } catch (Exception exception) {
                    //事务回滚;
                    e.setRollbackOnly();
                    exception.printStackTrace();
                    ab.set(true);
                    al.countDown();
                    return false;
                }
                return true;
            });
        }).start();
        al.await();
        return "success";
    }

JUC常用辅助类

1、CountDownLatch(减法)

允许一个或者多个线程去等待其他线程完成操作。CountDownLatch接收一个int型参数,表示要等待的工作线程的个数。

等于0主线程才往下执行

CountDownLatch countDownLatch = new CountDownLatch(6);
//-1
countDownLatch.countDown();
//等待计数器归零才会向下执行
 countDownLatch.await();
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
            try{
            	System.out.println(Thread.currentThread().getName()+"go out");
			} finally {
			 //用的时候需要谨慎在finally{countDownLatch.countDown()}  当心出现死锁
				countDownLatch.countDown();
			}                
            },String.valueOf(i)).start();
        }
        //等待计数器归零才会向下执行
        countDownLatch.await();
        countDownLatch.countDown();//此时结果集为:-1
    }
}


2、CyclicBarrier(加法)

有若干个线程,比如说有五个线程,需要它们都到达了某一个点之后才能开始一起执行,也就是说假如其中只有四个线程到达了这个点,还差一个线程没到达,此时这四个线程都会进入等待状态,直到第五个线程也到达了这个点之后,这五个线程才开始一起进行执行状态

等待的线程达到7个,才一起被唤醒最后一个执行的线程执行召唤神龙

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功");
        });
		
		cyclicBarrier.await();
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //集齐七颗龙珠,召唤神龙
        //召唤龙珠
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功");
        });
        for (int i = 0; i < 7; i++) {
            final int temp = i;
            //lambda不能直接拿到for循环中的i
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3、Semaphore(抢占)

Semaphore内部并没有真正保存P,而是只保存了P的个数。其次,Semaphore直接复用了AbstractQueueSyncrhonizer框架的共享模式锁,其acquire和release操作直接调用共享模式的AQS加锁和AQS解锁,没有增加其他逻辑,只不过在加锁和解锁的过程中,是把P的个数存入AQS原子整数。

线程中限制次数等待抢占,满了就等待其他线程执行完毕

Semaphore semaphore = new Semaphore(3);
semaphore.acquire();//获得,如果满了,会等待被释放为止
semaphore.release();//释放
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                //acquire()
                try {
                    semaphore.acquire();//获得,如果满了,会等待被释放为止
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

lock

1、ReentrantLock

再重写的run中进行锁操作

            ReentrantLock reentrantLock = new ReentrantLock();
            Condition condition = reentrantLock.newCondition();
//            睡眠  不建议使用 Thread.sleep(10);
            TimeUnit.SECONDS.sleep(10);
//            等待
            condition.await();
//            唤醒
            condition.signal();
//            唤醒全部
            condition.signalAll();
//            锁
            reentrantLock.lock();
//            释放
            reentrantLock.unlock();            ReentrantLock reentrantLock = new ReentrantLock();
            Condition condition = reentrantLock.newCondition();
//            等待
            condition.await();
//            唤醒
            condition.signal();
//            唤醒全部
            condition.signalAll();
//            锁
            reentrantLock.lock();
//            释放
            reentrantLock.unlock();

2、ReentrantReadWriteLock读写锁

区别查看 读写锁使用区别

            ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//            读锁
            ReentrantReadWriteLock.ReadLock readLock1 = readWriteLock.readLock();
//            写锁
            ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

3、Atomic 原子类

解决并发的线程安全问题有两种方式:
1、等待唤醒机制
如果抢不到锁,就将线程挂起,当锁释放的时候,然后将其唤醒重新抢锁。
2、自旋CAS
自旋就是设置循环CAS抢锁的意思,当CAS成功的时候才会退出循环
在这里插入图片描述
Atomic原子类就是利用自旋CAS来保证线程安全的。

1、基本常用方法

getAndIncrement() // 原子化 i++
getAndDecrement() // 原子化的 i--
incrementAndGet() // 原子化的 ++i
decrementAndGet() // 原子化的 --i
// 当前值 +=delta,返回 += 前的值
getAndAdd(delta) 
// 当前值 +=delta,返回 += 后的值
addAndGet(delta)
//CAS 操作,返回是否成功
compareAndSet(expect, update)
// 以下四个方法
// 新值可以通过传入 func 函数来计算
getAndUpdate(func)
updateAndGet(func)
getAndAccumulate(x,func)
accumulateAndGet(x,func)

2、对象引用类型

User user = new User("jaychou",24);
AtomicReference<User> userAtomicReference = new AtomicReference<>(user);

while (true){
    User updateUser = new User("jay",22);
    boolean flag = userAtomicReference.compareAndSet(userAtomicReference.get(),updateUser);

    if (flag){
        System.out.println(userAtomicReference.get());
        break;
    }
}

AtomicStampedReference

AtomicReference有一个缺点,不能解决ABA问题。
ABA问题就是多个线程前后修改值,导致线程CAS前后值没有变化,但是中间却发生了修改。

AtomicStampedReference通过引入时间戳来解决了ABA问题。每次要更新值的时候,需要额外传入oldStamp和newStamp。将对象和stamp包装成了一个Pair对象。

User user = new User("jaychou",24);
AtomicStampedReference<User> userAtomicStampedReference = new AtomicStampedReference<>(user,1);

while (true){
    User user1 = new User("jay",222);
    int oldStamp1 = userAtomicStampedReference.getStamp();
    int[] stamp = new int[1];
    User oldUser = userAtomicStampedReference.get(stamp);
    boolean flag = userAtomicStampedReference.compareAndSet(oldUser,user1,stamp[0],stamp[0]+1);
    if (flag){
        break;
    }
}

int[] s = new int[1];
System.out.println(userAtomicStampedReference.get(s));
System.out.println(s[0]);

AtomicMarkableReference是无法解决ABA问题的,因为boolean变量的mark是有很大可能重合的,还是会导致更新成功。

3、Atomic数组 Atomic数组主要有AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray.

AtomicIntegerArray integerArray = new AtomicIntegerArray(10);//10为数组长度
while (true){
    boolean flag = integerArray.compareAndSet(0,integerArray.get(0),2);
    if (flag){
        System.out.println(integerArray.get(0)+"---"+flag);
        break;
    }
}

AtomicReferenceArray<User> referenceArray = new AtomicReferenceArray<>(10);
while (true){
    boolean flag2 = referenceArray.compareAndSet(0,referenceArray.get(0),new User("jaychou",22));
    if (flag2){
        System.out.println(referenceArray.get(0)+"---"+flag2);
        break;
    }
}

4、对象属性原子更新器 有三类:AtomicIntegerFieldUpdater(修改对象中的)、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater


User user = new User("jaychou",22);

//初始化参数分别为:对应对应的类,属性对应的类,属性的名字
AtomicReferenceFieldUpdater fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class,String.class,"name");
//初始化参数分别为:对应对应的类,属性的名字。属性对应的类可以忽略,因为类名中已经记录了
AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

fieldUpdater.compareAndSet(user,user.name,"666");
integerFieldUpdater.compareAndSet(user,user.age,1000);

System.out.println(user);



class User{
    int age;
    volatile String name;

    public User(String name,int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

Java中提供了几类原子操作类,通过自旋+CAS来解决线程不安全问题。
1、AtomicLong、AtomicInteger、AtomicBoolean
这些类实现了++,–,+delta的原子操作。
2、AtomicReference
AtomicReference的作用是引用类型的原子修改,保证引用的修改不会出现线程问题。
并且通过AtomicStampedReference解决了ABA问题。
3、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
这个就是数组类型,和单独的对象操作基本一致,只不过在设置的时候需要填入下标罢了。
4、AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater、AtomicLongFieldUpdater
AtomicReference是修改引用,而AtomicReferenceFieldUpdater这三个类是用来修改实例对象中的属性的值的。
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater是AtomicReferenceFieldUpdater的一个特殊例子,是用来专门分别修改int和long属性的变量的。

出以上被修改变量必须用volatile修饰

问题

1、Sychronized和lock的区别

1.Sychronized 内置的java关键字,Lock锁是一个java类
2.Sychronized 无法判断获取锁的状态,Lock锁可以判断是否获取到了锁.
3.Sychronized 会自动释放锁lock必须手动释放锁,如果不释放锁,死锁
4.Sychronized 线程一(获得锁,阻塞),线程二(等待,傻傻的等),Lock锁就不一定会等待下去.
5.Sychronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以中断锁,非公平(可以自己设置)
6.Sychronized 适合锁少量的代码的同步问题,Lock适合锁大量的代码同步问题.

2、java真的可以开启线程吗?不可以

private native void start0();
//本地方法,调用底层c++,java运行在虚拟机之上,无法直接操作硬件,由c++开启多线程

3、线程的状态:6个

public enum State {
//就绪
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}

4、wait与sleep的区别

来自不同的类wait=》Object sleep=》Thread
wait释放锁,sleep抱着锁睡觉
wait必须在同步代码快中,sleep可以在任何地方睡觉
wait不需要捕获异常,sleep需要捕获异常(可能发生超时等待)

5、JMM

volatile是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.由于内存屏障,禁止指令重排
什么是JMM
JMM:java的内存模型,不存在的东西,概念,约定
关于JMM的一些同步的约定:
1.线程解锁前,必须把共享变量立刻刷回主存
2.线程枷锁前,必须读取主存中的最新值到工作的内存中
3.加锁和解锁是同一把锁
线程:工作内存 ,主内存
在这里插入图片描述

volatile关键字

先看下面在程序一直执行计数器一直循环这个地方,那么jit会优化代码
在这里插入图片描述

也就是取使用这个变量的时候,如果中途修改了这个值,线程就会去主内存下刷一下值 要结合锁去使用
不是线程安全,一般用于原子性操作 工作流程JMM 取值到线程自己的工作空间 原子性操作被volatile修饰加锁时候是安全的

原子性操作下加锁安全 列入单例下不加关键字的话就会出现原本有的话 但是工作空间没有 就会新创建对象赋值 就不能达到单例的效果

class Singleton {
 // 不是一个原子性操作
 //private static Singleton instance;
 //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
 private static volatile Singleton instance;

 // 构造器私有化
 private Singleton() {
 }

 // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用
 //如果不加关键字  那么  线程a中的和线程b中通过jmm刷内存   a赋值操作了   但是b没有去刷新内存  所以b是空的   又创建一次对象赋值操作
 public static Singleton getInstance() {
  if (instance == null) {
   synchronized (Singleton.class) {
    if (instance == null) {
     instance = new Singleton();
    }
   }
  }
  return instance;
 }
}

去除volatile 和不去除

package com.zhk.activiti;

public class VolatileExample {
    private volatile static boolean initFlag = false;//共享变量
    public static void main(String[] args) throws InterruptedException {
        //做业务的线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("等待数据。。。。");
                while (!initFlag){
                }
                System.out.println("工作完成了");
            }
        }).start();
        //保证第一个线程先执行
        Thread.sleep(2000);
        //准备数据的线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始准备数据。。。");
                initFlag = true;
                System.out.println("数据准备完毕。。。");
            }
        }).start();
    }

}

juc是

包名首字母 java.util.concurrent

ThreadLocal

当前前程只能获取当前线程的值,内部有个ThreadLocalMap
应用场景:
1、每个线程需要有自己单独的实例
2、实例需要在多个方法中共享,但不希望被多线程共享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张航柯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值