目录
Linux
进程
保存在硬盘上的程序运行之后,会在内存中形成一个独立的内存体,它有自己独立的地址空间,有自己的堆。操作系统会以进程为单位来分配系统资源(CPU的时间片,内存资源等)。进程是资源分配的最小单位。
线程
又称轻量级进程,是操作系统调度CPU执行的最小单位。
线程状态
新建状态(NEW):新创建的一个线程对象。
就绪状态(RUNNABLE):线程对象被创建并被调用后,该线程会进入可运行线程池中,等待调度,获取CPU的使用权。
运行状态(RUNNING):就绪状态的线程获得了CPU的时间片(使用权),即进入运行状态。
阻塞状态(BLOCKED):是指运行状态的线程放弃了CPU的使用权,停止运行,并转入阻塞状态。
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态(DEAD):线程执行结束,该线程的生命周期结束。
注:线程休眠属于阻塞状态,当线程结束休眠后将会重新进入就绪状态(RUNNABLE),并重新等待调用。
协程
个人理解:在CPU时间片轮转的基础之上的一种时间复用技术。
相关命令
ps
常用参数:
a | 显示现行终端机下的所有程序,包括其他用户的程序 |
-A | 显示所有程序 |
c | 显示每个程序真正的指令名称,而不包含路径 |
-C <指令名称> | 指定执行指令的名称,并列出该指令的程序的状况 |
-d | 显示所有程序,但不包括阶段作业管理员的程序 |
e | 列出程序时,显示每个程序所使用的环境变量 |
-f | 显示UID,PPIP,C与STIME栏位 |
f | 用ASCII字符显示树状结构,表达程序间的相互关系 |
g | 显示现行终端机下的所有程序,包括所属组的程序 |
-G <群组识别码> | 列出属于该群组的程序的状况 |
h | 不显示标题列 |
-H | 显示树状结构,表示程序间的相互关系 |
-j | 采用工作控制的格式显示程序状况 |
-l | 采用详细的格式来显示程序状况 |
L | 列出栏位的相关信息 |
-m | 显示所有的执行绪 |
n | 以数字来表示USER和WCHAN栏位 |
-N | 显示所有的程序,除了执行ps指令终端机下的程序之外 |
-p <程序识别码> | 指定程序识别码,并列出该程序的状况 |
r | 只列出现行终端机正在执行中的程序 |
-s <阶段作业> | 列出隶属该阶段作业的程序的状况 |
s | 采用程序信号的格式显示程序状况 |
S | 列出程序时,包括已中断的子程序资料 |
-t <终端机编号> | 列出属于该终端机的程序的状况 |
-T | 显示现行终端机下的所有程序 |
u | 以用户为主的格式来显示程序状况 |
-U <用户识别码> | 列出属于该用户的程序的状况 |
U <用户名称> | 列出属于该用户的程序的状况 |
v | 采用虚拟内存的格式显示程序状况 |
-V或V | 显示版本信息 |
-w或w | 采用宽阔的格式来显示程序状况 |
x | 显示所有程序,不以终端机来区分 |
X | 采用旧式的Linux i386登陆格式显示程序状况 |
-y | 配合选项”-l”使用时,不显示F(flag)栏位,并以RSS栏位取代ADDR栏位 |
参考实例:
显示系统中全部的进程信息,含详细信息:
[root@vanqiyeah ~]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 125716 1896 ? Ss 09:15 0:00 /usr/lib/systemd/systemd --switched-root --system --d
root 2 0.0 0.0 0 0 ? S 09:15 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? S< 09:15 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? S 09:15 0:00 [ksoftirqd/0]
...以下省略...
结合输出重定向,将当前进程信息保留备份至指定文件:
[root@vanqiyeah /]# ps aux > ./1.txt
[root@vanqiyeah /]# cat 1.txt
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 125716 2004 ? Ss 09:15 0:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root 2 0.0 0.0 0 0 ? S 09:15 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? S< 09:15 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? S 09:15 0:00 [ksoftirqd/0]
...以下省略...
结合管道操作符,将当前系统运行状态中指定进程信息过滤出来:
[root@vanqiyeah /]# ps -aux|grep docker
kill
kill的误区:kill 命令是向进程发送信号,kill 不是杀死的意思,-9 表示无条件退出,但由进程自行决定是否退出,这就是为什么 kill -9 终止不了系统进程和守护进程的原因。
常用参数:
-l | 列出系统支持的信号 |
-s | 指定向进程发送的信号 |
-a | 不限制命令名和进程号的对应关系 |
-p | 不发送任何信号 |
1(HUP) | 重新加载进程 |
9(KILL) | 杀死(结束)一个进程 |
15(TERM) | 正常停止一个进程 |
参考实例:
列出系统支持的全部信号列表:
[root@vanqiyeah /]# kill -l
结束某个指定的进程(数字为对应的PID值):
[root@vanqiyeah /]# kill 3809
强制结束某个指定的进程(数字为对应的PID值):
[root@vanqiyeah /]# kill -9 3809
pkill
常用参数:
-o | 仅向找到的最小(起始)进程号发送信号 |
-n | 仅向找到的最大(结束)进程号发送信号 |
-P | 指定父进程号发送信号 |
-g | 指定进程组 |
-t | 指定开启进程的终端 |
参考实例:
仅向找到的最小(起始)进程号发送信号:
[root@vanqiyeah /]# pkill -o
仅向找到的最大(结束)进程号发送信号:
[root@vanqiyeah /]# pkill -n
JAVA多线程并发
线程的新建
- 继承Thread类,重写run方法,调用start方法开启线程内部调用run方法
- 实现Runable接口,重写run方法
- callable接口,重写run方法,futureTask方法获取线程返回值
通过实现 Runnable 接口来创建线程
为了实现Runnable,一个类只需要执行一个方法调用run(),申明如下:
public void run()
在创建一个实现 Runnable 接口的类之后,可以在类中实例化一个线程对象。Thread 定义了几个构造方法,下面的这个是经常使用的:
Thread(Runnable threadOb,String threadName);
threadOb 是一个实现 Runnable 接口的类的实例。新线程创建之后,你调用它的 start() 方法它才会运行。
void start();
实例如下:
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
运行结果如下:
通过继承Thread来创建线程
另一种方法是创建一个新的类,该类继承Thread类,然后创建一个该类的实例。继承类必须重写run()方法,也必须调用start()才能执行。
实例如下:
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
运行结果:
通过 Callable 和 Future 创建线程
创建Callable接口的实现类,并实现Call()方法,该call()方法将作为线程执行体。
实例如下:
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " i" + i);
if (i == 20) {
new Thread(ft, "return:").start();
}
}
try {
System.out.println("return:" + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
运行结果:
Golang多线程并发
并发和并行
A. 多线程程序在一个核的cpu上运行,就是并发。
B. 多线程程序在多个核的cpu上运行,就是并行。
协程和线程
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。
goroutine 只是由官方实现的超级"线程池"
Go 语言支持并发,只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。goroutine 语法格式:
go 函数名( 参数列表 )
例如:
go f(x, y, z)
开启一个新的 goroutine:
f(x, y, z)
实例如下:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
运行结果:
通道(channel)
通道(channel)是用来传递数据的一个数据结构。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明通道使用chan关键字即可,通道在使用前必须先被创建:
ch := make(chan int)
实例如下:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
运行结果:
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
实例如下:
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
运行效果:
Go 遍历通道与关闭通道
通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
实例如下:
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
运行结果: