Kamafeel

求其上,得其中;求其中,得其下,求其下,必败


  • 首页

  • 归档

JAVA多线程锁

发表于 2020-09-18 | 分类于 JAVA , 并发
类型 并发特性 其他
synchronized JVM层面上实现,JDK1.6后不比ReentrantLock差 不要考虑加锁,释放锁,竞争不太频繁
ReentrantLock、ReentrantReadWriteLock 对象层面的锁定 锁定一定会被释放,就必须将unLock()放到finally{},根据场景使用
StampedLock 读多写少,很适合 是不可重入锁,不能在一个线程中反复获取同一个锁 ,根据场景使用

StampedLock使用说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Point {
private final StampedLock stampedLock = new StampedLock();

private double x;
private double y;

public void move(double deltaX, double deltaY) {
long stamp = stampedLock.writeLock(); // 获取写锁
try {
x += deltaX;
y += deltaY;
} finally {
stampedLock.unlockWrite(stamp); // 释放写锁
}
}

public double distanceFromOrigin() {
long stamp = stampedLock.tryOptimisticRead(); // 获得一个乐观读锁
// 注意下面两行代码不是原子操作
// 假设x,y = (100,200)
double currentX = x;
// 此处已读取到x=100,但x,y可能被写线程修改为(300,400)
double currentY = y;
// 此处已读取到y,如果没有写入,读取是正确的(100,200)
// 如果有写入,读取是错误的(100,400)
if (!stampedLock.validate(stamp)) { // 检查乐观读锁后是否有其他写锁发生
//如果在读取过程中有写入,版本号会发生变化,验证将失败。在失败的时候,我们再通过获取悲观读锁再次读取
stamp = stampedLock.readLock(); // 获取一个悲观读锁
try {
currentX = x;
currentY = y;
} finally {
stampedLock.unlockRead(stamp); // 释放悲观读锁
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}

https://www.liaoxuefeng.com/wiki/1252599548343744/1309138673991714

https://www.pdai.tech/md/java/java8/java8-stampedlock.html

MongoDB开发

发表于 2020-09-17 | 更新于 2021-08-02 | 分类于 中间件 , mongodb

PostgreSQL开发

发表于 2020-09-17 | 分类于 数据库 , PostgreSQL

https://www.cnblogs.com/houzheng/p/12951923.html

MYSQL VS PostgreSQL

发表于 2020-09-17 | 分类于 数据库 , PostgreSQL

PostgreSQL在处理海量数据集,复杂查询和读写操作时速度更快。
MySQL使用只读命令的速度更快
PostgreSQL自带集群,已经比较好
PostgreSQL的插件真的很多,很牛逼
GIS之类,请选择PostgreSQL
如果需要从Oracle切换,选择PostgreSQL
如果一般运用,选择MYSQL

MYSQL8+ PostgreSQL10+ 功能基本无差别
窗口函数,WITH表达式,申明式分区,全文检索

题外话

PostgreSQL 区分大小写

https://www.jianshu.com/p/1c8b4bb02eec?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

https://github.com/digoal/blog

https://developer.aliyun.com/article/98539

MYSQL优化建议

发表于 2020-09-17 | 更新于 2021-11-18 | 分类于 数据库 , MYSQL

索引

函数的参数,否则无法使用索引
多个列作为条件进行查询时,多列索引,比单列索引性能更高
like %字段名 会使索引失效

建立多列索引的顺序,注意数据分散:

1
2
3
4
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
COUNT(*)
FROM payment;

对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引
https://juejin.im/post/6844904200254521351

应用

1.多使用Explain看SQL执行计划,核心目的,减少返回数量。

2.切分大查询

3.如非必要,不要使用外键(性能要求不高,业务要求严谨可以考虑)

外键等于把数据的一致性事务实现,全部交给数据库服务器完成
增,删,改影响性能,加大锁表概率,或者死锁

char(n)
gbk 2
utf8 3
utf8mb4 4

Emoji表情使用utf8mb4

Java内存模型

发表于 2020-09-17 | 更新于 2022-02-28 | 分类于 JVM , 基础

Java Memory Model and Thread Specification
JMM是和线程相关的规范,JMM主要解决多线程环境下,线程之间的通信。

多线程环境下,CPU Cache和主内存之间数据不一致问题。
alt

JMM的角色:

JMM管理的程序变量,主要是指在对象实例字段、静态字段、构成数组字段的元素等,不包括方法参数、方法局部变量等保存在栈里的变量,因为栈本身就是线程私有的,并不存在线程一致性问题

alt

指令重排(Reordering)

编译器指令的重排
编译器在不改变单线程程序语义的前提下,可以重新调整语句的执行顺序

处理器指令级并行的重排
现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序

内存系统的重排
由于处理器使用缓存和读/写缓冲区,这使得主内存和工作内存间的数据加载和存储操作看上去可能是在乱序执行的

JMM的编译器重排序规则会禁止volatile变量、synchronized、final等特定指令的编译器重排序

Happens-before规则

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。

顺序一致性的解决方案:

volatile变量
注意变量+1这种,非原子操作,属于read,load,use,需要自行使用synchronized/Reentrantlock进行同步,或者直接使用tomicInteger之类的。

synchronized关键字

final关键字

https://segmentfault.com/a/1190000021637869

http://www.jiangxinlingdu.com/concurrent/2019/02/16/java-memory-model.html

堆heap 和 栈stack

alt

注意stack会出现StackOverflowError
线程栈的空间大小-Xss 默认是1M

单纯是增加 -Xss 一般都没用 需要检查是否存在递归无限调用,导致线程栈空间不足

数据库事务

发表于 2020-09-16 | 更新于 2021-11-18 | 分类于 数据库 , 基础

隔离级别

未提交读(READ UNCOMMITTED)

事务中的修改,即使没有提交,对其他事务也是可见的。

提交读(READ COMMITTED)

一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其他事务是不可见的。

可重复读(REPEATABLE READ)

保证在同一个事务中多次读取同样数据的结果是一样的。

可串行化(SERIALIZABLE)

强制事务串行执行。需要加锁实现,而其它隔离级别通常不需要。

不可重复读,幻读
不可重复读重点在于update和delete,而幻读的重点在于insert。
解决办法,select * from test where id=1 for update;(加锁)

主流数据库基本都使用 Multi-version Concurrency Control (MVCC) 控制事务。

PostgreSQL(事务回滚迅速),Oracle 提交读
MYSQL 可重复读

  • https://segmentfault.com/a/1190000016566788
  • https://juejin.im/post/6844903911623491592#heading-8

多线程工具包

发表于 2020-09-16 | 更新于 2020-09-17 | 分类于 JAVA , 并发
类型 并发特性 其他
CountDownLatch 一般用于某个线程 A 等待若干个其他线程执行完任务之后,它才执行 不可复用
CyclicBarrier 般用于一组线程互相等待至某个状态,然后这一组线程再同时执行 可复用;方法更多
Semaphore 流控(资源并发控制)
Exchanger 用于线程间协作的工具类,用于两个线程间能够交换
Phaser 移相器 线程之前的动作协调,同步 很灵活,底层非AQS,内部实现比较复杂

Phaser

alt

Phaser的灵活性主要体现在在构造函数时不需要强制指定目前有多少参与协作的线程,可以在运行时动态改变。

register()//添加一个新的注册者
bulkRegister(int parties)//添加指定数量的多个注册者
arrive()// 到达栅栏点直接执行,无须等待其他的线程
arriveAndAwaitAdvance()//到达栅栏点,必须等待其他所有注册者到达
arriveAndDeregister()//到达栅栏点,注销自己无须等待其他的注册者到达
onAdvance(int phase, int registeredParties)//多个线程达到注册点之后,会调用该方法。

(1)替代CountDownLatch实现一次性的共享锁例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void runTasks(List<Runnable> tasks) {
final Phaser phaser = new Phaser(1); // "1" to register self
// create and start threads
for (final Runnable task : tasks) {
phaser.register();
new Thread() {
public void run() {
phaser.arriveAndAwaitAdvance(); // await all creation
task.run();
}
}.start();
}

// allow threads to start and deregister self
phaser.arriveAndDeregister();
}

(2)模拟CyclicBarrier的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package concurrent.tools.phaser;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

/**
* Created by Administrator on 2018/8/27.
*/
public class PhaserDemo5 {

public static void main(String[] args) throws InterruptedException {

Phaser phaser=new Phaser(){
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("=================step-"+phase+"==================="+registeredParties);
return super.onAdvance(phase, registeredParties);
}
};

Bus bus1=new Bus(phaser,"小张");
Bus bus2=new Bus(phaser,"小李");
Bus bus3=new Bus(phaser,"小王");

bus1.start();
bus2.start();
bus3.start();


System.out.println(phaser.getRegisteredParties());



}


static public class Bus extends Thread{

private Phaser phaser;
private Random random;

public Bus(Phaser phaser,String name){
this.phaser=phaser;
setName(name);
random=new Random();
phaser.register();
}


private void trip(int sleepRange,String cityName){
System.out.println(this.getName()+" 准备去"+cityName+"....");
int sleep=random.nextInt(sleepRange);
try {
TimeUnit.SECONDS.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+" 达到"+cityName+"...... ");
if(this.getName().equals("小王1")){ // 测试掉队的情况
try {
TimeUnit.SECONDS.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
phaser.arriveAndDeregister();
}else {
phaser.arriveAndAwaitAdvance();
}
}





@Override
public void run() {

try {
int s=random.nextInt(3);
TimeUnit.SECONDS.sleep(s);
System.out.println(this.getName()+" 准备好了,旅行路线=北京=>上海=>杭州 ");
phaser.arriveAndAwaitAdvance();// 等待所有的汽车准备好
} catch (InterruptedException e) {
e.printStackTrace();
}


trip(5,"北京");
trip(5,"上海");
trip(3,"杭州");

}
}

}

atomic包

发表于 2020-09-16 | 分类于 JAVA , 并发
  • AtomicIntegeFieldUpdater:原子更新整型字段类;
  • AtomicLongFieldUpdater:原子更新长整型字段类;
  • AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号。而为什么在更新的时候会带有版本号,是为了解决 CAS 的 ABA 问题;

实列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class AtomicDemo {
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public static void main(String[] args) {
User user = new User("a", 1);
int oldValue = updater.getAndAdd(user, 5);
System.out.println(oldValue);
System.out.println(updater.get(user));
}

static class User {
private String userName;
public **volatile** int age;

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

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

线程池

发表于 2020-09-16 | 分类于 JAVA , 并发

alt

execute 方法执行逻辑有这样几种情况:

  1. 如果当前运行的线程少于 corePoolSize,则会创建新的线程来执行新的任务;
  2. 如果运行的线程个数等于或者大于 corePoolSize,则会将提交的任务存放到阻塞队列 workQueue 中;
  3. 如果当前 workQueue 队列已满的话,则会创建新的线程来执行任务;
  4. 如果线程个数已经超过了 maximumPoolSize,则会使用饱和策略 RejectedExecutionHandler 来进行处理。

CPU 密集型
Ncpu+1

IO密集型
2xNcpu

Runtime.getRuntime().availableProcessors()

1…141516…21

Kamafeel

204 日志
57 分类
129 标签
© 2022 Kamafeel
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.1.2