开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

spring三级缓存为什么不可以去掉第二级

发表于 2021-10-26

原因:因为大多数情况 singletonFactory.getObject()都是返回的一个新对象(相当于直接new对象),如果不加第二级缓存则不能保证单例。

如果强行去掉第二级缓存,只要第一级和第三级缓存行不行呢?

答:可以,但会增加实现的复杂度。例:singletonFactory.getObject()获取的未初始化单例可以放到一级缓存,但是别的对象获取到未初始化的对象,可以考虑添加前缀解决。

解释:如下

spring获取单例的方法,
singletonObjects(一级缓存:存放完全体单例)
earlySingletonObjects(二级缓存:存放初始化但没有赋值的单例)
singletonFactory(三级缓存:初始化后没有赋值的单例用ObjectFactory包装的对象,其实所有的单例实例化后都会放入此缓存;)

注:获取第三级缓存并不是直接从map里get值,如果是普通单例则 ObjectFactory.getObject直接返回自己本身;如果是实现了FactoryBean接口,ObjectFactory.getObject返回的是自己写的重载方法

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
java复制代码protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//第三级缓存要调用singletonFactory.getObject()方法
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringCloud微服务实战——搭建企业级开发框架(六)

发表于 2021-10-26

  knife4j是为集成Swagger生成api文档的增强解决方案,前后端Java代码以及前端Ui模块进行分离,在微服务架构下使用更加灵活,
提供专注于Swagger的增强解决方案,不同于只是改善增强前端Ui部分,我们这里使用knife4j作为文档管理工具来代替swagger-ui。

1、在GitEgg-Platform工程下新建gitegg-platform-swagger子工程,在GigEgg-Platform中的子工程gitegg-platform-bom中,修改pom
.xml,以maven bom的方式使用knife4j:

1
2
3
4
5
6
7
8
xml复制代码            <!-- Swagger2 knife4j bom方式引入 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-dependencies</artifactId>
<version>${knife4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

2、在gitegg-platform-swagger子工程中的pom.xml添加knife4j引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>GitEgg-Platform</artifactId>
<groupId>com.gitegg.platform</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gitegg-platform-swagger</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
</dependencies>

</project>

3、在gitegg-platform-swagger子工程中新建SwaggerConfig.java文件:

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
kotlin复制代码package com.gitegg.platform.swagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {

@Bean(value = "GitEggApi")
public Docket GitEggApi() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//分组名称
.groupName("2.X版本")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.gitegg.*.*.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder().version("1.0.0")
.title("Spring Cloud Swagger2 文档")
.description("Spring Cloud Swagger2 文档")
.termsOfServiceUrl("www.gitegg.com")
.build();
}

}

4、在gitegg-service工程中引入gitegg-platform-swagger

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
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>GitEgg-Cloud</artifactId>
<groupId>com.gitegg.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gitegg-service</artifactId>
<packaging>pom</packaging>
<modules>
<module>gitegg-service-base</module>
<module>gitegg-service-bigdata</module>
<module>gitegg-service-system</module>
</modules>

<dependencies>
<!-- gitegg数据库驱动及连接池 -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-db</artifactId>
</dependency>
<!-- gitegg mybatis-plus -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-mybatis</artifactId>
</dependency>
<!-- gitegg swagger2-knife4j -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-swagger</artifactId>
</dependency>
<!-- spring boot web核心包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring boot 健康监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

</project>

5、在gitegg-service-system工程下的SystemController.java类里面添加Swagger2相关注解

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
kotlin复制代码package com.gitegg.service.system.controller;

import com.gitegg.service.system.service.ISystemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "system")
@AllArgsConstructor
@Api(tags = "gitegg-system")
public class SystemController {

private final ISystemService systemService;

@GetMapping(value = "list")
@ApiOperation(value = "system list接口")
public Object list() {
return systemService.list();
}


@GetMapping(value = "page")
@ApiOperation(value = "system page接口")
public Object page() {
return systemService.page();
}
}

6、GitEggSystemApplication.java加入组件扫描的注解,让Spring在启动的时候加载到swagger2的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kotlin复制代码package com.gitegg.service.system;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
* gitegg-system 启动类
*/
@ComponentScan(basePackages = "com.gitegg")
@MapperScan("com.gitegg.*.*.mapper")
@SpringBootApplication
public class GitEggSystemApplication {

public static void main(String[] args) {
SpringApplication.run(GitEggSystemApplication.class,args);
}
}

