文章目录
注意!!!!
注解实现看这个文章: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、实例需要在多个方法中共享,但不希望被多线程共享