java多线程之概述和优势及风险

1.线程的概述:

1.1

进程(process):操作系统进行资源分配和调度的基本单位,可以把进程简单地理解为正在操作系统中运行的一个程序。

线程(thread):是进程的一个执行单元。一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支。

区别:进程是线程的容器;线程更加轻量级,创建和撤销一个线程的开销小得多;本质的区别在于每个进程都拥有自己的一套变量,而线程则共享数据;

1.2

Java中,创建一个线程就是创建Thread()类(子类)对象,它有两个构造方法,一个是Thread(),一个是Thread(Runnable)。分别对应1)实现Thread类的子类。2)实现一个Runnable接口的类。两种方法都需要对run()方法进行重写。

自动线程由start()方法来实现,启动线程的实质就是请求JVM运行相应的线程,而这个线程具体在什么时候运行由线程调度器(scheduler)决定。 

1.3

线程中的常用方法:(注意Thread开头表示方法是静态的,thread开头表示方法是实例方法)

Thread.currentThread():获得当前线程;注意Java中任何一段代码都是执行在某一个线程中的,执行当前代码的线程就是当前线程;特殊例子:main()方法中创建对象时,执行类的构造函数的线程是当前的main线程,而不是子线程。

thread.setName(线程名称)

thread.getName()

thread.isAlive():判断当前线程是否处于活动状态,活动状态就是线程已经启动,还没有终止。

Thread.sleep(millis):让当前线程休眠指定的毫秒数。与下面的yiele()函数不同的是,优先级更低的线程有机会得到执行。

thread.getID():可以获得线程的唯一标识。注意,当某个编号的线程运行结束之后,该编号可能被后续的线程使用。

Thread.yield():方法的作用是放弃当前的CPU 资源。由运行状态回到就绪状态。说明当前线程已经完成了生命周期最重要的部分,可以交给其他线程来执行其他任务。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

thread.setPriority(num):给线程设置优先级,取值范围有1~10,线程优先级本质上只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程,注意不能保证优先级高的线程先运行。优先级是具有继承性的,子线程的优先级和父线程的优先级是一样的。

thread.interrupt():中断函数,仅仅是给线程一个停止标志,并不是真正的停止线程。

thread.isInterrupted():测试线程是否被中断。实例方法。

Thread.interruped():测试当前线程(正在执行这个指令的线程)是否被中断,是静态方法。它还会将当前线程的中断状态重置为false。

thread.setDaemon(boolean):将线程设置为守护线程。

public Thread.State getState():获得此线程的状态。

1.4 中断

一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束

InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞

在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

1.5

在子线程的run()方法中,如果有受检异常(编译异常)需要处理,只有选择捕获处理,而不能抛出处理(异常的抛出处理其实就是交给上一级处理,线程的上一级是JVA)。可以从几个角度去理解:

(1)在java多线程程序中,所有线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException)(主线程main除外),也就是说各个线程需要自己把自己的checked exception处理掉。这一点是通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。但是线程依然有可能抛出unchecked exception(如运行时异常),当此类异常跑抛出时,线程就会终结,而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常)。JVM的这种设计源自于这样一种理念:“线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉.换句话说,我们不能捕获从线程中逃逸的异常。

(2)子类在重写父类抛出异常的方法时,要么不抛出异常,要么抛出与父类方法相同的异常或该异常的子类。如果被重写的父类方法只抛出受检异常,则子类重写的方法可以抛出非受检异常;

子类在重写父类抛出异常的方法时,如果实现了有相同方法签名的接口且接口中的该方法也有异常声明,则子类重写的方法要么不抛出异常,要么抛出父类中被重写方法声明异常与接口中被实现方法声明异常的交集

1.6线程状态(state)的图

 

 

2.多线程的优势和风险:

多线程编程具有以下优势:

1.提高系统的吞吐率(Throughout):可以使一个进程有多个并发的操作。

2.提高响应性(responsiveness):web服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间。

3.充分利用多核(multicore)处理器资源,通过多线程可以充分地利用CPU的资源。

多线程编程存在的问题与风险:

1) 线程安全(Thread safe)问题.多线程共享数据时,如果没有采取
正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数
据(过期的数据), 如丢失数据更新。脏读:对共享数据的修改和对共

享数据的读取不同步(解决办法:不仅要对共享数据的修改代码块

同步,还要对读取数据的代码块同步)

2) 线程活性(thread liveness)问题.由于程序自身的缺陷或者由资
源稀缺性导致线程一直处于非RUNNABLE 状态,这就是线程活性问题,
常见的活性故障有以下几种:(额外知识:线程出现异常时会自动释放锁)

(1) 死锁(Deadlock). 类似鹬蚌相争。在多线程程序中,同步可能需要多个锁

,如果获得锁的顺序不一致,则可能导致死锁。解决办法是:所有线程获得

锁的顺序保持一致。
(2) 锁死(Lockout), 类似于睡美人故事中王子挂了。
(3) 活锁(Livelock). 类似于小猫咬自己尾巴。活锁具有两个特点,
第一个是线程没有阻塞, 始终在运行中(所以叫活锁, 线程是活的, 运行中的. )
第二个特点: 程序却得不到进展, 因为线程始终重复同样的无效事情.
(4) 饥饿(Starvation).类似于健壮的雏鸟总是从母鸟嘴中抢到食
物。
3) 上下文切换(Context Switch). 处理器从执行一个线程切换到执
行另外一个线程。

4) 可靠性. 可能会由一个线程导致JVM 意外终止,其他的线程也
无法执行.

 

posted @ 2021-04-10 16:02  一颗小红星  阅读(244)  评论(0)    收藏  举报