7、运行gitegg-service-system,打开浏览器访问:http://127.0.0.1:8001/doc.html,可以看到swagger2文档界面

image.png

本文源码在gitee.com/wmz1930/Git… 的chapter-06分支。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

mysql 主从复制 详细步骤记录

发表于 2021-10-26

主服务器:阿里云 ;从服务器:Mac本地服务器[主从数据库版本最好一致]

主服务器配置 /etc/mysql/mysql.conf.d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ini复制代码[mysqld]

pid-file = /var/run/mysqld/mysqld.pid

socket = /var/run/mysqld/mysqld.sock

datadir = /var/lib/mysql

log-error = /var/log/mysql/error.log

log-bin = mysql-bin #开启二进制日志

server-id = 1 #设置server-id

binlog-do-db = test_db # 只同步test_db

创建数据库,用户,赋予权限[全程在root用户下]

1
2
3
4
5
6
7
8
9
sql复制代码CREATE DATABASE IF NOT EXISTS test_db; #创建数据库

CREATE USER 'test1'@'IP' IDENTIFIED BY 'test1';#创建用户

GRANT ALL PRIVILEGES ON `test_db`.* TO `test1`@`%` WITH GRANT OPTION;#赋予增删改查权限

GRANT REPLICATION SLAVE ON *.* TO 'test1'@'%';#建立复制时

FLUSH PRIVILEGES;#刷新权限

重启mysql

service mysql restart

查看mysql的test1用户信息

image.png

  • file:mysql-bin.000001
  • position:559

Mac从服务器

参考【MAC】MYSQL8.0版本添加配置文件MY.CNF

mysql安装路径在 /usr/local/mysql

在路径 /usr/etc中创建 my.cnf文件

1
2
3
4
5
6
7
8
9
ini复制代码[client]

default-character-set = utf8

[mysqld]

server-id = 2 #设置server-id,必须唯一

character-set-server = utf8

重启mysql

sudo /usr/local/mysql/support-files/mysql.server restart

连接slave,若已开启需要先暂停slave

slave stop/start;

1
2
3
4
5
6
7
8
9
10
11
ini复制代码CHANGE MASTER TO

CHANGE_HOST = 'IP',

CHANGE_USER = 'test1',

CHANGE_PASSWORD = '密码',

CHANGE_LOG_FILE = 'mysql-bin.000001',

MASTER_LOG_POS = 559;

查看连接状态,Slave_双YES则表示连接成功

show slave status\G

  • Slave_IO_Running: YES
  • Slave_SQL_Running: YES

两个都是YES则连接成功

扩展 常见问题排查

1.主服务器是云服务器,没有开通安全组;

2.账户密码错误;

3.连接服务器时语法;

4.mysql配置文件问题;

5.主服务器mysql权限;

6.从数据库没创建;

扩展思维 可互为主从,实现读写分离

image.png

安装mysql8详细步骤记录:juejin.cn/post/701916…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何在python中实现一个自定义的列表或字典 前情提要

发表于 2021-10-26

前情提要

在很多的python库之中,我们可以看到有的时候,库作者会使用一些很特殊的“列表”或者“字典”。虽然他们看起来很像是一个列表或者字典,但是使用的方法却又不一样,这是因为那不是真的python中原本的列表和字典,而是作者自己创建的。那么,我们如何可以创建我们自己的列表和字典呢?

魔法方法

前后都使用两个下划线的方法,一般被称之为魔法方法,比如我们常见的__init__,就是一种魔法方法。一般来说,我们自行定义变量名的时候,不要定义很像是魔法方法的变量名。魔法方法被定义后,可以在适当的时候自动被调用,一般不需要手动对其进行调用。

在python中,实现一个序列,我们需要以下四种魔法方法

1
2
3
4
5
6
7
Python复制代码__len__(self):这个方法应该返回元素的个数,比如我们常用的len(),就是通过这个魔法方法实现的。

__getitem__(self, key):这个方法得到了一个key,应当返回一个value。

__setitem__(self, key, value):这个方法定义了一个key和一个value,用于键值对的定义。

__delitem__(self, key):这个方法定义了使用del的时候,会进行怎么样的处理

另外,一般来说,错误的键应当引发TypeError异常,而错误的索引应当引发IndexError异常

使用方法

在python的列表中,只能够使用数字作为索引,如果使用字符串的数字的话,那么会引发异常。因此,我们可以尝试一下,对原始的列表进行扩充,使其可以接受字符串作为列表的索引。

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
Python复制代码class XiaList:
"""
作者:瞎老弟
时间:2021-10-26
说明:实现了一种伪列表,可以同时接受字符串索引和数字索引
联系方式:qq1413274264
"""
def __init__(self, old_list):
self.num = len(old_list)
self.list = old_list

