进程和线程的简介
进程
进程是程序的一次执行过程,是系统运行程序的基本单位。一个程序的运行就是从一个进程创建开始的。在Windows中查看任务管理器,就可以看见当前Windows运行的进程(.exe文件)
线程
线程和进程相似,但是线程是一个比进程更小的执行单位。一个进程的执行过程中可以产生多个线程。多个线程共享同一个内存空间和一组系统资源。所以系统在产生一个线程或者是在线程之间的切换,负担要比进程小得多,所以线程又被称为轻量级的进程。
几个重要概念
同步异步
同步和异步通常形容一次方法的调用。同步方法的调用,一旦开始,调用者必须在方法调用调用返回以后,才能执行后续的操作。异步方法的调用更像是一个消息的传递,一旦开始,方法调用就会立刻放回,调用者可以执行后续的操作。
并发(Concurrency)和并行(Parallelism)
并发和并行是一个相似的概念。他们都可以表示多个任务同时执行。但是并行才是真正意义上的同时执行。并发,是多个任务交替执行。
多线程在单核CPU中是交替执行(并发)。多核CPU,每个CPU有自己的运算器,所以可以同时执行(并行)。
并行:两列队伍,两个咖啡机。
并发:两列队伍,一个咖啡机。
高并发(High Concurrency)
高并发通常是指,通过设计保证系统能够同时并行处理很多请求。
高并发相关的常用指标:响应时间(response time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户等。
临界区
临界区是表示一种公共资源或者是共享数据,可以被多个线程使用。但是,同时间只能有一个线程可以使用它,一旦临界区的资源被占用,其他线程想要使用这个资源,就必须等待。在并行程序中,临界区资源是保护对象。
阻塞和非阻塞
非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前的线程,而会立刻返回,而阻塞与之相反。
使用多线程的三种常见方式
前面的两种方式很少使用,一般使用的是第三种的线程池方式。
继承Thread类
MyThread.java
1 | public class MyThread extends Thread{ |
Run.java
1 | public class Run{ |
运行结果:
从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者是随机的时间来调用线程中的run方法。
实现Runnable接口
推荐实现Runnable接口方式,因为Java单继承但是可以实现多个接口。
MyRunnable.java
1 | public class MyThread implements Runnale{ |
Run.java
1 | public class Run{ |
运行结果:
使用线程池
使用线程池的方式是最推荐的一种方式, 另外,在《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分强调“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。
实例变量和线程安全
不共享数据的情况
1 | public class MyThread extends Thread { |
运行结果:
由此可以看出每个线程都有一个属于自己实例变量count,他们之间相互不影响。
共享数据的情况
1 | public class SharedVariableThread extends Thread{ |
运行结果:
可以发现这里已经出现了错误,
在大多数的JVM中,Count–的操作分为一下三步:
1.取得原有的Count的值
2.计算Count-1
3.对Count赋值
所以多个线程同时访问出现问题是难以避免的了。
那么有没有什么解决的方法呢
第一种是利用synchronize关键字(保证任意时刻只能有一个线程执行该方法)
第二种是利用AtomicInteger类。
一些常用的方法
currentThread()
返回当前正在执行的线程对象的引用。
getId()
返回当前线程的标识符。
getName()
返回当前线程的名称。
isAlive()
测试这个线程是否处于活动的状态。
什么是活动状态
活动状态就是线程已经启动且尚未终止。线程处于正在运行或者准备运行的状态。
sleep()
是当前正在执行的线程以指定的毫秒数”休眠”(暂时停止执行)。
interrupt()
中断当前线程。
interrupted()和isInterrupted()
interrupted():测试当前线程是否是中断状态,执行后清除状态标志为false的线程。
isInterrupted():测试当前线程是否是中断状态,但是不清楚状态标志。
setName()
更改当前线程的名称
isDeamon()
测试当前线程是否是守护线程。
setDeamon(boolean on)
将当前线程标记为deamon线程或用户线程。
join()
很多情况下,主线程生成启动了子线程,如果子线程里需要进行大量的耗时的计算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用子线程的处理结果,也就是主线程需要等到子线程执行完成以后再结束,这个时候就要用到join()方法
join()的作用:“等待该线程结束”。
yield()
yield()方法的作用就是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。但是放弃占用的时间不一定,可能很快就会重新获得CPU时间。
setPriority(int newPriority)
更改当前线程的优先级。