目录
一、进程和多线程
1、进程
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
2、线程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
3、多线程
多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
4、多线程必要性
高并发系统,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
5、为什么提倡多线程而不是多进程
线程就是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。
6、线程的五种状态
(1)、五种状态
新建(NEW):新创建了一个线程对象。
可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
- 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
- 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
(2)、线程的状态图
二、几个重要的概念
1、同步和异步
同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。
异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。
2、并发和并行
并发是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。
并行是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行
3、高并发
高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
4、临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。在并行程序中,临界区资源是保护的对象。
5、阻塞和非阻塞
阻塞和非阻塞通常被用来形容多线程间的相互影响。当一个线程占用了临界区资源,那么其它需要使用这个资源的线程都必须在这个临界区上等待。等待会导致线程挂起,这样就形成了阻塞。如果占用资源的线程一直没有释放资源,那么其它的线程在这个临界区上都不能继续工作。
非阻塞表明多个线程之间的执行是不会相互影响的。
6、用户线程和守护线程
(1)、守护线程(即daemon thread),
- 是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,
- 再换一种说法,如果有用户自定义线程存在的话,jvm就不会退出,此时,守护线程也不能退出,也就是它还要运行,干嘛呢,就是为了执行垃圾回收的任务啊。
- 守护线程又被称为“服务进程”“精灵线程”“后台线程”,是指在程序运行是在后台提供一种通用的线程,这种线程并不属于程序不可或缺的部分。 通俗点讲,任何一个守护线程都是整个JVM中所有非守护线程的“保姆”。
(2)用户线程
-
应用程序里的线程,一般都是用户自定义线程。
-
用户也可以在应用程序代码自定义守护线程,只需要调用Thread类的设置方法设置一下即可。
-
用户线程和守护线程几乎一样,唯一的不同之处就在于如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了。 因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了,程序也就终止了,同时会“杀死”所有守护线程。 也就是说,只要有任何非守护线程还在运行,程序就不会终止。
(3)、设置守护线程
-
setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
-
在守护线程中产生的新线程也是守护线程
-
不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
(4)、用户线程和守护线程的区别
- 主线程结束后用户线程还会继续运行,JVM存活;主线程结束后守护线程和JVM的状态又下面第2条确定。
- 如果没有用户线程,都是守护线程,那么JVM结束(随之而来的是所有的一切烟消云散,包括所有的守护线程)。
三 、多线程创建的三种方式
-
继承Thread类、
-
实现Runnable接口:该方式可以避开Java单继承的限制,接口可以多实现
-
使用线程池
四、实例变量和线程安全
线程类中的实例变量针对其他线程可以有共享和不共享之分。
1、不共享数据的情况
/**
*@Description: 多个线程之间不共享变量线程安全的情况
*每个线程都有一个属于自己的实例变量count,它们之间互不影响。
*/
public class MyThread extends Thread {
private int count = 5;
public MyThread(String name) {
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count);
}
}
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}
2、共享数据的情况
/**
*
* @Description: 多个线程之间共享变量线程不安全的情况,实现输出依次递减的结果
*这段代码并不能实现需求,在jvm中,count–的操作分为如下下三步:取得原有count值、计算i -1、对i
*进行赋值,所以多个线程同时访问时就会出现问题。
*/
public class SharedVariableThread extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由 " + SharedVariableThread.currentThread().getName() + " 计算,count=" + count);
}
public static void main(String[] args) {
SharedVariableThread mythread = new SharedVariableThread();
// 下列线程都是通过mythread对象创建的
Thread a = new Thread(mythread, "A");
Thread b = new Thread(mythread, "B");
Thread c = new Thread(mythread, "C");
a.start();
b.start();
c.start();
}
}
五、一些线程常用方法
1、currentThread()
返回对当前正在执行的线程对象的引用。
2、 getId()
返回此线程的标识符
3、 getName()
返回此线程的名称
4、 getPriority()
返回此线程的优先级
5 、isAlive()
测试这个线程是否还处于活动状态。
什么是活动状态呢?
活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。
6、 sleep(long millis)
使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
7、 interrupt()
中断这个线程。知识做了一个终端标记,不会真正的将线程停止
8、 interrupted() 和isInterrupted()
interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能
isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志
9、 setName(String name)
将此线程的名称更改为等于参数 name 。
10 、isDaemon()
测试这个线程是否是守护线程。
11、 setDaemon(boolean on)
将此线程标记为 daemon线程或用户线程。
12、 join()
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是 主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
13、 yield()
yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。
14、 setPriority(int newPriority)
更改此线程的优先级
六、如何停止一个线程?
1、抛异常法
for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。
public class InterruptThread2 extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("已经是停止状态了!我要退出了!");
break;
}
System.out.println("i=" + (i + 1));
}
System.out.println("看到这句话说明线程并未终止------");
}
public static void main(String[] args) {
try {
InterruptThread2 thread = new InterruptThread2();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
}
}
2、使用return停止线程
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("ֹͣ停止了!");
return;
}
System.out.println("timer=" + System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t=new MyThread();
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
七、线程的优先级
- 每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低
- 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。
- 线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。
- 线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。
- Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1),Thread.NORM_PRIORITY(常数5),
- Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)
- Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)。