好久没有更新博客了,今天开始!记住这一天2019年12月9日
走出来吧,别整天做梦了。
目录
上下文(环境)切换:当前运行环境和另外一个运行环境(看书 切换 -> 看另外一本书 同时记录本书看到的页数)
有时也称做进程切换或任务切换,是指CPU 从一个进程或线程切换到另一个进程或线程。
切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。上下文切换包括保存当前任务的运行环境,恢复将要运行任务的运行环境。
进程的PCB(进程控制块,也称为PCB,即任务控制块)表示,它包括进程状态,CPU寄存器的值等。
并发编程中不是开的线程数越多越好
如何减少上下文的切换?
1、无锁的并发编程:
多线程竞争锁时产生上下文切换。
通过一些办法避免使用锁,例如按照Hash算法取模
2、CAS算法实现
底层通过原子性操作实现
Atomic包使用CAS算法来更新数据,而不需要加锁。使用最少线程
3、协程的方法(GO语言用的多)
单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。--GO
4、减少线程的使用
能减少线程的使用就少创建线程,不创建不必要的线程。
死锁
常见的死锁的例子:
(如果不加sleep(xx)的话不会发生死锁的情况,执行的速度很快)
new Thread(()->{
synchronized (HAIR_A) {
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (HAIR_B) {
System.out.println("A成功的抓住B的头发");
}
}
}).start();
new Thread(()->{
synchronized (HAIR_B) {
synchronized (HAIR_A) {
System.out.println("B成功抓到A的头发");
}
}
}).start();
IDEA中如何查看是否出现死锁的情况
jstack 进程程号
jconsole 进程号
方便的实现Java中的死锁的检查
上面的Thead1使用的Thread0的资源,Thead0使用的Thread1的资源.
线程1启动,获取到A资源之后的同时线程0启动,获取到资源B,
这时 线程1又需要资源B(0已经获取了独占资源)1就拿不到资源B了
线程0又需要资源A(1已经获取了独占资源)0就拿不到资源A了
形成一个环(0和1都不能释放获取的资源)
产生条件
两个人为了一个宝贝(独占),相互扯着不放想要宝贝(请求并保持),没拿到宝贝之前,别人也无法把他们拦开(不剥夺),都等着对方先放手最后形成还(循环等待状态)
1、互斥条件:资源只能被一个线程占有(独占)
2、请求并保持;一个进程获取到资源之后进行阻塞的时候,能够继续请求获取资源,之前获取的资源不释放
3、不剥夺条件:进程获取到资源之后,未使用之前不能剥夺
4、循环等待:线程形成首位相接的环状的循环等待资源的关系
线程安全问题:
运行没有问题,运行的结果不是想要的结果
/**
* 线程不安全操作代码实例
*/
public class UnSafeThread {
private static int num = 0;
private static CountDownLatch countDownLatch = new CountDownLatch(10);
/**
* 每次调用对num进行++操作
*/
public static void inCreate() {
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 100; j++) {
inCreate();
}
}).start();
}
System.out.println(num);
}
}
最后输出的结果为0,还没调用inCrease的方法,就已经进行输出打印了。
/**
* 线程不安全操作代码实例
*/
public class UnSafeThread {
private static int num = 0;
private static CountDownLatch countDownLatch = new CountDownLatch(10);
/**
* 每次调用对num进行++操作
*/
public static void inCreate() {
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 100; j++) {
inCreate();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//每个线程执行完成之后,调用countdownLatch
countDownLatch.countDown();
}).start();
}
while (true) {
if (countDownLatch.getCount() == 0) {
System.out.println(num);
break;
}
}
}
}
最终的运行结果为935,??
没有加锁
资源限制:
-
服务器: 1m 本机:2m
带宽的上传/下载速度、硬盘读写速度和CPU的处理速度。
-
软件资源
数据库连接 500个连接 1000个线程查询 并不会因此而加快 socket
- 进程:是系统进行分配和管理资源的基本单位
- 线程:进程的一个执行单元,是进程内调度的实体、是CPU调度和分派的基本单位,是比进程更小的独立运行的基本单位。线程也被称为轻量级进程,线程是程序执行的最小单位。
- 一个程序至少一个进程,一个进程至少一个线程。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
- 而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式进行。
- 如何处理好同步与互斥是编写多线程程序的难点。 多进程程序更健壮,进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响, 而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,所以可能一个线程出现问题,进而导致整个程序出现问题
线程间的状态转换
NEW runnable blocked waiting timed_waiting terminated
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):处于可运行状态的线程正在JVM中执行,但它可能正在等待来自操作系统的其他资源,例如处理器。调用start方法
- 阻塞(BLOCKED):线程阻塞于synchronized锁,等待获取synchronized锁的状态。
- 等待(WAITING):Object.wait()、join()、 LockSupport.park(),进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIME_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,该状态不同于WAITING,它可以在指定的时间内自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
new Thread(()->{
for (int j=0;j<1000;j++){
inCreate();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
block状态:
time_wait
T1:block
T0:time_wait