1.并发线程的3个特性
(1)原子性问题:如果多个线程共用共享资源,那么会出现资源被修改,但是其他线程拿不到最新的值,这时候可以用volatile(对其他线程可见和防止指令重排序,但是不能保证原子性,没办法实现线程同步)解决
(2)可见性问题:有些变量比如 i++,就不能用volatile,只能用同步锁 synchronized(可以解决原子性,有序性,可见性,但是不适用高并发,影响性能),单个线程自己独享。
(3)有序性问题:用JMM规范去解决多线程顺序的问题
2.java可以用内存屏障来解决编译器重排序的问题
3.volatile应用场景
线程执行run()的时候我们需要在线程中不停的做一些事情,比如while循环,那么这时候该如何停止线程呢?如果线程做的事情不是耗时的,那么只需要使用一个标志即可。如果需要退出时,调用setStop()即可。这里就使用了关键字volatile,这个关键字的目的是如果修改了isStop的值,那么在while循环中可以立即读取到修改后的值。
这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了。
4.解决线程安全问题
(1)方法一:
import com.ruoyi.framework.web.domain.server.Sys;
import net.sf.ehcache.concurrent.Sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Thread1 implements Runnable {
private int ticket = 5;
Lock lock = new ReentrantLock();
@Override
public void run(){
while (ticket>0) {
synchronized (this) {
if(ticket>0){
ticket--;
System.out.print(Thread.currentThread().getName() + "窗口卖了1张票还有剩余票:" + ticket);
System.out.print("\n");
}
}
}
}
}
(2)方法二:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Thread1 implements Runnable {
private int ticket = 5;
Lock lock = new ReentrantLock();
@Override
public void run(){
while (ticket>0) {
lock.lock();//多个线程竞争一个锁
try{
if(ticket>0){
ticket--;
System.out.print(Thread.currentThread().getName() + "窗口卖了1张票还有剩余票:" + ticket);
System.out.print("\n");
}
}catch (Exception e){
}finally {
lock.unlock();//抢到的线程执行完业务释放锁
}
try {
Thread.sleep(1000);
}catch (Exception e){
}
}
}
}
思考:为什么lock比synchronized 好用,因为finally 肯定会执行,那么就会解决死锁的问题
6.JVM中的锁解决不了分布式高并发多任务进程,得用分布式锁,三种分布式锁:Mysql数据库的锁机制实现,要求数据库支持行级锁;Redis的setnx命令实现;利用zookpper的节点特性。
下面重点讲解redis分布式
1.pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2.配置文件
# redis配置,默认没有密码
redis:
host: 192.168.1.142
port: 6379
password: 123456
jedis:
pool:
min-idle: 8
max-idle: 500
max-active: 2000
max-wait: 10000
timeout: 0
3.代码实现
Jedis jedis = new Jedis("192.168.1.142:6379");
String ret = jedis.set("wds","123456","NX","PX",10000);
注意:当redis中存在key,则不会有任何操作,当没有相同的key,才会往里面插数据;
NX表示使用"nx"模式,"PX"表示失效的时间单位毫秒;
加锁通过setnx写值成功则加锁成功;
解锁:匹配随机值,删除redis上的特点key数据,要保证获取数据,判断一致以及删除
数据三个操作是原子的,执行如下lua脚本:
if redis.call("get",KEYS[1])==ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
4.lua脚本,因为redis是单进程单线程模式,所以一开始就会执行lua脚本。redisTemplate不可以加锁的原因是它不支持setnx方法。
5.具体的加锁解锁代码(参考:https://www.cnblogs.com/linjiqin/p/8003838.html)
加锁:
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
解锁:
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}