1.线程和进程的区别
- 进程是资源分配的最小单位,线程是CPU调度的最小单位;
- 线程不能看作独立应用,而进程可以看作独立应用;
- 进程有独立的地址空间,相互不影响,线程只是进程执行的不同路径;
- 线程没有独立的地址空间,多进程的程序比多线程的程序健壮;
- 进程的切换比线程的切换开销大;
2.Java进程和线程的关系
- java对操作系统提供的功能进行封装,包括进程和线程;
- 运行一个程序会产生一个进程,进程至少包含一个线程;
- 每个进程对应一个JVM实例,多个线程共享JVM里面的堆;
- java采用单线程编程模型,程序会自动创建主线程;
- 主线程可以创建子线程,原则上要后于子线程完成执行;
3.Thread.start()和Thread.run()方法的区别
- 调用start()方法会创建一个新的子线程并启动;
- run()方法只是Thread的一个普通方法的调用;
4.Thread和Runnable的区别
- Thread是实现了Runnable接口的类,使得run支持多线程;
- 因类的单一继承原则,推荐多使用Runnable接口;
5.如何给run()方法传参
- 构造函数传参
- 成员变量传参
- 回调函数传参
6.如何实现处理线程的返回值
- 主线程等待法
public class ThreadReturn implements Runnable{
private String runResult;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
runResult="我是子线程方法执行想要返回的结果值";
}
public String getRunResult() {
return runResult;
}
public static void main(String[] args) {
ThreadReturn threadReturn=new ThreadReturn();
Thread thread=new Thread(threadReturn);
thread.start();
//主线程等待子线程执行完
while (threadReturn.getRunResult()==null){
try {
Thread.currentThread().sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println(threadReturn.getRunResult());
}
}
弊端: 无法准确地预估全部子线程执行完毕的时间。主线程sleep时间太久,主线程就需要空等;sleep时间太短,子线程又可能没有全部执行完毕。
- 使用Thread类的join()堵塞当前线程以等待子线程执行完毕
public class ThreadReturn implements Runnable{
private String runResult;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
runResult="我是子线程方法执行想要返回的结果值";
}
public String getRunResult() {
return runResult;
}
public static void main(String[] args) {
ThreadReturn threadReturn=new ThreadReturn();
Thread thread=new Thread(threadReturn);
thread.start();
//主线程等待子线程执行完
// while (threadReturn.getRunResult()==null){
// try {
// Thread.currentThread().sleep(100);
// }catch (Exception e){
// e.printStackTrace();
// }
// }
try {
thread.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(threadReturn.getRunResult());
}
}
- 通过Callable接口实现:通过FutureTask或者线程池获取
//通过FutureTask获取线程返回值
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
String value="test result";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("Task done");
return value;
}
public static void main(String[] args) {
FutureTask<String> futureTask=new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
if(!futureTask.isDone()){
System.out.println("Task has not finished,pleas wait");
}
try {
System.out.println("Task return:"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("All is done");
}
}
//线程池获取返回值
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
String value="test result";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("Task done");
return value;
}
public static void main(String[] args) {
// FutureTask<String> futureTask=new FutureTask<>(new MyCallable());
// new Thread(futureTask).start();
// if(!futureTask.isDone()){
// System.out.println("Task has not finished,pleas wait");
// }
// try {
// System.out.println("Task return:"+futureTask.get());
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
// System.out.println("All is done");
ExecutorService executorService= Executors.newCachedThreadPool();
Future future=executorService.submit(new MyCallable());//往线程池中提交线程
if(!future.isDone()){
System.out.println("Task has not finished,pleas wait");
}
try {
System.out.println("Task return:"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();//关闭线程池
}
System.out.println("All is done");
}
}
6.线程的6种状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - 阻塞(BLOCKED):表示线程阻塞于锁,等待获取排它锁。
- 等待(WAITING):不会分配CPU执行时间,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)来唤醒。
没有设置TIMEOUT参数的Object.wait()方法;
没有设置TIMEOUT参数的Thread.join()方法;
LockSupport.park()方法; - 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
Thread.sleep()方法;
设置了TIMEOUT参数的Object.wait()方法;
设置了TIMEOUT参数的Thread.join()方法;
LockSupport.parkNanos()方法;
LockSupport.parkUntil()方法; - 终止(TERMINATED):表示该线程已经执行完毕。
7.Thread.sleep()和Object.wait()的区别
- Thread.sleep()是Thread类的方法,Object.wait是Object类中定义的方法。
- Thread.sleep()方法可以在任何地方使用,Object.wait()方法只能在synchronized方法或synchronized块中使用。
本质区别: - Thread.sleep()只会让出CPU,不会导致锁行为的改变。
- Object.wait()不仅让出CPU,还会释放已经占用的同步资源锁。
public class WaitSleepDemo {
public static void main(String[] args) {
final Object lock=new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock){
System.out.println("thread A get lock");
try {
Thread.sleep(20);
System.out.println("thread A do wait method");
lock.wait(1000);
System.out.println("thread A is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread b is waiting to get lock");
synchronized (lock){
System.out.println("thread b get lock");
try {
System.out.println("thread b is sleeping 10000ms");
Thread.sleep(10000);
System.out.println("thread b is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
8.Object.notify()和Object.notifyAll()的区别
先谈两个概念:
- 锁池EntryList
假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想调用这个对象的synchronized方法或块,由于B、C线程在进入对象的synchronized方法或块之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正被A所占用,此时B、C线程就会被堵塞,进入一个地方去等待锁的释放,这个地方便是锁池。 - 等待池WaitSet
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
notify:会随机从等待池中选取一个线程进入锁池去竞争获取锁的机会。
notifyAll:会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会。
public class NotificationDemo {
private volatile boolean go=false;
public static void main(String[] args) {
NotificationDemo test=new NotificationDemo();
Runnable waitTask=new Runnable() {
@Override
public void run() {
try {
test.shouldGo();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"finished execution");
}
};
Runnable notifyTask=new Runnable() {
@Override
public void run() {
test.go();
System.out.println(Thread.currentThread().getName()+"finished execution");
}
};
Thread t1=new Thread(waitTask,"WT1");
Thread t2=new Thread(waitTask,"WT2");
Thread t3=new Thread(waitTask,"WT3");
Thread t4=new Thread(notifyTask,"NT1");
//starting all waiting thread
t1.start();
t2.start();
t3.start();
//确保所有等待线程都能成功开始执行
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
t4.start();
}
private synchronized void shouldGo() throws InterruptedException {
while (go!=true){
System.out.println(Thread.currentThread()+"is going to wait on this object");
wait();//释放锁,线程进入等待池,等待唤醒
System.out.println(Thread.currentThread()+"is woken up");
}
go=false;
}
private synchronized void go(){
while (go==false){
System.out.println(Thread.currentThread()+" is going to notify all or one thread waiting on this object");
go=true;
//notify();
notifyAll();
}
}
}
9.Thread.yield()方法
让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。yield()不会导致线程转到等待、阻塞、终止等状态。
10.并发编程的三个概念:原子性,可见性,有序性
- 原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断要么就都不执行; 可以通过synchronized和Lock来实现。由synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
- 可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值; 于可见性,Java提供了volatile关键字来保证可见性。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
- 有序性:即程序执行的顺序按照代码的先后顺序执行; 在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
11.synchronized
线程安全问题的主要诱因
- 存在共享数据(也称临界资源)
- 存在多条线程共同操作这些共享数据
解决问题的根本方法:
同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作。
互斥锁的特性:
- 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性。
- 可见性:必须确保在锁被释放之前,对共享变量所作的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致。
synchronized 锁的不是代码,锁的都是对象。
根据获取的锁的分类:获取对象锁和获取类锁
获取对象锁的两种用法
- 同步代码块(synchronized(this)),synchronized(类实例对象),锁是小括号()中的实例对象。
- 同步非静态方法(synchronized method),锁是当前对象的实例对象。
获取类锁的两种用法
- 同步代码块(synchronized(类.class)),锁是小括号()中的类对象(Class对象)。
- 同步静态方法(synchronized static method),锁是当前对象的类对象(Class对象)。
对象锁和类锁的总结
- 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块;
- 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另外一个访问对象的同步代码块的线程会被堵塞;
- 若锁住的是同一个对象,一个线程在访问对象的同步方法时,另外一个访问对象同步方法的线程会被堵塞;
- 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步方法的线程会被堵塞,反之亦然;
- 同一个 类的不同对象的对象锁互不干扰;
- 类锁由于也是一种特殊的对象锁,因此表现和上述1、2、3、4一致,而由于一个类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的;
- 类锁和对象锁互补干扰;
public class SynchRunnable implements Runnable{
@Override
public void run() {
String threadName=Thread.currentThread().getName();
if(threadName.startsWith("A")){
asyc();
}else if(threadName.startsWith("B")){
synchObjectBlock1();
}else if(threadName.startsWith("C")){
synchObjectBlock2();
}else if(threadName.startsWith("D")){
synchClassBlock1();
}else if(threadName.startsWith("E")){
synchClassBlock2();
}
}
public void asyc(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
public void synchObjectBlock1(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
public synchronized void synchObjectBlock2(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
public void synchClassBlock1(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SynchRunnable.class){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
public static synchronized void synchClassBlock2(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
public static void main(String[] args) {
SynchRunnable synchRunnable=new SynchRunnable();
Thread a1=new Thread(synchRunnable,"A1");
Thread a2=new Thread(synchRunnable,"A2");
Thread b1=new Thread(synchRunnable,"B1");
Thread b2=new Thread(synchRunnable,"B2");
Thread c1=new Thread(synchRunnable,"C1");
Thread c2=new Thread(synchRunnable,"C2");
Thread d1=new Thread(new SynchRunnable(),"D1");
Thread d2=new Thread(new SynchRunnable(),"D2");
Thread e1=new Thread(new SynchRunnable(),"E1");
Thread e2=new Thread(new SynchRunnable(),"E2");
a1.start();
a2.start();
b1.start();
b2.start();
c1.start();
c2.start();
d1.start();
d2.start();
e1.start();
e2.start();
}
}
12.volatile
上面介绍了使用锁的方式可以解决共享变量内存可见性问题,但是使用锁太笨重,因 为它会带来线程上下文的切换开销。 对于解决内存可见性问题, Java 还提供了一种弱形式的同步,也就是使用 volatile关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。 当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。 volatile 的内存语义和 synchronized 有相似之处,具体来说就是,当线程写入了volatile 变量值时就等价于线程退出 synchronized 同步块(把写入工作内存的变量值同步到主内存),读取 volatile 变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。
//线程不安全
public class ThreadNotSafeinteger{
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
// synchronized 关键宇进行同步的方式
public class ThreadSafeinteger {
private int value;
public synchronized int getValue() {
return value;
}
public synchronized void setValue(int value) {
this.value = value;
}
}
//使用 volatile 进行同步
public class ThreadNotSafeinteger2{
private volatile int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
在这里使用 synchronized 和使用 volatile 是等价的,都解决了共享变量 value 的内存可见性问题,但是前者是独占锁,同时只能有一个线程调用 get()方法,其他调用线程会被阻塞,同时会存在线程上下文切换和线程重新调度的开销,这也是使用锁方式不好的地方。 而后者是非阻塞算法,不会造成线程上下文切换的开销。
但并非在所有情况下使用它们都是等价的,volatile 虽然提供了可见性保证,但并不保证操作的原子性。
那么一般在什么时候才使用 volatile 关键字呢?
- ·写入变量值不依赖、变量的当前值时。 因为如果依赖当前值,将是获取一计算一写入 三步操作,这三步操作不是原子性的,而 volatile不保证原子性。
- 读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为 volatile 的。
13.CAS
CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
- 循环时间长开销很大;
- 只能保证一个共享变量的原子操作;
- ABA问题;
14.锁的概述
1、悲观锁与乐观锁
悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。 悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。 如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。 如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。
@Transactional(propagation = Propagation.REQUIRED)
public int updateEntry(long id) {
//(1)使用悲观锁获取指定记录
EntryObject entry= query (” select * from tablel where id = #{id} for update” , id) ;
//(2)修改记录内容, 根据计算修改entry记录的属性
String name= generatorName(entry) ;
entry.setName(name);
//(3)update操作
int count = update (” update tablel set name=#{name},age=#{age} where id =#{id}” , entry) ;
return count ;
}
当多个线程同时调用 updateEntry 方法,并且传递的是同一个 id 时, 只有一个线程执行代码(1)会成功,其他线程则会被阻塞,这是因为在同一时间只有一个线程可以获取对应记录的锁,在获取锁的线程释放锁前 (updateEntry执行完毕,提交事务前),其他线程必须等待,也就是在同一时间只有一个线程可以对该记录进行修改。
乐观锁是一种不使用数据库锁和不堵塞线程并发的方案,相对悲观锁来说的,它认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而是在进行数据提交更新时,才会正式对数据冲突与否进行检测。具体来说,根据update返回的行数让用户决定如何去做。 将上面的例子改为使用乐观锁的代码如下。乐观锁是多线程CAS(compare and Swap)的一种实现,开发者一般引入版本号(version)来避免出现CAS的ABA问题。
@Transactional(propagation = Propagation.REQUIRED)
public int updateEntry(long id) {
//(1)使用悲观锁获取指定记录
EntryObject entry= query (” select * from tablel where id = #{id} for update” , id) ;
//(2)修改记录内容,version字段不能被修改
String name= generatorName(entry) ;
String version=entry.getVersion();
entry.setName(name);
//(3)update操作
int count = update (” update tablel set name=#{name},age=#{age} ,version=version+1 where id =#{id} and version=#{version}” , entry) ;
//(4)count结果为1表示该线程修改成功,为0表示修改失败,说明数据在该线程修改过程中有被其他线程修改
return count ;
}
为避免乐观锁导致修改失败的的问题,而引入重入机制,也就是一旦修改失败,就重新做一次。
使用时间戳限制重入的乐观锁
@Transactional(propagation = Propagation.REQUIRED)
public int updateEntry(long id) {
long start=System.currentTimeMillis();
int count=0;
//循环直到成功
while(true){
long end=System.currentTimeMillis();
//如果循环时间大于 100 ms 返回终止循环
if(end-start>100){
return false;
}
//(1)使用悲观锁获取指定记录
EntryObject entry= query (” select * from tablel where id = #{id} for update” , id) ;
//(2)修改记录内容,version字段不能被修改
String name= generatorName(entry) ;
String version=entry.getVersion();
entry.setName(name);
//(3)update操作
count = update (” update tablel set name=#{name},age=#{age} ,version=version+1 where id =#{id} and version=#{version}” , entry) ;
if(count==0){
continue;
}
return true;
}
return count ;
}
使用限定次数重入的乐观锁
@Transactional(propagation = Propagation.REQUIRED)
public int updateEntry(long id) {
//循环次数
int retryNum=5;
int count=0;
//循环直到成功
while(retryNum>0){
//(1)使用悲观锁获取指定记录
EntryObject entry= query (” select * from tablel where id = #{id} for update” , id) ;
//(2)修改记录内容,version字段不能被修改
String name= generatorName(entry) ;
String version=entry.getVersion();
entry.setName(name);
//(3)update操作
count = update (” update tablel set name=#{name},age=#{age} ,version=version+1 where id =#{id} and version=#{version}” , entry) ;
if(count==1){
break;
}
retryNum--;
}
return count ;
2、公平锁与非公平锁
根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程请求锁的时间早晚来决定的,也就是最早请求锁的线程将最早获取到锁。 而非公平锁则在运行时闯入,也就是先来不一定先得。ReentrantLock 提供了公平和非公平锁的实现。如果构造函数不传递参数,则默认是非公平锁。
- 公平锁: ReentrantLock pairLock =new ReentrantLock(true)。
- 非公平锁: ReentrantLock pairLock =new ReentrantLock(false)。
例如,假设线程 A 已经持有了锁,这时候线程 B 请求该锁其将会被挂起。 当线程 A 释放锁后,假如当前有线程 C 也需要获取该锁,如果采用非公平锁方式,则根据线程调度策略, 线程 B 和线程 C 两者之一可能获取锁,这时候不需要任何其他干涉,而如果使用公平锁则需要把 C 挂起,让 B 获取当前锁。
在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来性能开销。
3、独占锁与共享锁
根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。
独占锁保证任何时候都只有一个线程能得到锁ReentrantLock就是以独占方式实现的。共享锁则可以同时由多个线程持有,例如 ReadWriteLock 读写锁,它允许一个资源可以被多线程同时进行读操作。
独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这限制了并发性,因为读操作并不会影响数据的一致性,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。
共享锁则是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作。
4、其他锁(重入锁,自旋锁等)
线程池
spring事务隔离级别
事务的隔离性级别越高,并发性能越差。
mysql支持以4种事务隔离级别,oralce只有读写提交、串行化两种事务隔离级别,现实中一般而言会选择读写提交,它能够防止脏读。
spring事务传播行为
spring一共有7种事务传播行为,主要用到一下三种:
- Propagation=Propagation.REQUIRED
spring事务默认的事务传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法。 - Propagation=Propagation.REQUIRED_NEW
无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务独立。 - Propagation=Propagation.NESTED
在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的sql,而不回滚当前方法的事务。
spring事务失效问题
有于spring事务的实现原理是AOP,而AOP的原理是动态代理,在自调用过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,这样spring也不会将事务织入到代码中,导致事务失效。