def __len__(self):
return self.num

def __getitem__(self, key):
try:
k = int(key)
return self.list[k]
except:
raise TypeError

def __setitem__(self, key, value):
try:
k = int(key)
self.old_list[key] = value
except:
raise TypeError

if __name__ == "__main__":
x = XiaList([1, 2, 3, 4, 5])
print(x[0])
print(x["0"])
print(x[-1])
print(x["-1"])
print(len(x))

注意,这样的自建列表,存在很多问题,几乎全部的关于列表的方法都不能再被使用了。

继承使用

为了解决正常列表的方法不能再被使用,我们可以考虑直接继承list,以此得到普通列表的所有方法。

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
Python复制代码class XiaList2(list):
"""
作者:瞎老弟
时间:2021-10-26
联系方式:qq1413274264
说明:使用XiaList2来获取元素时,如果元素为整数,则显示为多少万
"""
def __init__(self, *args):
super().__init__(*args)

def __getitem__(self, key):
if not isinstance(key, int):
raise TypeError
try:
n = int(super().__getitem__(key))
return str(n / 10000) + "万"
except:
return super().__getitem__(key)

if __name__ == "__main__":
x = XiaList2([10000, 52000, 99999, "314125926", "abcd"])
for i in range(len(x)):
print(x[i])
x.append(520000)
print(x[-1])

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Mac m1 配置java环境

发表于 2021-10-26
  • 安装jdk,其中jdk已经包含jre,www.oracle.com/java/techno…,选择macos,m1的选择下载arm版本,或者选择其它的jdk版本,这是jdk17

BBEDBD91-D822-4104-82A8-1C9359A4C72F.png

  • 下载完成后,傻瓜式安装,下一步即可,最后下载完成
  • 配置环境变量,打开终端输入vim ~/.zshrc,zshrc是配置文件,配置java环境变量,我的地址是/Library/Java/JavaVirtualMachines/jdk-17.0.1.jdk/Contents/Home,默认安装的应该都是这个路径,在zshrc文件中写入java_home环境变量
1
javascript复制代码export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-17.0.1.jdk/Contents/Home
  • 测试,输出HelloWorld,工具idea
  • 新建项目-选择java模块*
    image.png
  • 选择添加JDK*
    image.png
  • 选择jdk-17.0.1.jdk*
    image.png
  • 下一步,自定义,不要选择默认模版*

image.png
定义好项目名称,点击完成
image.png
新建包名
image.png

image.png

image.png
新建的包下面创建类
image.png
右键点击运行

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java多线程:tryLock()方法 Java多线程:tr

发表于 2021-10-26

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Java多线程:tryLock()方法

