以下的示例是 hashmap在三个线程的并发下进行添加操作
import java.util.HashMap;
import java.util.Map;
public class Main_1 {
private static Map sharedMap = new HashMap<>();
public static void main(String[] args) {
Thread writer1 = new Thread(() -> addToMap("key1", "value1"));
Thread writer2 = new Thread(() -> addToMap("key2", "value2"));
Thread writer3 = new Thread(() -> addToMap("key3", "value3"));
writer1.start();
writer2.start();
writer3.start();
try {
writer1.join();
writer2.join();
writer3.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(sharedMap);
}
private static void addToMap(String key, String value) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
sharedMap.put(key, value);
System.out.println(key + " -> " + value);
}
}
结论:通过多次运行得到结果,发现hashmap是线程不安全的。如何解决?第一:直接对hashmap的操作方法进行上锁。第二:可以使用map中的ConCurrentMap,它是支持并发操作的。
下面是对hashmap上锁的两种方法:1.直接使用synchronized对hashmap的各个操作修饰;
2.Collections.synchronizedMap(map参数)返回一个线程安全的hashmap
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Main_1 {
private static Map sharedMap = new HashMap<>();
public static void main(String[] args) {
//写一个线程安全的hashmap
//......测试代码
}
//下面是简单的操作方法,直接包装一层synchronized
//增
public synchronized boolean add(String key, String value){
sharedMap.put(key, value);
return true;
}
//删
public synchronized boolean remove(String key){
sharedMap.remove(key);
return true;
}
//改
public synchronized boolean update(String key,String value){
sharedMap.replace(key,value);
return true;
}
//查
public synchronized String find(String key){
return sharedMap.get(key);
}
}
堆:存储各种共享资源,包括:大部分常量(字符串常量池等)、各种数据结构、以及对象信息(实例)【实例的创建需要动态分配内存】 栈:存储各种临时资源,包括:方法调用时的参数、方法调用时创建栈帧包含方法的各种信息(纠正说法:方法调用时会加载到栈中,有点笼统了)、程序进行时的临时变量、进行计算时的中间值等、局部变量和常量 方法区:存储和类相关的资源(静态资源),包括:静态方法、静态变量、静态常量等 共享:栈是独有的,堆和方法区是共享的
//同步代码块
public class Main_1 {
private static int n = 1; // 用于控制打印的计数器
private static final int count = 100; // 循环次数
private static final Object lock = new Object(); // 共享锁对象
public static void main(String[] args) {
Thread thread1 = new Thread_fjw(1, "f");
Thread thread2 = new Thread_fjw(2, "j");
Thread thread3 = new Thread_fjw(0, "w");
thread1.start();
thread2.start();
thread3.start();
}
static class Thread_fjw extends Thread {
private int turn; //标志
public Thread_fjw(int turn, String name) {
this.turn = turn;
this.setName(name); // 设置线程名
}
@Override
public void run() {
while (n <= count) {
synchronized (lock) {
while (n % 3 != turn) {
try {
lock.wait(); // 等待直到当前线程可以打印
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.print(Thread.currentThread().getName()); // 打印当前线程名,代表字符
n++; // 正常递增计数器
lock.notifyAll(); // 唤醒其他等待的线程
}
}
}
}
}
//解释:这里的lock,其实并不是锁对象,知识一个唯一性对象而已。用唯一性对象调用方法,其实是让当前线程 保持或释放对当前唯一性资源的持有
//方法锁:直接破环掉多线程,没有实际意义,但是可以完成要求:将整个多线程变成一个串行的程序:
//我本来的写法:
public class Main_1 {
public static void main(String[] args) {
final int j = 100;
final Object lock = new Object();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < j; i++) {
synchronized (lock) {
if (i % 3 == 1) {
System.out.print("f");
}
lock.notifyAll(); // 唤醒其他等待的线程
}
try {
Thread.sleep(5); // 控制打印速度
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < j; i++) {
synchronized (lock) {
if (i % 3 == 2) {
System.out.print("j");
}
lock.notifyAll();
}
try {
Thread.sleep(5); // 控制打印速度
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Thread thread3 = new Thread(() -> {
for (int i = 0; i < j; i++) {
synchronized (lock) {
if (i % 3 == 0) {
System.out.print("w");
}
lock.notifyAll();
}
try {
Thread.sleep(5); // 控制打印速度
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
//说明和条件变量 + 锁的不同:
//说明和同步代码块的不同:同步代码块是不满足条件时,线程挂起,等待满足条件时输出;而我的代码是不满足条件直接结束线程,等待下一次抢锁。
//锁 ReentrantLock + Condition
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Main_1 {
private static int n = 1;//计数器
private static final int count = 100; // 循环次数
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();//lock创建通信对象
public static void main(String[] args) {
Thread thread1 = new Thread_fjw(1, "f");
Thread thread2 = new Thread_fjw(2, "j");
Thread thread3 = new Thread_fjw(0, "w");
thread1.start();
thread2.start();
thread3.start();
}
static class Thread_fjw extends Thread {
private int turn; // 每个线程应该打印的字符的顺序
public Thread_fjw(int turn, String name) {
this.turn = turn;
this.setName(name); // 设置线程名
}
@Override
public void run() {
lock.lock();//进入run方法,获取锁
try {
for (int i = 1;i<=count;i++) {
while (n%3!=turn){
condition.await();//这里条件不满足就阻塞线程并释放锁
}
System.out.print(Thread.currentThread().getName());
n++;
condition.signalAll();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
}
基于springboot的简单web应用:实现查询所有用户;实现基于路径接参的表单提交,参数为name、password,自动存储id和存储时间
依赖
4.0.0 org.springframework.boot spring-boot-starter-parent 3.2.8 com.yym demo_test_02 0.0.1-SNAPSHOT demo_test_02 demo_test_02 21 org.springframework.boot spring-boot-starter org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test com.alibaba druid-spring-boot-3-starter 1.2.20 mysql mysql-connector-java 8.0.28 com.baomidou mybatis-plus-spring-boot3-starter 3.5.5 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
配置:数据库 + 数据源 + 端口
#数据库配置 spring: datasource: druid: url: jdbc:mysql://localhost:3306/newtest username: root password: mysql123 driver-class-name: com.mysql.cj.jdbc.Driver #mybatis-plus配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl #端口号 server: port: 8888
实体类
package com.yym.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 准备实体类
*/
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String password;
private String time;
}
mapper接口:直接继承basemapper
package com.yym.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yym.pojo.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper {
}
简单的web应用:直接用controller调用,省去service
package com.yym.controller;
import com.yym.mapper.UserMapper;
import com.yym.pojo.User;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/test")
public class UserController {
@Resource
UserMapper userMapper;
@PostMapping("/insert/{name}/{password}")
public void testInsert(@PathVariable("name") String name, @PathVariable("password") String password) {
User user = new User();
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
user.setTime(simpleDateFormat.format(date));
user.setName(name);
user.setPassword(password);
userMapper.insert(user);
System.out.println("添加成功");
}
@GetMapping("/getAll")
public List getAll() {
List users = userMapper.selectList(null);
return users;
}
}
springboot启动类:一定要扫描mapper
package com.yym;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.yym.mapper")
@SpringBootApplication
public class DemoTest02Application {
public static void main(String[] args) {
SpringApplication.run(DemoTest02Application.class, args);
}
}