线程基础、线程之间的共享和协作(一)
基础概念
什么是进程和线程
进程是程序运行资源分配的最小单位,比如在电脑里打开一个程序,进程和进程 之间是相互独立的线程是 CPU 调度的最小单位,必须依赖于进程而存在。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
CPU 核心数和线程数的关系
目前主流 CPU 都是多核的。增加核心数目就是为了增加线 程数,因为操作系统是通过线程来执行任务的,一般情况下它们是 1:1 对应关系,也 就是说四核 CPU 一般拥有四个线程。但 Intel 引入超线程技术后,使核心数与线程 数形成 1:2 的关系
CPU 时间片轮转机制
时间片轮转调度又被叫做RR调度,每个进程被分配一个时间段,称作它的时间片(即该进程允许运行时间)。
时间片设置得太短会导致过多的进程间切换,降低了cpu的效率。设置得太长的话,对超短的交互请求的响应有可能变差。将时间片设为100ms通常是比较合理的折衷。
并行和并发
并行: 同时进行,比如在公司里有4个饮水机,你和同事在不同的饮水机同时接水。这个时候的最大并行数就是4了。cpu也是这样 ,一个cpu相当于饮水机,线程数就是4台饮水机,多个cpu就相当于有了更多的饮水机。同时执行不同的任务。
并发:并发是不能脱离时间单位的,比如6楼只有一台饮水机,某个时间段你和同事都想要使用这台饮水机,那么就只能排队使用。原则上一个 CPU 只能分配给一个 进程,以便运行这个进程。应用交替的执行不同的任务。
两者区别:一个是交替执行,一个是同时执行。
高并发编程的意义、好处
(1)充分利用 CPU 的资源
(2)加快响应用户的时间
(3)可以使你的代码模块化,异步化,简单化
多线程程序需要注意事项
(1)线程之间的安全性
在同一个进程里面的多线程是资源共享的,也就 是都可以访问同一个内存地址当中的一个变量。如果每个线程中对全局变量、静态变量只有读操作,没有写操作,那么一般来说,这个全局变量就是线程安全的;但是如果有多个线程同时执行写操作,那么我们就需要考虑线程同步,不然就可能影响线程安全。
(2)线程之间的死锁
因为线程之间的安全性问题,引入了java的锁机制,一不小心就会产生java线程死锁的多线程问题。一个线程如果等待一个根本不会被释放的锁,那么目标就无法达成。有两个线程,他们互相需要对方释放锁才能继续下去,那么就会永远的等待下去。
(3)线程太多了会将服务器资源耗尽形成死机当机
线程数太多有可能造成系统创建大量线程而导致消耗完系统内存以及 CPU 的“过渡切换”,造成系统的死机。可以参考数据库连接池。
启动一个Java的main方法
程序就会是多线程的,除了下面6种线程还可能会有其他的。
[6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的
[5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程
[3] Finalizer // 调用对象的finalize(这个方法在gc启动,该对象被回收的时候被调用) 方法的线程
[2] Reference Handler //清除 Reference 的线程
[1] main //main 线程,用户程序入口
线程的启动方法
很多人把callable也算为了一种,但是jdk的官方注释上说的是线程的启动方式只有两种。
(1)、继承Thread类,重写run()方法。
(2)、实现Runnable接口。
package com.wsh.enjoygit.threadTest;
public class MyThread {
/**
* 继承Thread类方式实现
*/
private static class UseThread extends Thread{
@Override
public void run() {
//do my work
System.out.println("我使用继承Thread类方式实现");
}
}
/**
* 实现Runnable接口方式实现
*/
private static class UseRunnable implements Runnable{
@Override
public void run() {
//do my work
System.out.println("我使用实现Runnable接口方式实现");
}
}
public static void main(String[] args) {
UseThread useThread = new UseThread();
UseRunnable useRunnable = new UseRunnable();
useThread.start();
new Thread(useRunnable).start();
}
}
Thread 和 Runnable 的区别
Thread是java里对线程的唯一抽象,Runnable是对任务或者业务逻辑的抽象。Thread可以接受任意一个Runnable的实例并执行。
在一个创业公司里,一个人开发不仅要做开发,还要做产品、设计、测试,运维、前端的工作,老板良心发现了,看你一个人忙不过来,就又找了几个人来帮忙。这里每个人就是一个线程Thread,我们要做的产品、设计、测试……等等 就是Runnable。
线程的终止方法
线程自然终止
要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
suspend() 暂停
resume() 恢复
stop() 停止
这些API是过期不建议使用的。
suspend()方法在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。这样容易引发死锁问题。
stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
因为 suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
中断
jdk的线程是协作式的,不是抢占式的
安全的中止是其他线程通过调用某个线程的interrupt()方法对其进行中断操作,对该线程打了一个中断标记。
就好像A线程对B线程说,“B,你要中断了!”,但是这不代表线程B立即就会停止自己的工作。同样,B线程完全可以不理会这种中断请求。线程通过检查自身的中断标志位是否被置为true来进行响应。
线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。
package com.wsh.enjoygit.thread;
/**
* 安全中断线程
*/
public class EndThread {
private static class UseThread extends Thread{
public UseThread(String name){
super(name);
}
@Override
public void run(){
String threadName = Thread.currentThread().getName();
// //1、这种情况下虽然通知了该线程要终止,但是并没有理会,一直在跑
// System.out.println(threadName+" interrupt flag ="+isInterrupted());
// while (true){
// System.out.println(threadName+" is running");
// System.out.println(threadName+" inner interrupt flag ="+isInterrupted());
// }
// //2.检查当前线程是否被中断,isInterrupted()开始为false,隔了一段时间,最后检查到了中断状态true
// System.out.println(threadName+" interrupt flag ="+isInterrupted());
// while(!isInterrupted()){
// System.out.println(threadName+" is running");
// System.out.println(threadName+" inner interrupt flag ="+isInterrupted());
// }
// System.out.println(threadName+" interrupt flag ="+isInterrupted());
//3.检查当前线程是否被中断,Thread.interrupted(),发现为true以后,会把标志位改为false
System.out.println(threadName+" interrupt flag ="+isInterrupted());
while(!Thread.interrupted()){
System.out.println(threadName+" is running");
System.out.println(threadName+" inner interrupt flag ="+isInterrupted());
}
System.out.println(threadName+" interrupt flag ="+isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("wsh");
endThread.start();
Thread.sleep(20);
//中断线程,其实是设置线程的
endThread.interrupt();
}
}
测试demo的运行结果:
1.这种情况下虽然通知了该线程要终止,但是并没有理会,一直在跑
2.检查当前线程是否被中断,isInterrupted()最后检查到了中断状态true
3.检查当前线程是否被中断,Thread.interrupted(),发现为true以后,会把标志位改为false