设计模式
单例模式
Java单例模式是一种常用的设计模式,它可以保证一个类只有一个实例,并且提供一个全局访问点。在Java中,可以通过以下两种方式来实现单例模式:
饿汉模式
在类加载时就创建实例,并且提供一个公共的访问点,保证整个系统中只有一个实例。例实例在类加载时就已经创建好了,因此不存在多线程下的线程安全问题。示例代码如下:
public class Singleton {
// 在类加载时就创建实例
private static final Singleton instance = new Singleton();
// 私有化构造方法,避免外部实例化
private Singleton() {}
// 提供公共的访问方法
public static Singleton getInstance() {
return instance;
}
}
饿汉模式的优点是实现简单,不存在线程安全问题,而且在访问单例对象时不需要进行同步操作。但是饿汉模式的缺点是会在类加载时就创建实例,这样会增加系统的负担,特别是当单例对象比较大时,会影响系统的性能。
懒汉模式
在懒汉模式中,单例实例只有在第一次使用时才被创建,这样可以避免在系统启动时就创建实例,从而减轻系统的负担。示例代码如下:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
// 对getInstance方法进行同步操作,确保线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
从而减轻系统的负担,但是它也存在线程安全问题。因为在多线程情况下,当多个线程同时调用getInstance()方法时,可能会创建多个实例。为了解决这个问题,可以使用双重校验锁来实现线程安全的懒汉模式,或者使用静态内部类的方式来实现单例模式。
多线程下面怎么保证单例模式的安全性
在多线程环境下,单例模式的实现可能会存在线程安全问题,例如多个线程同时调用getInstance()方法可能会创建多个实例。为了保证多线程安全,可以使用以下几种方式来实现单例模式:
饿汉模式:
在饿汉模式中,单例实例在类加载时就已经创建好了,因此不存在多线程下的线程安全问题。
懒汉模式(使用同步锁):
在懒汉模式中,单例实例只有在第一次使用时才被创建,为了保证线程安全,可以在getInstance()方法中使用同步锁来避免多个线程同时创建实例。示例代码如下:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式(双重校验锁):
使用双重校验锁来避免每次都对getInstance()方法进行同步锁操作,从而提高系统的性能。
为了避免每次都对getInstance()方法进行同步锁操作,可以使用双重校验锁来实现懒汉模式的线程安全。示例代码如下:
public class Singleton {
// 在这种方式中,使用了volatile关键字来确保每个线程都能够正确地读取实例,而使用双重校验锁来避免每次都对getInstance()方法进行同步锁操作,从而提高系统的性能。
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类方式:
使用静态内部类的方式也可以保证单例模式的线程安全,因为静态内部类只会在被调用时才会被加载,而且加载时只会被加载一次。示例代码如下:
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
在这种方式中,SingletonHolder是一个静态内部类,当getInstance()方法被调用时,才会加载SingletonHolder类,并创建Singleton实例。由于静态内部类只会被加载一次,因此可以保证线程安全。
java 多线程
多线程demo
public class MultiThreadDemo {
public static void main(String[] args) {
Thread oddThread = new Thread(new OddPrinter());
Thread evenThread = new Thread(new EvenPrinter());
oddThread.start();
evenThread.start();
}
}
class OddPrinter implements Runnable {
public void run() {
for (int i = 1; i <= 10; i += 2) {
System.out.println("Odd: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class EvenPrinter implements Runnable {
public void run() {
for (int i = 2; i <= 10; i += 2) {
System.out.println("Even: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面的示例创建了两个线程,一个是OddPrinter,负责打印奇数,另一个是EvenPrinter,负责打印偶数。每个线程都是通过实现Runnable接口来创建的。在每个线程的run()方法中,使用一个循环来打印奇数或偶数,并使用Thread.sleep()方法来暂停1秒钟,以便我们可以看到两个线程交替打印的效果。
注意,由于多线程并发访问共享资源时可能会导致竞态条件问题,因此在上面的示例中,我们没有使用共享资源。如果需要在多个线程之间共享数据,需要使用同步锁等机制来保证线程安全。
同步锁/异步锁
同步锁是Java中一种用于解决多线程并发访问的机制。当多个线程同时访问共享资源时,可能会导致竞态条件(race condition)的问题,即多个线程争抢同一资源导致结果不可预期。
为了解决这个问题,Java提供了同步锁(也叫监视器锁,monitor),它可以保证同一时间只有一个线程能够访问共享资源,其他线程需要等待当前线程释放锁之后才能继续访问。
Java中的同步锁可以使用synchronized关键字来实现。当一个线程获取到锁时,其他线程就会被阻塞,直到该线程释放锁为止。使用同步锁可以有效地避免多线程并发访问共享资源的问题,从而保证程序的正确性和稳定性。
需要注意的是,在使用同步锁时,需要考虑死锁(deadlock)的问题,即当多个线程互相持有对方所需要的资源时,就会发生死锁。因此,在使用同步锁时,需要合理设计代码,避免出现死锁问题。
死锁:例如,线程A持有资源X,线程B持有资源Y,线程A想要获得资源Y,线程B想要获得资源X,但是由于双方都不释放自己持有的资源,导致两个线程都无法继续执行下去,形成死锁。
异步锁
线程池
线程池中的线程是重复利用的,主要有以下几个步骤:
当线程池中的线程空闲时,线程池并不会立刻将线程销毁,而是将其标记为“可用线程”,等待下一次任务的到来。
当新的任务到来时,线程池会先查看是否有空闲线程可用,如果有,则将任务分配给可用线程,并将该线程标记为“忙碌状态”,开始执行任务。
如果没有空闲线程可用,则根据线程池的配置情况,决定是否创建新的线程来执行任务。
当任务执行完毕后,线程池会将线程标记为“可用线程”,并返回线程池,等待下一次任务的到来。
这样做的好处在于,避免了频繁创建和销毁线程的开销,提高了程序性能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个线程池,大小为5
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交10个任务给线程池执行
for (int i = 1; i <= 10; i++) {
executor.submit(new Task(i));
}
// 关闭线程池
executor.shutdown();
}
}
class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " is running.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " is done.");
}
}
这个demo创建了一个大小为5的线程池,然后提交了10个任务给线程池执行。每个任务打印出任务编号,并在执行完后打印出“任务编号完成”的信息。
Task 1 is running.
Task 2 is running.
Task 3 is running.
Task 4 is running.
Task 5 is running.
Task 1 is done.
Task 6 is running.
Task 2 is done.
Task 7 is running.
Task 8 is running.
Task 9 is running.
Task 10 is running.
Task 5 is done.
Task 6 is done.
Task 3 is done.
Task 4 is done.
Task 7 is done.
Task 8 is done.
Task 9 is done.
Task 10 is done.
我们可以看到,线程池按顺序执行了5个任务,然后又执行了5个任务,直到所有任务都执行完毕。这样做可以避免频繁地创建和销毁线程,提高了程序的性能和效率。
java 序列化
Java 序列化是将 Java 对象转化为字节序列(byte stream)的过程,可以用于在网络上传输对象或者将对象存储在本地磁盘上。序列化可以使得一个对象在不同的JVM、操作系统和网络环境中进行传输和交互,同时也是Java RMI、JMS等技术的基础。序列化后的字节序列可以在需要时进行反序列化,将其还原为原来的 Java 对象。Java 序列化可以通过实现 Serializable 接口和 Externalizable 接口来实现。
Spring 多种序列化方式
1.Java原生序列化(Java Serialization):
Java原生序列化是一种Java标准库提供的序列化技术,可以将Java对象转换为字节流进行传输和存储。可以通过实现java.io.Serializable接口来实现序列化,但是Java原生序列化存在一些性能和兼容性的问题。
2.JSON序列化:
JSON是一种轻量级的数据交换格式,可以将Java对象序列化为JSON字符串,通过HTTP请求进行传输和存储。在Java中可以使用Gson、Jackson等第三方库实现JSON序列化。
3.XML序列化:
XML也是一种常用的数据交换格式,可以将Java对象序列化为XML格式进行传输和存储。在Java中可以使用JAXB等工具实现XML序列化。
4.Protocol Buffers序列化:
Protocol Buffers是一种高效的数据交换格式,由Google开发,可以将结构化数据序列化为二进制格式,具有高效、可扩展、跨语言等优点。在Java中可以使用Google提供的protobuf-java库实现Protocol Buffers序列化。
5.Kryo序列化:
Kryo是一种高效的Java序列化框架,可以将Java对象序列化为二进制格式进行传输和存储。Kryo序列化速度较快,序列化后的数据体积也较小,适合用于需要高性能和低网络带宽的场景。
以上几种序列化方式各有优缺点,需要根据具体的业务需求和应用场景选择合适的序列化方式。例如,如果需要跨平台传输数据,则可以选择使用JSON或XML序列化;如果需要高效传输大量数据,则可以选择使用Protocol Buffers或Kryo序列化。
spring自动序列化json对象?
spring boot 如何自动将对象自动序列化成成json格式
在 Spring Boot 中,自动将对象返回成 JSON 格式,通常需要通过使用 Spring Boot 自带的 Spring MVC 框架,并借助内置的Jackson 或者 Gson 等 JSON 处理器实现。具体步骤如下:
1. 编写一个 REST 接口方法,并使用 @RestController 注解进行标注。例如:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/{userId}")
public User getUserById(@PathVariable String userId) {
User user = new User();
user.setId(userId);
user.setName("张三");
user.setAge(18);
return user;
}
}
1. 在 Spring Boot 配置文件 application.yml 或 application.properties 中,配置 Jackson 或 Gson 在转换时的相关参数,例如:
# 使用 jackson
spring.jackson.date-format: yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone: GMT+8
spring.jackson.default-property-inclusion: non_null
# 使用 gson
spring.http.converters.preferred-json-mapper: gson
spring.gson.date-format: yyyy-MM-dd HH:mm:ss
spring.gson.time-zone: GMT+8
通过以上操作,就可以在使用 Spring Boot 自带的 Spring MVC 进行开发时,自动将返回的对象转换为 JSON 格式了。注意:使用 Jackson 时,Spring Boot 默认已经集成了 Jackson,而使用 Gson 时,需要手动添加依赖,并在配置文件中配置使用 Gson。
Java 默认序列化 与 JSON 序列化 的区别 ?
Java 默认序列化和 JSON 序列化都是用于将 Java 对象转换为可存储或传输的数据格式。它们的主要区别如下:
1. 格式:Java 默认序列化采用二进制格式,而 JSON 序列化采用文本格式。
2. 可读性:Java 默认序列化的结果是二进制流,不易阅读,而 JSON 序列化的结果是文本格式,易于阅读、调试和理解。
3. 性能:Java 默认序列化在序列化和反序列化时需要额外的 CPU 和内存开销,而 JSON 序列化性能较好,可以通过高效的序列化和反序列化算法提高速度和效率。
4. 版本兼容性:Java 默认序列化对类的结构和版本变化敏感,同一对象在不同版本间进行序列化和反序列化时容易出现兼容性问题。而 JSON 序列化不依赖于类的版本和结构,所以更具有兼容性和灵活性。
JSON 序列化 映射注解
在进行 JSON 序列化时,可以使用 Jackson 库提供的注解来控制属性的序列化方式,常见的注解如下:
1. @JsonIgnore:标记在属性或方法上,表示在序列化或反序列化时忽略此属性或方法。
2. @JsonProperty:标记在属性或方法上,表示将属性或方法名序列化为指定的 JSON 键值对的键名,或者将 JSON 中的键名映射为对象属性或方法。
这个注解与fastjson中@JSONField 注解类似 都是将json字段映射到对象上。
3. @JsonFormat:标记在属性或方法上,用于指定日期时间格式,在序列化或反序列化时将日期时间转换为指定格式。
4. @JsonInclude:标记在类或属性上,用于控制序列化时是否包含空值或默认值。
5. @JsonCreator:标记在构造方法上,用于反序列化时从 JSON 对象中创建 Java 对象。
6. @JsonGetter/@JsonSetter:标记在 Getter/Setter 方法上,用于与 @JsonProperty 注解一起使用,控制 Getter/Setter 方法在序列化和反序列化时的行为。
下面是一个示例:
public class User {
private Long id;
@JsonProperty("name")
private String username;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
public User() {}
@JsonCreator
public User(@JsonProperty("id") Long id,
@JsonProperty("name") String username,
@JsonProperty("createTime") Date createTime) {
this.id = id;
this.username = username;
this.createTime = createTime;
}
@JsonGetter("id")
public Long getId() {
return id;
}
@JsonSetter("id")
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
在上面的示例中,我们使用了多个注解来完成以下操作:
1. @JsonProperty(“name”):将 username 属性序列化为 key 为 “name” 的键值对。
2. @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = “yyyy-MM-dd HH:mm:ss”):将 createTime 属性序列化为指定格式的日期时间字符串。
3. @JsonCreator、@JsonProperty:用于反序列化,从 JSON 对象中创建 User 对象。
4. @JsonGetter、@JsonSetter:与 @JsonProperty 一起使用,用于控制序列化和反序列化时的属性映射。