tryLock(long time, TimeUnit unit) 的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁,通过该方法可以实现锁对象的限时等待。

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
java复制代码package com.wkcto.lock.reentrant;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
*tryLock(long time, TimeUnit unit) 的基本使用
*/
public class Test07 {
static class TimeLock implements Runnable{
private static ReentrantLock lock = new ReentrantLock(); //定义锁对象

@Override
public void run() {
try {
if ( lock.tryLock(3, TimeUnit.SECONDS) ){ //获得锁返回true
System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
// Thread.sleep(4000); //假设Thread-0线程先持有锁,完成任务需要4秒钟,Thread-1线程尝试获得锁,Thread-1线程在3秒内还没有获得锁的话,Thread-1线程会放弃
Thread.sleep(2000); //假设Thread-0线程先持有锁,完成任务需要2秒钟,Thread-1线程尝试获得锁,Thread-1线程会一直尝试,在它约定尝试的3秒内可以获得锁对象
}else { //没有获得锁
System.out.println(Thread.currentThread().getName() + "没有获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}

public static void main(String[] args) {
TimeLock timeLock = new TimeLock();

Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
}

tryLock()仅在调用时锁定未被其他线程持有的锁,如果调用方法时,锁对象对其他线程持有,则放弃,调用方法尝试获得没,如果该锁没有被其他线程占用则返回true表示锁定成功; 如果锁被其他线程占用则返回false,不等待。

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
java复制代码package com.wkcto.lock.reentrant;

import java.util.concurrent.locks.ReentrantLock;

/**
*tryLock()
* 当锁对象没有被其他线程持有的情况下才会获得该锁定
*/
public class Test08 {
static class Service{
private ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
if (lock.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁定");
Thread.sleep(3000); //模拟执行任务的时长
}else {
System.out.println(Thread.currentThread().getName() + "没有获得锁定");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}

public static void main(String[] args) throws InterruptedException {
Service service = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
service.serviceMethod();
}
};

Thread t1 = new Thread(r);
t1.start();
Thread.sleep(50); //睡眠50毫秒,确保t1线程锁定
Thread t2 = new Thread(r);
t2.start();
}
}
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
java复制代码package com.wkcto.lock.reentrant;

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

/**
* 使用tryLock()可以避免死锁
*/
public class Test09 {
static class IntLock implements Runnable{
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
private int lockNum; //用于控制锁的顺序

public IntLock(int lockNum) {
this.lockNum = lockNum;
}

@Override
public void run() {
if ( lockNum % 2 == 0 ){ //偶数先锁1,再锁2
while (true){
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁1, 还想获得锁2");
Thread.sleep(new Random().nextInt(100));

try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁1与锁2 ----完成任务了");
return; //结束run()方法执行,即当前线程结束
}
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
}else { //奇数就先锁2,再锁1
while (true){
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁2, 还想获得锁1");
Thread.sleep(new Random().nextInt(100));

try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁1与锁2 ----完成任务了");
return; //结束run()方法执行,即当前线程结束
}
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
}
}
}
public static void main(String[] args) {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
//运行后,使用tryLock()尝试获得锁,不会傻傻的等待,通过循环不停的再次尝试,如果等待的时间足够长,线程总是会获得想要的资源
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java多线程:lockInterruptibly()方法

发表于 2021-10-26

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Java多线程:lockInterruptibly()方法

lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常。

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
java复制代码package com.wkcto.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常.
*/
public class Test05 {
static class Servier{
private Lock lock = new ReentrantLock(); //定义锁对象
public void serviceMethod(){
try {
// lock.lock(); //获得锁定,即使调用了线程的interrupt()方法,也没有真正的中断线程
lock.lockInterruptibly(); //如果线程被中断了,不会获得锁,会产生异常
System.out.println(Thread.currentThread().getName() + "-- begin lock");
//执行一段耗时的操作
for (int i = 0; i < Integer.MAX_VALUE; i++) {
new StringBuilder();
}
System.out.println( Thread.currentThread().getName() + " -- end lock");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println( Thread.currentThread().getName() + " ***** 释放锁");
lock.unlock(); //释放锁
}
}
}

public static void main(String[] args) throws InterruptedException {
Servier s = new Servier();
Runnable r = new Runnable() {
@Override
public void run() {
s.serviceMethod();
}
};
Thread t1 = new Thread(r);
t1.start();

Thread.sleep(50);
Thread t2 = new Thread(r);
t2.start();
Thread.sleep(50);
t2.interrupt(); //中断t2线程
}
}

对于synchronized内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待。

对于ReentrantLock可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求。

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
java复制代码package com.wkcto.lock.reentrant;

import com.wkcto.pipestream.Test2;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 通过ReentrantLock锁的lockInterruptibly()方法避免死锁的产生
*/
public class Test06 {
static class IntLock implements Runnable{
//创建两个ReentrantLock 锁对象
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lockNum; //定义整数变量,决定使用哪个锁

public IntLock(int lockNum) {
this.lockNum = lockNum;
}

@Override
public void run() {
try {
if ( lockNum % 2 == 1){ //奇数,先锁1,再锁2
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获得锁1,还需要获得锁2");
Thread.sleep(new Random().nextInt(500));
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2....");
}else { //偶数,先锁2,再锁1
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获得锁2,还需要获得锁1");
Thread.sleep(new Random().nextInt(500));
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2....");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if ( lock1.isHeldByCurrentThread()) //判断当前线程是否持有该锁
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println( Thread.currentThread().getName() + "线程退出");
}
}
}

public static void main(String[] args) throws InterruptedException {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);

Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();

//在main线程,等待3000秒,如果还有线程没有结束就中断该线程
Thread.sleep(3000);

//可以中断任何一个线程来解决死锁, t2线程会放弃对锁1的申请,同时释放锁2, t1线程会完成它的任务
if (t2.isAlive()){ t2.interrupt();}
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

解决MybatisPlus批量插入慢的问题

发表于 2021-10-26

问题复现

最近在做一个同步其他数据源的用户和部门,批量插入采用MybatisPlus提供的saveBatch方法,但是在测试中,发现速度很慢

经过翻看源码翻到如下信息:

image.png

MybatisPlus在批量插入的时候也是循环一次一次插入的,每一次会判断这个变量i有没有达到阈值,如果达到阈值,就将这一批数据刷新到数据库

解决方案

在jdbcUrl后追加参数rewriteBatchedStatements=true,让多批次的变成同一批次提交,这样会大大减少时间

原理

详见文档:mysql官方文档

  • 文档截图
    image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go的json解析:Marshal与Unmarshal Go

发表于 2021-10-26

Go的json解析:Marshal与Unmarshal

简介

Json(Javascript Object Nanotation)是一种数据交换格式,常用于前后端数据传输。任意一端将数据转换成json 字符串,另一端再将该字符串解析成相应的数据结构,如string类型,strcut对象等。

实现

Json Marshal:将数据编码成json字符串

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
go复制代码type Stu struct {
Name string `json:"name"`
Age int
HIgh bool
sex string
Class *Class `json:"class"`
}

type Class struct {
Name string
Grade int
}

func main() {
//实例化一个数据结构,用于生成json字符串
stu := Stu{
Name: "张三",
Age: 18,
HIgh: true,
sex: "男",
}

//指针变量
cla := new(Class)
cla.Name = "1班"
cla.Grade = 3
stu.Class=cla

//Marshal失败时err!=nil
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("生成json字符串错误")
}

//jsonStu是[]byte类型,转化成string类型便于查看
fmt.Println(string(jsonStu))
}

结果:

1
bash复制代码{"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}

为什么命名为首字母小写不能导出? 答案:因为golang语法规定,首字母小写的为私有属性,首字母大写为公有属性

从结果中可以看出

  • 只要是可导出成员(变量首字母大写),都可以转成json。因成员变量sex是不可导出的,故无法转成json。
  • 如果变量打上了json标签,如Name旁边的 json:"name" ,那么转化成的json key就用该标签“name”,否则取变量名作为key,如“Age”,“HIgh”。
  • bool类型也是可以直接转换为json的value值。Channel, complex 以及函数不能被编码json字符串。当然,循环的数据结构也不行,它会导致marshal陷入死循环。
  • 指针变量,编码时自动转换为它所指向的值,如cla变量。(当然,不传指针,Stu struct的成员Class如果换成Class struct类型,效果也是一模一样的。只不过指针更快,且能节省内存空间。)

最后,强调一句:json编码成字符串后就是纯粹的字符串了。

上面的成员变量都是已知的类型,只能接收指定的类型,比如string类型的Name只能赋值string类型的数据。
但有时为了通用性,或使代码简洁,我们希望有一种类型可以接受各种类型的数据,并进行json编码。这就用到了interface{}类型。

前言:
interface{}类型其实是个空接口,即没有方法的接口。go的每一种类型都实现了该接口。因此,任何其他类型的数据都可以赋值给interface{}类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go复制代码type Stu struct {
Name interface{} `json:"name"`
Age interface{}
HIgh interface{}
sex interface{}
Class interface{} `json:"class"`
}

type Class struct {
Name string
Grade int
}

func main() {
//与前面的例子一样
......
}

结果:

1
bash复制代码{"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}

从结果中可以看出,无论是string,int,bool,还是指针类型等,都可赋值给interface{}类型,且正常编码,效果与前面的例子一样。

补充:
在实际项目中,编码成json串的数据结构,往往是切片类型。如下定义了一个[]StuRead类型的切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
go复制代码//正确示范

//方式1:只声明,不分配内存
var stus1 []*StuRead

//方式2:分配初始值为0的内存
stus2 := make([]*StuRead,0)

//错误示范
//new()只能实例化一个struct对象,而[]StuRead是切片,不是对象
stus := new([]StuRead)

stu1 := StuRead{成员赋值...}
stu2 := StuRead{成员赋值...}

//由方式1和2创建的切片,都能成功追加数据
//方式2最好分配0长度,append时会自动增长。反之指定初始长度,长度不够时不会自动增长,导致数据丢失
stus1 := appen(stus1,stu1,stu2)
stus2 := appen(stus2,stu1,stu2)

//成功编码
json1,_ := json.Marshal(stus1)
json2,_ := json.Marshal(stus2)

解码时定义对应的切片接受即可

Json Unmarshal:将json字符串解码到相应的数据结构

我们将上面的例子进行解码

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
go复制代码type StuRead struct {
Name interface{} `json:"name"`
Age interface{}
HIgh interface{}
sex interface{}
Class interface{} `json:"class"`
Test interface{}
}

type Class struct {
Name string
Grade int
}

func main() {
//json字符中的"引号,需用\进行转义,否则编译出错
//json字符串沿用上面的结果,但对key进行了大小的修改,并添加了sex数据
data:="{\"name\":\"张三\",\"Age\":18,\"high\":true,\"sex\":\"男\",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}"
str:=[]byte(data)

//1.Unmarshal的第一个参数是json字符串,第二个参数是接受json解析的数据结构。
//第二个参数必须是指针,否则无法接收解析的数据,如stu仍为空对象StuRead{}
//2.可以直接stu:=new(StuRead),此时的stu自身就是指针
stu:=StuRead{}
err:=json.Unmarshal(str,&stu)

//解析失败会报错,如json字符串格式不对,缺"号,缺}等。
if err!=nil{
fmt.Println(err)
}

fmt.Println(stu)
}

结果:

1
bash复制代码{张三 18 true <nil> map[naME:1班 GradE:3] <nil>}

总结:

  • json字符串解析时,需要一个“接收体”接受解析后的数据,且Unmarshal时接收体必须传递指针。否则解析虽不报错,但数据无法赋值到接受体中。如这里用的是StuRead{}接收。
  • 解析时,接收体可自行定义。json串中的key自动在接收体中寻找匹配的项进行赋值。匹配规则是:
+ 先查找与key一样的json标签,找到则赋值给该标签对应的变量(如Name)。
+ 没有json标签的,就从上往下依次查找变量名与key一样的变量,如Age。或者变量名忽略大小写后与key一样的变量。如HIgh,Class。第一个匹配的就赋值,后面就算有匹配的也忽略。
(前提是该变量必需是可导出的,即首字母大写)。
  • 不可导出的变量无法被解析(如sex变量,虽然json串中有key为sex的k-v,解析后其值仍为nil,即空值)
  • 当接收体中存在json串中匹配不了的项时,解析会自动忽略该项,该项仍保留原值。如变量Test,保留空值nil。
  • 你一定会发现,变量Class貌似没有解析为我们期待样子。因为此时的Class是个interface{}类型的变量,而json串中key为CLASS的value是个复合结构,不是可以直接解析的简单类型数据(如“张三”,18,true等)。所以解析时,由于没有指定变量Class的具体类型,json自动将value为复合结构的数据解析为map[string]interface{}类型的项。也就是说,此时的struct Class对象与StuRead中的Class变量没有半毛钱关系,故与这次的json解析没有半毛钱关系。

让我们看一下这几个interface{}变量解析后的类型

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
go复制代码func main() {
//与前边json解析的代码一致
...
fmt.Println(stu) //打印json解析前变量类型
err:=json.Unmarshal(str,&stu)
fmt.Println("--------------json 解析后-----------")
...
fmt.Println(stu) //打印json解析后变量类型
}

//利用反射,打印变量类型
func printType(stu *StuRead){
nameType:=reflect.TypeOf(stu.Name)
ageType:=reflect.TypeOf(stu.Age)
highType:=reflect.TypeOf(stu.HIgh)
sexType:=reflect.TypeOf(stu.sex)
classType:=reflect.TypeOf(stu.Class)
testType:=reflect.TypeOf(stu.Test)

fmt.Println("nameType:",nameType)
fmt.Println("ageType:",ageType)
fmt.Println("highType:",highType)
fmt.Println("sexType:",sexType)
fmt.Println("classType:",classType)
fmt.Println("testType:",testType)
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
bash复制代码nameType: <nil>
ageType: <nil>
highType: <nil>
sexType: <nil>
classType: <nil>
testType: <nil>
--------------json 解析后-----------
nameType: string
ageType: float64
highType: bool
sexType: <nil>
classType: map[string]interface {}
testType: <nil>

从结果中可见

  • interface{}类型变量在json解析前,打印出的类型都为nil,就是没有具体类型,这是空接口(interface{}类型)的特点。
  • json解析后,json串中value,只要是”简单数据”,都会按照默认的类型赋值,如”张三”被赋值成string类型到Name变量中,数字18对应float64,true对应bool类型。

“简单数据”:是指不能再进行二次json解析的数据,如”name”:”张三”只能进行一次json解析。
“复合数据”:类似”CLASS\”:{\”naME\”:\”1班\”,\”GradE\”:3}这样的数据,是可进行二次甚至多次json解析的,因为它的value也是个可被解析的独立json。即第一次解析key为CLASS的value,第二次解析value中的key为naME和GradE的value

对于”复合数据”,如果接收体中配的项被声明为interface{}类型,go都会默认解析成map[string]interface{}类型。如果我们想直接解析到struct Class对象中,可以将接受体对应的项定义为该struct类型。如下所示:

1
2
3
4
5
6
7
go复制代码type StuRead struct {
...
//普通struct类型
Class Class `json:"class"`
//指针类型
Class *Class `json:"class"`
}

stu打印结果

1
2
bash复制代码Class类型:{张三 18 true <nil> {1班 3} <nil>}
*Class类型:{张三 18 true <nil> 0xc42008a0c0 <nil>}

可以看出,传递Class类型的指针时,stu中的Class变量存的是指针,我们可通过该指针直接访问所属的数据,如stu.Class.Name/stu.Class.Grade

如果不想指定Class变量为具体的类型,仍想保留interface{}类型,但又希望该变量可以解析到struct Class对象中,这时候该怎么办呢?

这种需求是很可能存在的

办法还是有的,我们可以将该变量定义为json.RawMessage类型

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
go复制代码type StuRead struct {
Name interface{}
Age interface{}
HIgh interface{}
Class json.RawMessage `json:"class"` //注意这里
}

type Class struct {
Name string
Grade int
}

func main() {
data:="{\"name\":\"张三\",\"Age\":18,\"high\":true,\"sex\":\"男\",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}"
str:=[]byte(data)
stu:=StuRead{}
_:=json.Unmarshal(str,&stu)

//注意这里:二次解析!
cla:=new(Class)
json.Unmarshal(stu.Class,cla)

fmt.Println("stu:",stu)
fmt.Println("string(stu.Class):",string(stu.Class))
fmt.Println("class:",cla)
printType(&stu) //函数实现前面例子有
}

结果

1
2
3
4
5
6
7
bash复制代码stu: {张三 18 true [123 34 110 97 77 69 34 58 34 49 231 143 173 34 44 34 71 114 97 100 69 34 58 51 125]}
string(stu.Class): {"naME":"1班","GradE":3}
class: &{1班 3}
nameType: string
ageType: float64
highType: bool
classType: json.RawMessage

从结果中可见

  • 接收体中,被声明为json.RawMessage类型的变量在json解析时,变量值仍保留json的原值,即未被自动解析为map[string]interface{}类型。如变量Class解析后的值为:{“naME”:”1班”,”GradE”:3}
  • 从打印的类型也可以看出,在第一次json解析时,变量Class的类型是json.RawMessage。此时,我们可以对该变量进行二次json解析,因为其值仍是个独立且可解析的完整json串。我们只需再定义一个新的接受体即可,如json.Unmarshal(stu.Class,cla)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Matplotlib控制坐标轴刻度间距和标签

发表于 2021-10-26

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

控制刻度间距

目前为止,我们让Matplotlib自动处理刻度在坐标轴上的位置,但有时我们需要覆盖默认的坐标轴刻度配置,以便更加快速估计图形中点的坐标。

1
2
3
4
5
6
7
8
9
10
python复制代码import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
x = np.linspace(-20, 20, 1024)
y = np.sinc(x)
ax = plt.axes()
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))
plt.plot(x, y, c = 'm')
plt.show()

控制刻度间距

以上代码,强制水平刻度每隔5个单位步长呈现一次。此外,我们还添加了副刻度,副刻度的间隔为1个单位步长,步骤说明如下:

  1. 首先实例化一个Axes对象——用于管理图形中的坐标轴:ax=plot.Axes()。
  2. 然后使用Locator实例设置x轴(ax.xaxis)或y轴(ax.yaxis)的主刻度和副刻度。

也为副刻度添加辅助网格:

1
2
3
4
5
6
7
8
9
10
11
python复制代码import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
x = np.linspace(-20, 20, 1024)
y = np.sinc(x)
ax = plt.axes()
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))
plt.grid(True, which='both', ls='dashed')
plt.plot(x, y, c = 'm')
plt.show()

为副刻度添加辅助网格

Tips:我们已经知道,可以使用plt.grid()添加辅助网格,但此函数还有一个可选参数which,它具有三个可选值:"minor"、"major"和"both",分别用于仅显示副刻度、仅显示主刻度、主副刻度同时显示。

控制刻度标签

是时候介绍刻度标签的设置了,刻度标签是图形空间中的坐标,虽然数字刻度标签对于大多说场景来说是足够的,但是却并不总是能够满足需求。例如,我们需要显示100个公司的营收情况,这时候我们就需要横坐标刻度标签为公司名,而非数字;同样对于时间序列,我们希望横坐标刻度标签为日期…。考虑到此类需求,我们需要使用Matplotlib为此提供了的API控制刻度标签。
可以按以下步骤为任何Matplotlib图形设置刻度标签:

1
2
3
4
5
6
7
8
9
10
11
python复制代码import numpy as np
import matplotlib.ticker as ticker
import matplotlib.pyplot as plt
name_list = ('Apple', 'Orange', 'Banana', 'Pear', 'Mango')
value_list = np.random.randint(0, 99, size = len(name_list))
pos_list = np.arange(len(name_list))
ax = plt.axes()
ax.xaxis.set_major_locator(ticker.FixedLocator((pos_list)))
ax.xaxis.set_major_formatter(ticker.FixedFormatter((name_list)))
plt.bar(pos_list, value_list, color = 'c', align = 'center')
plt.show()

控制刻度标签

Tips:我们首先使用ticker.Locator实例来生成刻度的位置,然后使用ticker.Formatter实例将为刻度生成标签。FixedFormatter从字符串列表中获取标签,然后用Formatter实例设置坐标轴。同时,我们还使用了FixedLocator来确保每个标签中心都正好与刻度中间对齐。

更简单的设置方式

虽然使用上述方法可以控制刻度标签,但可以看出此方法过于复杂,如果刻度标签是固定的字符列表,那么可以用以下简单的设置方法:

1
2
3
4
5
6
7
8
python复制代码import numpy as np
import matplotlib.pyplot as plt
name_list = ('Apple', 'Orange', 'Banana', 'Pear', 'Mango')
value_list = np.random.randint(0, 99, size = len(name_list))
pos_list = np.arange(len(name_list))
plt.bar(pos_list, value_list, color = 'c', align = 'center')
plt.xticks(pos_list, name_list)
plt.show()

简单的设定坐标刻度标签的方法

Tips:使用plt.xticks()函数为一组固定的刻度提供固定标签,此函数接受位置列表和名称列表作为参数值,可以看出,此方法比第一种方法实现起来更简单。

高级刻度标签控制

不仅可以使用固定标签,使用ticker API可以使用函数生成的标签:

1
2
3
4
5
6
7
8
9
10
11
python复制代码import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
def make_label(value, pos):
return '%0.1f%%' % (100. * value)
ax = plt.axes()
ax.xaxis.set_major_formatter(ticker.FuncFormatter(make_label))
x = np.linspace(0, 1, 256)
plt.plot(x, np.exp(-10 * x), c ='c')
plt.plot(x, np.exp(-5 * x), c= 'c', ls = '--')
plt.show()

高级刻度标签控制

在此示例中,刻度标签是由自定义函数make_label生成的。此函数以刻度的坐标作为输入,并返回一个字符串作为坐标标签,这比给出固定的字符串列表更灵活。为了使用自定义函数,需要使用FuncFormatter实例——一个以函数为参数的格式化实例。

这种将生成标签的实际任务指派给其他函数的方法称为委托(delegation)模式,这是一种漂亮的编程技术。比方说,我们要将每个刻度显示为日期,这可以使用标准的Python时间和日期函数完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码import numpy as np
import datetime
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
start_date = datetime.datetime(1998, 1, 1)
def make_label(value, pos):
time = start_date + datetime.timedelta(days = 365 * value)
return time.strftime('%b %y')
ax = plt.axes()
ax.xaxis.set_major_formatter(ticker.FuncFormatter(make_label))
x = np.linspace(0, 1, 256)
plt.plot(x, np.exp(-10 * x), c ='c')
plt.plot(x, np.exp(-5 * x), c= 'c', ls = '--')
labels = ax.get_xticklabels()
plt.setp(labels, rotation = 30.)
plt.show()

委托模式示例

Tips:可以利用ax.get_xticklabels()获取刻度标签实例,然后对标签进行旋转,以避免长标签之间重叠,旋转使用plt.setp()函数,其接受刻度标签实例和旋转角度作为参数值。

系列链接

Matplotlib常见统计图的绘制

Matplotlib使用自定义颜色绘制统计图

Matplotlib控制线条样式和线宽

Matplotlib自定义样式绘制精美统计图

Matplotlib在图形中添加文本说明

Matplotlib在图形中添加注释

Matplotlib在图形中添加辅助网格和辅助线

Matplotlib添加自定义形状

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…466467468…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%