#程序、进程、线程、纤程
- 什么是程序
- 可执行文件,例如(QQ.EXE)
- 什么是进程
- 进程是资源的分配的基本单位
- 什么是线程
- 线程是调度执行的基本单位
- 多个线程共享同一个进程的资源
- 线程的切换是需要消耗资源的
- 什么是协程/纤程
- 是一种最轻量化的线程(lightweight threads)。它是一种用户线程(user thread),让应用程序可以独立决定自己的线程要如何运作。操作系统内核不能看见它,也不会为它进行调度。就像一般的线程,纤程有自己的寻址空间。但是纤程采取合作式多任务(Cooperative multitasking),而线程采取先占式多任务(Pre-emptive multitasking)。应用程序可以在一个线程环境中创建多个纤程,然后手动运行它。纤程不会被自动运行,必须要由应用程序自已指定让它运行,或换到下一个纤程。
工作中如何设置有效的线程数量
- 根据压测,测试出适合的线程数量
- 在Java Concurrency in Practice一书中给出了估算线程池大小的公式:
- 关于等待时间可以使用profiler这类的系统时间统计工具(例如:Jprofiler、arthas)
- 针对IO密集型的Cpu进行线程池的估计
- 目前按照我看过的一些开源框架,线程池中的现线程基本就是IO密集型(2n +1 ),CPU密集型设置为 n + 1。但实际情况往往复杂的多,不会按照这个进行设置,进行这种设置,通常是框架层面,例如netty,dubbo这种底层通讯框架会参考这样的标准去设置,在实际业务中往往不会这样做。
- 对于IO密集型网上还有一个公式:
线程数 = CPU核心数/(1-阻塞系数)
这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。
-
我觉这个公式有一定的道理,考虑了阻塞的概念。
-
首先在我们业务开发中,基本上都是IO密集型,因为往往都会去操作数据库,操作redis,es等存储型组件,设涉及磁盘IO,网络IO,那什么场景下是CPU密集型呢?纯计算类,例如计算圆周率的位数,当然我们基本接触不到。
-
IO密集型,多一些线程,主要是可以增加IO的并发度,CPU密集型不宜过多线程是会造成线程切换,浪费时间。
-
-
接下来我们以一个实际的场景来说明如何设置线程数量。
-
一个4C8G的机器上部署了一个MQ消费者,用来消费MQ消息的,在RocketMQ的实现中,消费端也是用一个线程池来消费线程的,那这个线程数要怎么设置呢?
-
首先如果按照 2n + 1 的公司来做,线程数设置为 9个,但我们会发现,如果增大线程数量,会显著提高消息的处理能力,说明 2n + 1 对于业务场景来说,不太合适。
-
这个时候套用 线程数 = CPU核心数/(1-阻塞系数) 阻塞系数取 0.8 ,线程数为为 20 。阻塞系数取 0.9,大概线程数40,20个线程数我觉得可以。
-
如果我们发现数据库的操作耗时比较多,此时可以继续提高阻塞系数,从而增大线程数量。
-
java中启动线程的5种方法
public class HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
static class MyCall implements Callable<String> {
@Override
public String call() {
System.out.println("Hello MyCall");
return "success";
}
}
//启动线程的5种方式
public static void main(String[] args) throws Exception {
new MyThread().start(); //@1
new Thread(new MyRun()).start(); //@2
new Thread(() -> {
System.out.println("Hello Lambda!"); //@3
}).start();
FutureTask<String> task = new FutureTask<>(new MyCall()); //@4
Thread t = new Thread(task);
t.start();
System.out.println(task.get());
ExecutorService service = Executors.newCachedThreadPool(); //@5
service.execute(() -> {
System.out.println("Hello ThreadPool");
});
Future<String> f = service.submit(new MyCall());
String s = f.get();
System.out.println(s);
service.shutdown();
}
}
- @1 继承thread方法
- @2 实现runnable方法
- 实现比继承好,java的多态:单继承多实现
- @3 lambda表达式实现
- @4 使用Callable带返回值的任务执行
- @5 使用线程池
java的6种线程状态
- NEW: 线程刚刚创建,还没有启动
- RUNABLE: 可运行状态,由线程调度器可以安排执行
- WAITING: 等待被唤醒
- TIMED WAITING: 隔一段时间后自动唤醒
- BLOCKED: 被阻塞,正在等待锁
- TERMINATED: 线程结束
线程的中断机制
在Thread类中,提供了stop(),suspend()和resume()方法,这三个方法分别是用来结束,暂停,恢复线程. 但是都已经被标记为@Deprecated废弃了. 因为一个线程不应该由其他线程来结束,他应该收到别人的通知,然后自己在合适的位置结束,如果不合理的结束,会导致很多意外的结果,比如临界区还没完全操作完,提前释放锁,但是部分状态已经改变,还有没有做一些清理操作等等.
基于上面的理由,Java提供了新的中断机制(interrupt),其他线程调用想要终止线程的interrupt()方法. 这个时候线程会根据自己的状态做出响应:
- 如果线程处于阻塞状态(sleep,wait,join),则线程会抛出InterruptedException异常.
- 如果线程处于正常运行状态,则还是正常运行,但是中断的标志被设置为true,相当于有人通知 你该结束自己了.
以下是Thread中提供中断的三个方法:
- java.lang.Thread#interrupt
- 打断某个线程(设置标记位)
- java.lang.Thread#interrupted
- 查询当前线程是否被打断过,并重置打断标志
- java.lang.Thread#isInterrupted()
- 查询某个线程是否被打断过(查询标志位)
如何结束一个线程
- 使用
stop方法(已经废弃)- 直接释放锁,可能存在数据不一致问题
suspend(暂停),resume(恢复执行)(已经废弃)- 直接释放锁,可能存在数据不一致问题
- 使用volatile关键字
- 自定义标志位退出条件
- 使用interrupt 中断
欢迎大家关注我的微信公众号共同学习进步: