- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
乐观锁 : 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改数据,但是在对数据提交更新的时候,再去判断这个数据在这个期间是否有别人对这个数据进行了修改.
悲观锁 : 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改数据,每次在拿数据的时候都会上锁,当别人想去拿这个数据的时候就会阻塞直到它拿到这个锁…
乐观锁的一个使用方法—版本号机制
引入一个版本号 version
,初值为1, 并且规定只有提交的版本大于记录当前版本才能执行
图解版本号机制:
线程对于数据的访问,主要存在两种操作: 读操作 和 写操作
但是在第三种情况的时候,两个线程用同一把锁就会产生性能损耗.
读写锁就能解决这个性能消耗问题.也就是把读操作和写操作区别对待.
Java标准库中提供了ReentrantReadWriteLock
类,实现了读写锁.
ReentrantReadWriteLock.ReadLock
类表示一个读锁. 提供了一个lock/unlock方法进行加锁解锁.ReetrantReadWriteLock.WriteLock
类表示一个写锁.提供了一个lock/unlock方法进行加锁解锁.读写锁适合"读多写少"的场景
CPU提供了原子操作指令,操作系统对这些指令封装了一层,提供了一个mutex互斥锁,JVM相对于操作系统提供的mutex互斥锁,又封装了一层,实现了synchronized这样的锁
重量级锁: 加锁机制重度依赖了OS提供的mutex,加锁的开销很大.通常是使用内核来完成的.
轻量级锁 加锁机制尽可能不使用mutex,加锁的开销更小.通常是在用户态来完成的.
挂起等待锁: 线程在枪锁失败了之后会进入阻塞等待的状态,结束阻塞需要看操作系统具体的调度时间.当线程挂起来的时候,不占用CPU;
自旋锁: 在线程枪锁失败后,不是阻塞等待,而是快速的再循环一次,一旦锁被其他线程释放,就能第一时间获取到锁.
自旋锁的优点 : 没有放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,能第一时间获取到锁.
自旋锁的缺点 : 如果其他的线程一直使用这个锁,长时间没有释放,那么CPU的消耗就很大.
图解自旋锁 与 挂起等待锁
公平锁: 遵循 “先来后到” 原则,如果线程2比线程3先来,当线程1释放锁之后,线程2就能在线程3之前先获取到锁
非公平锁: 不遵循 “先来后到” 原则,如果线程1释放锁之后,线程2和线程3都可能获取到锁.
公平锁和非公平锁 图解:
可重入锁: 允许同一个线程多次获取同一把锁.
不可重入锁 不允许同一个线程多次获取同一把锁.
例如:一个递归函数里面有加锁操作,如果在递归的过程中,多次获取了同一把锁,如果这个锁不会阻塞自己,就是可重入锁(也叫递归锁)
Java中 只要以Reettrant
开头的锁都是可重入锁.JDK提供的所有现成的Lock实现类,包括synchronized
关键字锁都是可重入锁.
CAS 英文是 compare and swap 顾名思义就是.“比较并交换”.CAS可以视为一种乐观锁
一个CAS涉及到以下操作:
假设内存中的数据为A,旧的预期数据为B,需要修改的值为C.
这个操作是一个原子性的.CPU提供了一组CAS相关的指令
一个CAS的伪代码,辅助理解CAS的工作流程
boolean CAS(value,oldvalue,expectvalue){
if(value == oldvalue){
value = expectvalue;
return true;
}
return false;
}
当多个线程同时对某一个资源进行CAS操作,只能有一个线程操作成功,其他的线程并不会阻塞,而是收到操作失败的信号.
标准库中提供了 java.util.concurrent.atomic
包,这里面的类都是基于这种方式来实现的.
典型的 AtomicInteger
类,其中getAndIncrement
相当于 i++
操作
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();//相当于 i++
一个AtomicInteger的伪代码
class AtomicInteger{
private int value;
public int getAndIncrement() {
int oldValue = value;
while ( CAS(value,oldValue,oldValue+1) != true){
oldValue = value;
}
return oldValue;
}
}
图解:
两个线程调用这个getAndIncrement
方法
此时不需要使用重量级锁,就能高效的完成多线程的自增操作
一个自旋锁的伪代码
public class SpinLock {
private Thread owner = null;
public void lock() {
while(!CAS(this.owner, null , Thread.currentThread())){
}
}
public void unlock(){
this.owner = null;
}
}
引入版本号,在 CAS 比较数据当前值 和 旧值的同时,也要比较版本号是否符合规则(如果当前版本号 和 读取到的版本号相同,则修改数据并把版本号+1,反之则操作失败).
图解:
根据锁策略总结了Synchronized的特点,在 JDK 8 的情况下
JVM 将 Synchronized 锁分为 无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁. 会根据情况依次升级. 也叫 锁膨胀
偏向锁,也是一种乐观锁.认为没有线程会去竞争锁,但是会去给该线程做一个"偏向锁的标记",记录这个锁是属于哪一个线程的.
当偏向锁状态随着其他的线程的竞争,偏向锁的状态被消除门进入了轻量级锁状态,也就是自适应的自旋锁.
此时就是通过 CAS来实现的.
是在用户态完成的操作,但是自旋操作会浪费CPU.
这里的自旋不会一直进行下去,当达到一定时间或者一定的次数的时候,就不会自旋了.
当此时的冲突概率比较大,锁的竞争更激烈,自旋锁不能快速获取到锁的状态,就会继续膨胀为重量级锁.
这里的重量级锁就用到了内核提供的mutex
通过 编译器 和 JVM 来判断锁是否可以消除.如果可以消除那么就直接消除锁.
例如StringBuffer中,很多方法用到了Synchronized .但是在单线程下,就算有加锁,编译器和JVM都会消除锁.
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("c");
这里的append的调用就会频繁的涉及到加锁和解锁,但是在单线程下,这些加锁和解锁的操作都是没必要的.
一段逻辑中如果多次出现加锁解锁,编译器 + JVM 就会自动进行锁的粗化
Callable也是一个创建线程的方法,
一种多线程计算 1~1000的代码
public class ThreadDemo1 {
static class Result{
public int sum=0;
public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread t1 = new Thread(){
@Override
public void run() {
int sum = 0;
for (int i = 0; i <= 1000; i++) {
sum += i;
}
synchronized (result.lock){
result.sum = sum;
result.lock.notify();
}
}
};
t1.start();
synchronized (result.lock){
if(result.sum == 0){
result.lock.wait();
}
System.out.println(result.sum);
}
}
}
这个代码运用到了一个辅助类 Result 和 wait notify 操作.
这里使用Callable 来解决
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000 ; i++) {
sum+=i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);
}
}
这里就简化了很多.
还可以写成匿名类部类的形式
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <= 1000 ; i++) {
sum+=i;
}
return sum;
}
});
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
Callable 相比于 Runnable ,都是描述一个"任务"的,Callable有返回值.Runnable没有.Callable中是call方法 Runnale 是run方法.
RenntrantLock 是一个可重入的互斥锁.
RenntrantLock 把加锁和解锁的操作分开了
提供了几个方法:
ReentrantLock 和 Synchronized 的区别
原子类 内部是使用 CAS 来实现的.所以性能比加锁实现 i++ 高很多.
原子类有:
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
Atomicreference
AtomicStampedReference
一个使用示例:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.addAndGet(2); // i += delta
atomicInteger.decrementAndGet(); //--i
atomicInteger.getAndDecrement(); //i--
atomicInteger.incrementAndGet(); //++i
atomicInteger.getAndIncrement(); //i++
}
虽然线程相比于进程,创建和销毁更轻量.但频繁的创建和销毁的时候还是会比较低效.为了解决这个问题就引入了线程池.如果某个线程不使用了,就将该线程放到池子里,需要使用的时候,也不需要创建,而是从池子里拿.
使用示例:
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors 创建线程池的几种方式
Executors 本质上是 ThreadPoolExecutor 类的封装.
ThreadPoolExecutor 的构造方法
理解 ThreadPoolExecutor 构造方法的参数
把创建一个线程池想象成开个公司. 每个员工相当于一个线程.
ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<3;i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
信号量,是用来表示"可用资源的个数".本质上是一个计数器.
例子: 信号量就相当于是你进停车场的时候,外面会有一个计数牌子,表示当前可用停车位还有多少个.
代码示例:
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(4);//这里的4是初始化可用资源数目
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("申请资源成功");
semaphore.acquire();// 申请资源
System.out.println("获取到了资源");
Thread.sleep(1000);
semaphore.release();// 释放资源
System.out.println("释放了资源");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 20; i++) {
Thread t = new Thread(runnable);
t.start();
}
}
观察结果:
前四次的获取资源会很快,然后就会等待其他线程释放资源后进行获取.
CountDownLatch 同时等待N个任务执行结束.
相当于比赛的时候,等全部比完,才会公布成绩.
import java.util.concurrent.CountDownLatch;
public class ThreadDemo7 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) Math.random() * 10000);
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
Thread t = new Thread(runnable);
t.start();
}
countDownLatch.await();
System.out.println(" 结束 ");
}
}
我们知道 HashMap 是线程不安全的,相比于HashTable 和 ConcurrentHashMap比起来,他的key值是允许为空的.
HashTable 是线程安全的,把关键方法都加了 synchronized 关键字
一个HashTable 实例,只有一把锁.
当有多个线程并发去修改这个 HashTable 实例的时候,此时这多个线程就会同时竞争一把锁(锁的冲突概率非常高)
当HashTable 扩容的时候,是创建一个更大的内存,然后把数据全部复制过去,这个时候这个插入操作就非常的复杂.
ConcurrentHashMap也是线程安全的.但是是相当于针对每一个哈希桶来加锁的.
图解
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止
情形一: 一个线程针对一把锁,连续加锁两次或两次以上,并且该锁是不可重入锁.
lock void func1(){
}
lock void func2(){
func1()
}
这里当第一个线程还没释放锁的时候又进行加锁,如果是不可重入锁那么就是死锁.
情形二: 有两个线程 有两个锁
这两个线程同时并发执行就可能出现死锁.
死锁产生的四个必要条件:
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
我将 Bootstrap 与 css 和 java 脚本结合使用。在不影响前端代码的情况下,我真的很难在css中绘制这个背景。在许多问题中,人们将宽度和高度设置为 0%。但是由于我的导航栏,我不能使用
我正在用 c 编写一个程序来读取文件的内容。代码如下: #include void main() { char line[90]; while(scanf("%79[^\
我想使用 javascript 获取矩阵数组的所有对 Angular 线。假设输入输出如下: input = [ [1,2,3], [4,5,6], [7,8,9], ] output =
可以用pdfmake绘制lines,circles和other shapes吗?如果是,是否有documentation或样本?我想用jsPDF替换pdfmake。 最佳答案 是的,有可能。 pdfm
我有一个小svg小部件,其目的是显示角度列表(参见图片)。 现在,角度是线元素,仅具有笔触,没有填充。但是现在我想使用一种“内部填充”颜色和一种“笔触/边框”颜色。我猜想line元素不能解决这个问题,
我正在为带有三角对象的 3D 场景编写一个非常基本的光线转换器,一切都工作正常,直到我决定尝试从场景原点 (0/0/0) 以外的点转换光线。 但是,当我将光线原点更改为 (0/1/0) 时,相交测试突
这个问题已经有答案了: Why do people write "#!/usr/bin/env python" on the first line of a Python script? (22 个回
如何使用大约 50 个星号 * 并使用 for 循环绘制一条水平线?当我尝试这样做时,结果是垂直(而不是水平)列出 50 个星号。 public void drawAstline() { f
这是一个让球以对角线方式下降的 UI,但球保持静止;线程似乎无法正常工作。你能告诉我如何让球移动吗? 请下载一个球并更改目录,以便程序可以找到您的球的分配位置。没有必要下载足球场,但如果您愿意,也可以
我在我的一个项目中使用 Jmeter 和 Ant,当我们生成报告时,它会在报告中显示 URL、#Samples、失败、成功率、平均时间、最短时间、最长时间。 我也想在报告中包含 90% 的时间线。 现
我有一个不寻常的问题,希望有人能帮助我。我想用 Canvas (android) 画一条 Swing 或波浪线,但我不知道该怎么做。它将成为蝌蚪的尾部,所以理想情况下我希望它的形状更像三角形,一端更大
这个问题已经有答案了: Checking Collision of Shapes with JavaFX (1 个回答) 已关闭 8 年前。 我正在使用 JavaFx 8 库。 我的任务很简单:我想检
如何按编号的百分比拆分文件。行数? 假设我想将我的文件分成 3 个部分(60%/20%/20% 部分),我可以手动执行此操作,-_-: $ wc -l brown.txt 57339 brown.tx
我正在努力实现这样的目标: 但这就是我设法做到的。 你能帮我实现预期的结果吗? 更新: 如果我删除 bootstrap.css 依赖项,问题就会消失。我怎样才能让它与 Bootstrap 一起工作?
我目前正在构建一个网站,但遇到了 transform: scale 的问题。我有一个按钮,当用户将鼠标悬停在它上面时,会发生两件事: 背景以对 Angular 线“扫过” 按钮标签颜色改变 按钮稍微变
我需要使用直线和仿射变换绘制大量数据点的图形(缩放图形以适合 View )。 目前,我正在使用 NSBezierPath,但我认为它效率很低(因为点在绘制之前被复制到贝塞尔路径)。通过将我的数据切割成
我正在使用基于 SVM 分类的 HOG 特征检测器。我可以成功提取车牌,但提取的车牌除了车牌号外还有一些不必要的像素/线。我的图像处理流程如下: 在灰度图像上应用 HOG 检测器 裁剪检测到的区域 调
我有以下图片: 我想填充它的轮廓(即我想在这张图片中填充线条)。 我尝试了形态学闭合,但使用大小为 3x3 的矩形内核和 10 迭代并没有填满整个边界。我还尝试了一个 21x21 内核和 1 迭代,但
我必须找到一种算法,可以找到两组数组之间的交集总数,而其中一个数组已排序。 举个例子,我们有这两个数组,我们向相应的数字画直线。 这两个数组为我们提供了总共 7 个交集。 有什么样的算法可以帮助我解决
简单地说 - 我想使用透视投影从近裁剪平面绘制一条射线/线到远裁剪平面。我有我认为是使用各种 OpenGL/图形编程指南中描述的方法通过单击鼠标生成的正确标准化的世界坐标。 我遇到的问题是我的光线似乎
我是一名优秀的程序员,十分优秀!