下面以简要的代码说明:
创建用于测试的对象
包括:Controller,Service,Repo,Entity。
Entity: User
User里有大量随机字段(超过50个)
@Entity
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String sasdfex;
private String Seasddfgfx;
private String Se23asfx;
//省略的还有许多随机字段,目的让表结构变大。
//省略getter和setter
Repo: UserRepo
Repo故意用慢速的方法去查询,延长时间。
public interface UserRepo extends JpaRepository<User, Long> {
List<User> findBySeasddfgfxLikeAndSe23asfxLike(String Seasddfgfx, String se23asfx);//重点:用like加上多个"%"号,提高搜索难度。
}
Service: UserService
public class UserService {
@Autowired
private UserRepo userRepo;
public void concurrentVoid() {
this.doConcurrent();
}
private void doConcurrent() {
User user = null;
List<User> users = userRepo.findBySeasddfgfxLikeAndSe23asfxLike("%3df%gdsfg%", "%2%asfdasf%3a%");
if (users.isEmpty()) {
user = new User();
user.setName("zz");
user.setSeasddfgfx("3df%gdsfg");
user.setSe23asfx("%2%asfdasf%3a%");
}
userRepo.save(user);
}
}
Serivce做的事情,我们可以看到。一个public,一个private。
doConcurrent:先like查出所有满足条件的值。这个list没什么用,只是需要这个去延长方法执行时间。
如果查到的结果为empty,那么插入一条数据。数据的要求是:可以被Like搜索到。
Controller: UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/concurrentReq")
public String concurrentReq() {
userService.concurrentVoid();
return "success";
}
}
到这里骨架已经完成。
想办法,对user的数据表插入大量数据,这部分我就不详述了。
我采用的是mysql,数据量是5927009(接近600万)条。点用的空间大概是2G多。
select count(*) from user;
count(*) |
---|
5927009 |
这时候,就可以调用后端接口了:
server-port:8080
http://localhost:8080/user/concurrentReq
调用方法:
在浏览器里输入网址或者相关工具(PostMan?SoapUI?),手工点击。想要并发,你的手速可以吗?^-^
采用相关工具PostMan或者SoapUI,和浏览器类似。达不到我们想要的效果。
写java代码,模拟http请求。想要模拟并发,我们需要使用多线程。这里我采用线程池加CountDownLatcher。
编写测试用例
Test: UserTests
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleWebUiApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:8080")
@DirtiesContext
public class UserTests {
@Test
public void testUserConcurrentReq() throws Exception {
ExecutorService service = Executors.newCachedThreadPool(); // 创建一个线程池
final CountDownLatch cdlForNotification = new CountDownLatch(1);// 当cdlForNotification变为0,则唤醒所有等待唤醒的并发线程。
final CountDownLatch cdlForConThread = new CountDownLatch(10);// 准备用10个并发线程,所以设置为10。每个线程运行完成则countDown一次,当10个都运行完成,变为0。则cdlForNotification停止等待。
for (int i = 0; i < 10; i++) {
Runnable runnable = new Runnable() {
public void run() {
try {
System.out.println("线程" + Thread.currentThread().getName() + "准备完成");
cdlForNotification.await(); // 各线程都处于等待状态
System.out.println("线程" + Thread.currentThread().getName() + "开始运行");
new TestRestTemplate().getForEntity("http://localhost:" + 8080 + "/user/concurrentReq", String.class);
System.out.println("线程" + Thread.currentThread().getName() + "运行完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
cdlForConThread.countDown(); // 任务运行完成,cdlForConThread减1。
}
}
};
service.execute(runnable);// 为线程池加入任务
}
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将唤醒所有并发线程");
cdlForNotification.countDown(); // 发送命令,cdlForNotification减1,处于等待的线程停止等待转去运行任务。
System.out.println("线程" + Thread.currentThread().getName() + "唤醒所有并发线程,正在等待结果");
cdlForConThread.await(); // 唤醒并发线程后处于等待状态。一旦cdlForConThread为0时停止等待,继续往下运行
System.out.println("线程" + Thread.currentThread().getName() + "已收到全部响应结果");
} catch (Exception e) {
e.printStackTrace();
} finally {
}
service.shutdown(); // 任务结束。停止线程池的全部线程
}
}
这是一个并发请求的测试用例。还是要注意这里的用法。
其用意是,创建10个线程,用cdlForConThread(CountDownLatch)来控制线程的启动,阻塞,继续执行。
主线程用cdlForNotification控制他们的继续执行。
启动测试用例
步骤:
- 直接运行
- public 加上synchronized
- private 加上synchronized
- public和private 同时加上 synchronized
- public 加上 @Transactional
- private 加上 @Transactional
- public和private 同时加上 @Transactional
问题:
1. 只用synchronized行不行?
2. 为什么@Transactional在私有方法上不起作用?
如果有人看到这里,可以讨论下。