一篇搞懂Java多线程运行机制
Posted 墨辰JC
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇搞懂Java多线程运行机制相关的知识,希望对你有一定的参考价值。
文章目录
前言
Java是一种支持多线程编程的语言。多线程可以让程序同时执行多个任务,从而提高程序的效率和响应速度。在本篇博客中,我将介绍Java多线程的基础知识,包括线程的创建、启动、中断以及线程同步等方面。
什么是程序?
程序是为完成特定任务,用某种语言编程写的一组指令的集合。一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息化工具(简单来说就是我们写的代码)
什么是进程?
进程是指运行中的程序,比如我们使用的QQ,启动qq.exe可执行程序就启动了一个线程,操作系统就会为进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
进程是程序的一次执行过程,或正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。具体关系如图所示:
打开电脑的任务管理器,即可查看电脑运行的进程
进程共有三种状态:就绪、阻塞和运行
- 就绪态:就绪状态是指程序已达到可以运行的状态,只等CPU分配资源就可以运行的状态。
- 阻塞态:当程序运行条件没有满足时,需要等待条件满足时候才能执行时所处的状态,如等待i/o操作时候,此刻的状态就叫阻塞态。
- 运行态:进程占用CPU,并在CPU上运行。即程序正在运行时所处的状态。
线程
线程是由进程创建的,是一个进程的实体,一个进程可以有多个线程。比如:百度网盘的下载任务,一个下载任务对应一个线程,当同时下载多个任务时,相当于开启了多个线程,线程分为单线程和多线程。
- 单线程:同一时刻,只允许执行一个线程
- 多线程:同一时刻,可以执行多个线程
在Java中线程使用有两种方式
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
并发和并行
早期计算机的 CPU 都是单核的,一个 CPU 在同一时间只能执行一个进程/线程,当系统中有多个进程/线程等待执行时,CPU 只能执行完一个再执行下一个。并发和并行是用来操作线程和进程的方式。
并发
同一时刻,多个任务交替执行,造成一种同时的错觉,并不是正在同时运行。(单核CPU实现的多任务就是并发)
并行
同一时刻,多个任务同时执行,多核CPU可以实现并行。
注意:也可能会有并发和并行同时存在
Runnable接口
一个实现Runnable接口的类就是一个线程,通过Thread类启动。需要把实现类对象传入Thread类的构造方法中 然后通过Thread的start方法启动该Runnable实现类的线程。(因为Java是单继承机制,所以当有某个类已经继承了某个父类时,则需要用到Runnable接口)
该接口中只有一个**run()**方法,所以需要传入Thread类的构造器中进行启动
Thread类
Java语言是支持多线程的,一个正在运行的Java程序可以称之为一个进程(process),在每个进程里面包含多个线程.一个Thread实例对象,就是一个线程。Thread类继承于Runnable接口
常用方法
- getName():获取当前线程的名称,默认线程名称是Thread-索引
- setName(String name):将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称
- sleep(long time):让当前线程休眠指定的时间后再继续执行,单位是毫秒。
- run():线程任务方法(线程需要执行的业务内容)
- start():开启当前线程
- currentThread():获取当前执行线程的引用对象
- setPriority(int newPriority) :更改线程的优先级
- getPriority():获取线程的优先级
- interrupt():中断线程
- yield():给调度程序的一个提示,当前线程愿意得到它当前的处理器的使用(线程的礼让)
- join():线程的插队
用来控制线程优先级的范围常用的有三种:
MAX_PRIORITY | 线程可以拥有的最大优先级(10) |
---|---|
MIN_PRIORITY | 线程可以拥有的最小优先级(1) |
NORM_PRIORITY | 被分配给线程的默认优先级(5) |
注意:Thread类本身没用run方法的,而是继承Runnable接口重写得来的
使用start方法,会自动开启子线程,调用run方法。这里使用了设计模式(代理模式)
线程的创建
在Java中,可以通过继承Thread类或实现Runnable接口来创建一个线程。继承Thread类需要重写run()方法,而实现Runnable接口需要实现run()方法。下面是两种创建线程的示例代码:
- 继承Thread类的方式:
public class MyThread extends Thread
@Override
public void run()
// 线程执行的代码
MyThread myThread = new MyThread();
myThread.start(); // 启动线程
- 实现Runnable接口的方式:
public class MyRunnable implements Runnable
@Override
public void run()
// 线程执行的代码
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // 启动线程
原理和注意事项
当执行执行代码程序时(run),会开启一个进程。之后会进入到主方法main中,会开启一个主线程(main线程)
可在控制台使用 jconsole指令监控线程执行情况,如果出现 不是内部或外部命令,也不是可运行的程序或批处理文件 。则需要检查jdk环境配置(建议手动配置)
注意:执行时需要开启需要监控的线程,才能进行查看
- 当主线程执行完毕后,子线程未执行完,则继续执行,执行完毕所有线程后,程序进程退出
- 当程序进程结束,所有线程直接结束
为什么不直接调用run方法?
xx.run(); xx.start();
为什么不在主方法中直接调用run,而是通过start方法开启线程
如果使用xx.run()方法,则是主线程直接调用run方法,而此时线程的名称是main(此时的run方法就是一个普通的方法,并没有真正的启动子线程,会阻塞这里,等待run方法执行完毕,才会继续执行main主方法中代码);而使用xx.start()方法,则是通过子线程调用run方法(底层是JVM调用的,相当于开启了一个子线程)
start方法调用后,该线程并不一定会立刻马上执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度。
线程终止和中断
线程终止的两种情况:
- 当线程完成任务后,会自动退出
- 通过使用变量来控制run方法退出的方式停止线程,即通知方式
中断
interrupt方法,用来中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠的线程,此时调用该线程的interrupt方法,那么该线程将抛出一个InterruptedException中断异常,从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。
如果需要中断一个线程,可以调用线程的interrupt()方法。但是,中断线程只是给线程一个中断的标志位,并不能直接终止线程的执行。线程需要自己在执行的过程中检查这个标志位,并在合适的时候终止自己的执行。下面是一个简单的中断线程的示例代码:
Thread thread = new Thread(() ->
while (!Thread.currentThread().isInterrupted())
// 线程执行的代码
);
thread.start(); // 启动线程
// 中断线程
thread.interrupt();
用户线程和守护线程
Java的线程可以分为两类:User Thread(用户线程)、Daemon Thread(守护线程)
- 用户线程:也叫工作线程,当线程的任务执行完毕或以通知的方式来结束,平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。所以当系统只剩下守护进程的时候,Java虚拟机会自动退出。
如何将一个线程设置为守护线程?
需要使用Thread类中的setDaemon方法
- setDaemon(boolean on):为true是守护线程,反则为false,线程默认是用户线程(此方法必须在开始线程之前调用)
当主线程结束,子线程自动结束,将子线程设置为守护线程,演示如下
public class ThreadTest3
public static void main(String[] args)
DaemonThread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);
daemonThread.start();
for (int i = 1; i < 10; i++)
System.out.println("主线程(用户线程)");
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
class DaemonThread extends Thread
@Override
public void run()
for (; ; ) //无线循环
System.out.println("子线程(设置为守护线程)执行中");
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
代码演示
- 使用Thread使用线程,每隔一秒输出hello,worldI(死循环)
public class Thread01
public static void main(String[] args)
test t1 = new test();
t1.start();
class test extends Thread
@Override
public void run()
while (true)
System.out.println(Thread.currentThread().getName());//获取线程的名称
System.out.println("hello,world");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
- 实现Runnable接口,使用线程
public class ThreadTest
public static void main(String[] args)
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
class Test implements Runnable
@Override
public void run()
while (true)
System.out.println(Thread.currentThread().getName());//获取线程的名称
System.out.println("hello,world");
try
Thread.sleep(1000);//休眠一秒
catch (InterruptedException e)
e.printStackTrace();
- 多线程的使用:创建两个线程,一个线程每隔1秒输出hello,world,输出10次,退出。一个线程每隔1秒输出hi,输出5次退出
public class ThreadTest1
public static void main(String[] args)
Thread thread1 = new Thread(new T1());
thread1.start();
Thread thread2 = new Thread(new T2());
thread2.start();
class T1 implements Runnable
int count = 0;
@Override
public void run()
//每隔1秒输出hello,world,输出10次
while (true)
if (count == 10)
break;
System.out.println(Thread.currentThread().getName());
System.out.println("hello,world " + (++count));
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
class T2 implements Runnable
int count = 0;
@Override
public void run()
//每隔1秒输出hello,world,输出10次
while (true)
if (count == 5)
break;
System.out.println(Thread.currentThread().getName());
System.out.println("hi " + (++count));
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
- 主线程输出5次hello之后,会让子线程提前结束休眠(原版子线程会休眠20秒,interrupt之后会提前结束休眠)
public class ThreadTest1
public static void main(String[] args) throws InterruptedException
T t = new T();
t.setPriority(Thread.MIN_PRIORITY);
t.start();
for (int i = 0; i < 5; i++)
System.out.println("hello");
Thread.sleep(1000);
t.interrupt();
class T extends Thread
int count = 0;
@Override
public void run()
for (int i = 0; i < 100; i++)
System.out.println(Thread.currentThread().getName() + "线程执行中..." + (++count));
try
System.out.println("线程休眠中...");
Thread.sleep(20000);//20秒
catch (InterruptedException e) //当执行interrupt方法,InterruptedException会捕获到中断异常
System.out.println(Thread.currentThread().getName() + "被中断线程(interrupt)了");
- 线程插队:让主线程运行输出5次之后,等待子线程执行完毕之后,主线程继续执行
public class ThreadTest2
public static void main(String[] args) throws InterruptedException
T t = new T();
t.start();
for (int i = 0; i < 20; i++)
System.out.println("主线程" + Thread.currentThread().getName() + "运行中...");
Thread.sleep(100);
if (i == 5)
t.join();//子线程插队(强制成功)
// Thread.yield();//子线程插队(不一定成功)
class T extends Thread
@Override
public void run()
for (int i = 0; i < 20; i++)
System.out.println("子线程" + Thread.currentThread().getName() + "运行中...");
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
一篇搞懂nginx与Django部署
文章目录
前言
nginx 是啥我想都不要多说了,那么这里简单说说为什么要用这玩意,单单从django项目来说,那就是如果不使用nginx来帮我们代理的话,那么我们的静态资源是访问不了的,因为我们的runserver只负责动态代理,也就是没有写路由就没有资源返回。除此之外在某些情况下我们需要指定80端口做测试时,我们能够确保我们能够启动,因为你如果直接使用runserver的话那么80端口大概率是启动不了的。除此之外我们还可以用nginx做负载均衡,用处很多,并且使用起来也很简单但是这里由于时间关系就先不说这个,后面如果各位有兴趣的话可以持续关注本博客,我后面会实战开发自己的博客网站并整理成博文,从开发到部署。
环境
这里的话操作环境还是分两大块吧,一个是windows一个是Linux那么我先说说在windows里面吧(Linux里面基本上就是路径不一样,指令能够玩了),我在这里的话是没有在我的windows里面直接安装nginx 和数据库等web应用的,原因很简单安装过程中很容易出现问题并且一旦出现问题可能解决不了,例如装个sql一旦失败就很难再给它弄好。所以我这里都是使用 PHPstudy 这个软件的,这里面集成了很多工具例如nginx redis mysql apache 等等,里面还有图形化界面,比如图形化数据库管理工具等等,用起来很方便,所以在windows环境下我都是使用它来进行测试开发的。所以本文的Windows演示也是基于phpstudy来进行演示的。
nginx模式
我们先来看看我们正常的普通的情况。
nginx的http模式
反向代理模式
这个最常用了,同时但是对nginx的性能消耗也最大(因为它要进行会话维持从而消耗了大量资源),所以某些完全相对独立的页面我们可以直接使用nginx去直接转发给客户端。
好,那么说说什么是反向代理,不要被唬住了其实很好理解而且这玩意其实和科学上网很像。
所以这玩意区别就是反向代理是向服务端代理的,而不是向客户端代理的,它是服务器内部的。不像科学上网,是通过第三方代理服务器转发的。
nginx基本使用
下载安装
首先在windows下的话直接使用集成工具,这玩意自带了。
在Linux里面的话,请参考长达万字的linux基本使用
nginx基本命令
这里主要介绍通过nginx命令如何打开。如果你是Linux系统并且安装好的话,那么直接把指令放在终端里面运行就好了,因为这个是nginx指令嘛,nginx直接支持。
那么在我们现在windows的环境下的话,我们现直接找到phpstudy的安装目录
查看版本信息
nginx -v
nginx -V
启动
nginx 直接输入就启动默认80端口
在Linux里面的要权限
sudo nginx
nginx -c 配置文件 按照配置文件启动
nginx -t 配置文件 检查配置文件对不对有木有错误,不启动
nginx -t -c 配置文件 检查指定的配置文件
关闭&重启
nginx -s stop 立刻强制停止
nginx -s quit 自动停止(等待线程结束)
nginx -s reload 重启(修改配置文件后直接重启,这个重启还会保持之前的链接,很流批)
事实上我们完全可以使用Linux的systemctl来管理nginx但是这样做的后果是他不会按照我们的配置文件启动,除非你更改默认的配置文件。
nginx配置文件
这个非常重要,事实上我们想要实现nginx与django完美会合必须要使用到这玩意。这里的话我们先展示在当前的windows环境当中。
Http配置文件
这个是我们主要关注的对象,其中我们最关注的是server这个部分。
这个太长了,我直接复制了过来我们跳主要的说,感兴趣的自己再到官方文档去看
http {
include mime.types; `这个是返回类型,include是引入配置文件,把一个配置文件拆开来更好看`
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65; `这个是超时时间`
#gzip on;
# another virtual host using mix of IP-, name-, and port-based configuration
`这个部分是重点但是这里被注释掉了,我们后面的操作就是在这进行的,下面我们把这个提取出来细说`
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
#include vhosts.conf;
map $time_iso8601 $logdate {
'~^(?<ymd>\\\\d{4}-\\\\d{2}-\\\\d{2})' $ymd;
default 'date-not-found';
}
include vhosts/*.conf;
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
client_max_body_size 50m;
client_body_buffer_size 60k;
client_body_timeout 60;
client_header_buffer_size 64k;
client_header_timeout 60;
error_page 400 /error/400.html;
error_page 403 /error/403.html;
error_page 404 /error/404.html;
error_page 500 /error/500.html;
error_page 501 /error/501.html;
error_page 502 /error/502.html;
error_page 503 /error/503.html;
error_page 504 /error/504.html;
error_page 505 /error/505.html;
error_page 506 /error/506.html;
error_page 507 /error/507.html;
error_page 509 /error/509.html;
error_page 510 /error/510.html;
keepalive_requests 100;
large_client_header_buffers 4 64k;
reset_timedout_connection on;
send_timeout 60;
sendfile_max_chunk 512k;
server_names_hash_bucket_size 256;
}
worker_rlimit_nofile 100000;
server部分
server {
listen 8000;
listen somename:8080;
server_name somename alias another.alias;
location / {
root html; 这个建议写到外面后面还要再写就不用重复了
index index.html index.htm;
}
}
我们重点来到 location部分
前面的是端口之类的配置。server_name 是你的地址
location其实就是相当于django的路由
root 是项目的根目录,index 其实是你的一个路由。这个/是你的解析目录(直接在html文件下那么就加个/)(在phpstudy默认的那个nginx文件里面是没有的,因为他启动的时候也不会用这个配置文件)
既然location是一个路由匹配那么他也是支持正则的
location [modifier] / {
index xxx;
}
modifier:
= 精确匹配
~ 区分大小写的正则匹配
~* 不区分的正则
^~ 最佳匹配
现在我们举个例子试试 我们做个简单的修改。
我直接在桌面搞了个index文件
所以理论上我们可以直接在server里面写。然后的话为了避免出错我建议还是把默认的配置文件复制过来然后再修改这样好点。
nginx与Django部署
静态文件转发
现在我们把django走一下,我这里就把我先前的应该示例的dome拿过来吧。Python一键生成验证码并部署(django)。现在我们在debug模式下跑我们的dome
现在改一下
然后开始部署我们的nginx。还是修改配置文件
server {
listen 80;
listen localhost;
server_name somename alias another.alias;
root C:/Users/31395/Desktop/Sourse/; 这个是项目地址
location /static {
#在static目录
alias /C:/Users/31395/Desktop/Sourse/static;
#这个就是起别名它会帮助我们自动把下面的文件转化为相应的 “路由”
这样你就不用一个个写了(一个个资源去写)
}
location /{
...如果还有的话
}
}
然后再测试成功。
django 反向代理
首先 nginx可以代理很多个服务器,他对于接受的服务器是没有任何要求的,我们可以使用django自带的runserver也可以使用uwsgi.这里我们先看看runserver这个最简单。
django 代理 runserver
我们还是直接做个例子,我们默认runserver启动8000端口现在我让它走80通过反向代理。
加上一条配置
location / {
proxy_pass http://127.0.0.1:8000;
}
代理 uwsgi
这个的话我们还需要配置一下uwsgi,当然还要下载。相对而言uwsgi要比runserver更高效,真正部署也是到uwsgi里面的,这玩意可是对接nginx的,也是用C写的。所以你会发现其实除了业务逻辑部分,其他都不是python干活,有相当一部分还是C干活(底层)。
- 下载uwsgi
pip3 install uwsgi
- 配置文件。
在这里准备了一个通用的模板
[uwsgi]
sockof=127.8.6.1:881e#等使用nginx连接时使用
#http=0.0.0.0:8888#直接作为web服务器使用
chdir=/工程目录
wsgi-file = Django项目名称.wsgi
processes=4 #进程数,几个核心几个进程
threads=10 #一个进程几个线程
enable-threads=True # 开启线程
master=True #主从开启
pidfile=uwsgi.pid #pid文件保存目录
daemonize=uwsgi.log #后台输出日志
按照模板启动,这玩意默认8888端口
uwsgi --ini xxx.ini
uwsgi --stop xxx.pid
这个时候写location就有两个选择了。
要么向上面那样,直接转发。但是效率没有另一种高,我们的uwsgi可以直接和nginx匹配。
我们有自带的配置,
这个在linux也有,其他的配置几乎一样也就是路径不一样。
location / {
include 目录/uwsgi_params;
uwsgi_pass http://127.0.0.1:8888;
}
在Linux当中
首先所有的操作与windows几乎没有差别,区别就是路径不一样。
如果你的nginx是使用默认安装,那么你的配置文件就在 /etc/nginx当中。
然后我们一个个查看就好了,然后修改。
不过要注意的是在不管是哪个系统填写的都是绝对路径在(nginx -t -c 这个命令当中)
这里的话先不在Linux里面演示了,由于我这边项目暂时保密的问题。
以上是关于一篇搞懂Java多线程运行机制的主要内容,如果未能解决你的问题,请参考以下文章