并发与并行
- 并发:并发是指两个或多个任务在同一时间间隔内发生,比如在4核CPU上运行100个线程,由于核数限制,这100个线程无法在同一时刻运行,所以CPU只能采用时间片切换的方式来运行,如果这100个线程能够在1s内全部处理完成,那么我们可以认为当前的并发数为100.
- 并行:当有多个CPU核心时,在同一个时刻可以同时运行多个任务,这种方式叫并行。比如,4核CPU可以同时运行4个线程。
在linux系统中可以通过命令ulimit -n查看,假如得到的结果是1024,那么该进程能够并行处理的连接数就是1024。
java中使用多线程
- 实现Runnable接口创建线程
/**
* @author ZhangHao
* @since 1.0.0
*/
@Slf4j
public class RunnableThreadExample implements Runnable{
@Override
public void run() {
log.info("RunnableThreadExample.run");
}
public static void main(String[] args){
Thread thread = new Thread(new RunnableThreadExample());
thread.start();
}
}
- 继承Thread类创建线程
/**
* @author ZhangHao
* @since 1.0.0
*/
@Slf4j
public class ThreadExample extends Thread{
@Override
public void run() {
log.info("ThreadExample.run");
}
public static void main(String[] args){
Thread thread = new ThreadExample();
thread.start();
}
}
- 实现Callable接口并创建带返回值的线程
/**
* @author ZhangHao
* @since 1.0.0
*/
@Slf4j
public class CallableExample implements Callable<String> {
@Override
public String call() throws Exception {
return "执行结果成功";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableExample callableExample = new CallableExample();
FutureTask<String> futureTask = new FutureTask<>(callableExample);
Thread thread = new Thread(futureTask);
thread.start();
log.info("result:{}", futureTask.get());
}
}
Java多线程应用的实际场景
- 网络请求分发场景:针对每个请求连接,可以通过分配一个线程去处理,从而提升服务端处理的连接数,比如Tomcat就是采用线程来处理客户端请求的。
- 文件导入场景:当有较大的文件导入时,我们可以先对文件进行解析,然后以每10000条数据作为一个任务给线程来处理,这样可以提升文件处理的效率。
- 异步业务场景:如支付场景中,客户端发起支付请求,服务端收到支付请求后,先不直接调用渠道进行支付,而是先返回给用户一个处理成功的结果,在通过异步线程的方式触发这个支付请求。
- 在BIO(阻塞I/O)模型中,服务端基于ServerSocket.accept()方法来接受客户端的请求,当客户端发起一次请求后,在等待服务端返回执行结果前,ServerSocket无法处理其他的请求。换句话说,服务端同一时刻只能处理一个请求,在多用户访问的系统中,显然会产生不好的用户体验。
- 使用多线程的方式来实现,那么服务端可以针对每个请求,分配一个专门的线程去处理,然后服务端继续接受下一个请求。通过这种方式的优化可以大大提升服务端同时处理的客户端请求数量。具体实现如下。
/**
* @author ZhangHao
* @since 1.0.0
*/
public class ServerSocketExample {
public static void main(String[] args) throws IOException {
final int DEFAULT_PORT = 8080;
ServerSocket serverSocket = null;
serverSocket = new ServerSocket(DEFAULT_PORT);
System.out.println("启动服务,监听端口:" + DEFAULT_PORT);
while (true){
Socket socket = serverSocket.accept();
//针对每个请求,分配一个线程来处理,提升服务端的处理效率,从而能够处理更多的客户端请求
new Thread(new SocketThread(socket)).start();
}
}
}
/**
* @author ZhangHao
* @since 1.0.0
*/
public class SocketThread implements Runnable{
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
System.out.println("客户端:" + socket.getPort() + "已连接");
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String clientStr = null;
clientStr = bufferedReader.readLine();
System.out.println("客户端发了一条消息:" + clientStr);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("我已经收到了你的消息\n");
bufferedWriter.flush(); // 清空缓冲区,触发消息发送
}catch (Exception e){
e.printStackTrace();
}
}
}
- ServerSocket定义了一个监听端口8080,当客户端访问8080时,服务端就会接收请求。
- Server端接收用户请求是采用accept()方法来实现的,当收到一个客户端请求时,会返回一个Socket实例。
- 当Server端收到请求后,采用Thread异步执行请求,由于线程具有非阻塞性,因此可以快速进入第二次循环,继续监听客户端请求。
多线程的基本原理
- 当执行start()方法启动线程时,会先在JVM层创建一个线程,JVM具有跨平台特性,它会根据当前操作系统的类型调用相关指令来创建线程并启动。
- 当线程启动后,并不会立刻运行,而是要等到操作系统层面的CPU调度算法,把当前线程分配给某个cpu来执行。线程被分配执行后,会调线程中的run()方法执行相关指令。
线程从创建到最终执行的整体流程
线程的运行状态(Java中的线程一共有6个状态)
- NEW,新建状态,也就是new Thread()时的状态。
- RUNNABLE,运行状态,通过start()方法启动线程后的状态。
- BLOCKED,阻塞状态,当线程执行synchronized代码,并且未抢占到锁时,会变成该状态。
- WAITING,调用Object.wait()等方法,会让线程变为该状态。
- TIMED_WAITING,超时等待状态,如sleep(timeout),超时后会自动唤醒。
- TERMINATED,终止状态,线程的run()方法中的指令执行完毕后的状态。
Jstack工具
-
打开终端命名,输入“jps”(显示当前所有java进程pid)
-
根据获取的pid使用jstack -l pid 命令打印指定进程的dump信息。
TIMED_WAITING状态
/**
* 超时等待状态(通过sleep()方法阻塞的线程进入了TIMED_WAITING状态)
* @author ZhangHao
* @since 1.0.0
*/
public class TimedWaitingStatusExample {
public static void main(String[] args){
new Thread(() ->{
try {
TimeUnit.SECONDS.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "TIME_WAITING").start();
}
}
//dump信息
"TIME_WAITING" #12 prio=5 os_prio=0 tid=0x000000001f552000 nid=0x220c waiting on condition [0x000000001fe1f000]
java.lang.Thread.State: TIMED_WAITING (sleeping) //线程状态
at java.lang.Thread.sleep(Native Method) //导致该状态的方法
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at TimedWaitingStatusExample.lambda$main$0(TimedWaitingStatusExample.java:11)
at TimedWaitingStatusExample$$Lambda$1/990368553.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
WAITING状态
/**
* 通过Object.wait()方法进入WAITING状态
* @author ZhangHao
* @since 1.0.0
*/
public class WaitingStatusExample {
public static void main(String[] args){
new Thread(()->{
synchronized (WaitingStatusExample.class){
try {
WaitingStatusExample.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "WAITING").start();
}
}
//dump信息
"WAITING" #12 prio=5 os_prio=0 tid=0x000000001f063800 nid=0x1390 in Object.wait() [0x000000001f92f000]
java.lang.Thread.State: WAITING (on object monitor) //线程状态
at java.lang.Object.wait(Native Method) //导致该状态的方法
- waiting on <0x000000076be7da30> (a java.lang.Class for WaitingStatusExample)
at java.lang.Object.wait(Object.java:502)
at WaitingStatusExample.lambda$main$0(WaitingStatusExample.java:11)
- locked <0x000000076be7da30> (a java.lang.Class for WaitingStatusExample)
at WaitingStatusExample$$Lambda$1/990368553.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
BLOCKED状态
/**
* BLOCKED状态只有在synchronized锁阻塞时存在。
* @author ZhangHao
* @since 1.0.0
*/
public class BLOCKEDStatusExample implements Runnable{
@Override
public void run() {
synchronized (BLOCKEDStatusExample.class){
while (true){}
}
}
public static void main(String[] args) {
new Thread(new BLOCKEDStatusExample(), "BLOCKED_T1").start();
new Thread(new BLOCKEDStatusExample(), "BLOCKED_T2").start();
}
}
//dump信息
//互相抢占锁造成的阻塞状态
"BLOCKED_T2" #13 prio=5 os_prio=0 tid=0x000000001e61d800 nid=0x3148 waiting for monitor entry [0x000000001f4cf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at BLOCKEDStatusExample.run(BLOCKEDStatusExample.java:10)
- waiting to lock <0x000000076be7d690> (a java.lang.Class for BLOCKEDStatusExample)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"BLOCKED_T1" #12 prio=5 os_prio=0 tid=0x000000001e61c800 nid=0x3698 runnable [0x000000001f3cf000]
java.lang.Thread.State: RUNNABLE
at BLOCKEDStatusExample.run(BLOCKEDStatusExample.java:10)
- locked <0x000000076be7d690> (a java.lang.Class for BLOCKEDStatusExample)
at java.lang.Thread.run(Thread.java:748)
线程运行状态流转图