java多线程编程核心技术——第一章总结
目录:
- 1.1进程、多线程的概念,及线程的优点
- 1.2多线程的使用
- 1.3currentThread()方法
- 1.4isAlive()方法
- 1.5sleep()方法
- 1.6getId()方法
- 1.7停止线程
- 1.8暂停线程
- 1.9yield()方法
- 1.10线程的优先级
- 1.11守护线程
1.1进程、多线程的概念及线程的优点
进程的概念:
百度百科讲解: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。
程序是指令、数据及其组织形式的描述,进程是程序的实体。
着重点的话,就是最后一句进程是程序的实体,个人理解的话,也可以认为是进程就是程序的表现,你所看到的每一个进程,其实质都是一个程序,或者说是在运行的程序。
线程的概念:
线程可以理解成在进程中独立运行的子任务,如在qq运行中,好友视频线程,文件下载线程,传输数据线程等,这些不同的独立的任务都在同时的进行,其中每一项任务都可以理解成是"线程"在工作。
线程就是子任务。
1.2多线程的使用
想要实现线程,有两种方式(目前):
- 继承Thread类
- 实现Runnable接口
注:Thread类也实现了Runnable接口。
若想实现“多继承”只能通过实现Runnable接口来实现。继承Thread类与实现Runnable接口,创建的线程工作时的性质都是一样的。
通过重写run()方法,将需要线程开启后执行的任务放入其中就可以通过调用线程对象的start()或者run()方法来实现开启线程。
注:start()方法与run()方法的区别:
start()方法是另外开启一个线程来执行run()里面的内容,run()方法则是当前线程获取run()里面的执行权。一个是开启新的线程执行任务,一个是当前线程执行任务。
书中的话是:Thread.start()方法是告诉“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法,这个过程就是让系统安排一个时间来调用Thread中的run()方法。从而使线程运行,启动线程,具有异步的效果。
线程的调用具有随机性,因为多线程的实现是在极短的时间内cpu在不同线程间跳动来实现的(对于线程来说就是获取了执行权)。因为执行权的获取、失去都具有随机性,所以导致了线程的调用也具有随机性。
Thread的构造方法:
由于Thread类实现了Runnable接口。所以构造方法中Thread(Runnable targer)不仅可以传入Runnable接口对象,还可以传入Thread对象(java三大特性:多态),这样可以将当前线程的任务交由其他线程来执行。
线程的安全性问题:
当多线程操作共享数据时,如果不进行处理(加锁),就会出现数据不同步的现象,也就是说所谓的线程不安全问题。
synchronized关键字解决线程不安全问题:可以在线程的run()上加上关键字synchronized来为线程加锁,当第一个线程执行到此处时,会进行加锁处理,在其运行完之前不会放开锁,此时其他线程要想获得执行权,只能排队等到当前线程运行完run()代码(放开锁)后才能获得线程执行权并重新加锁。
注:synchronized关键字可以在任意对象和方法上加锁,而这种加锁的代码称为:“互斥区”或“临界区”。
注:非线程安全:多个线程对同一个对象中的同一个实例变量进行操作时出现值被更改、值不同步的情况,进而影响程序的执行流程。
i--与System.out.println()的异常:
println()源码:
若自增自减操作是在println()操作外进行的,那么就有可能会出现非线程安全问题。
注:
在某些JVM中,i--的操作要经历下面三个步骤:
- 取得原有i值
- 计算i-1
- 对i进行赋值
若上述任何一步中,有多个线程同时访问,那么就可能会出现多线程问题。
1.3CurrentThread()方法
currentThread()方法可以返回正在执行的线程的调用信息。
注:
Thread.currentThread()获得的始终是执行start()方法的线程(或者说当前运行的线程)。
而this获得的都是run()方法所在线程对象的名称。
Thread.currentThread().getName()和this.getName()方法都可以获取线程的名字。
1.4isAlive()方法
方法isAlive()是判断当前线程是否处于活动状态。
活跃状态:线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活的”。
1.5sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
1.6getId()方法
getId()方法的作用是获取线程的唯一标识。
1.7停止线程
线程停止:在线程处理完任务之前,停掉正在做的操作,也就是放弃当前操作。
在java中有三种方法可以实现线程的停止:
- 使用退出标志,使线程正常退出,也就是当run方法执行完后线程终止。
- 使用stop()强行终止线程。但是不推荐这个方法,因为stop()与suspend()以及resume()一样,都是作废过期的方法,使它们产生不可预期的结果。
- 使用interrupt()方法中断线程。该方法不会终止一个线程,还需要加入一个判断才能够完成线程的终止。
停不了的线程:
使用interrupt()方法停止线程,但是interrupt()方法并不像循环中的break关键字一样可以立即起效,interrupt()方法仅仅是在当前线程中打了一个停止的标记,并没有真正停止线程。
判断线程是否停止:
在Java的SDK中,Thread.java类里提供了两种方法判断线程是否停止
根据JDK来看interrupted()方法会清除停止状态,也就是说当前如果出现暂停,若调用多次后第二次之后一定返回false。
isInterrupted()方法不会清除停止状态。
2017/12/15补充:
interrupt()仅仅是为当前线程添加一个状态,并不影响线程的执行。
即,线程此时的状态为打断状态,但是线程是正常执行run()里面的内容的。
若想实现真正的打断,可以通过interrupted()或者isInterrupted()与判断语句实现打断。
能停止的线程——异常法:
在线程执行的run()中直接抛出一个异常就可以将当前在运行的线程停止。
在沉睡中停止:
若某一线程处于sleep状态,此时将该线程停止的话就会抛出异常java.lang.InterruptedException:sleep interrupted进而停止线程。
注:若先进入interrupt中,在进行睡眠还是会爆出该异常。
2017/12/15补充:
注:实际上是,只要线程在进入睡眠状态时,打断状态是true,就会报出异常。
能停止的线程——暴力停止:
stop()方法停止线程是暴力的,该方法会直接停止线程。
方法stop()与java.lang.ThreadDead异常:
调用stop()方法时,会抛出java.lang.ThreadDeath异常,但在通常情况下,此异常不需要显示地捕捉。
注:如果不特地注明ThreadDeath方法时,是不会爆出异常进入catch中的。
stop()方法已经作废,因为如果强制停止线程会导致一些清理的工作无法完成,另外一种情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。
注:stop()方法会杀死线程,然后将锁释放,此时若run()中的共享数据修改没有完成,可能会出现数据不同步的现象。
使用return()停止线程:
可以结合interrupt()与判断线程是否停止的两个方法,通过判断实现是否通过return()停止线程。
注:仅仅使用return;即可。return;可以结束方法的执行。
1.8暂停线程
suspend()与resume()的使用:
在java中,使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。
suspend()与resume()的缺点——独占:
在使用suspend与resume方法时,可能会导致公共的同步对象的独占发生,使得其他线程无法访问公共同步对象。
即若在临界区(互斥区)中停止了线程,那么其他线程在resume()前将永远无法获得锁。
注:println()方法内部使用了synchronized关键字,这可能导致在一些测试中出现异常。
suspend()与resume()的缺点——不同步:
若方法使用不当,可能会导致出现数据更新的不同步现象。
1.9yield方法
yield()方法的解释:放弃当前cpu资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不能确定,有可能刚刚放弃,但马上又获得CPU时间片。
1.10线程的优先级
在操作系统中,线程是有优先级划分的,优先级较高的线程会得到相对较多的资源。也就是说CPU会优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定下次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源代码如下:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
在java中,线程的优先级分为1~10这10个优先级,如果小于1或者大于10,则JDK抛出异常IllegalArgumentException()。
JDK常用下面三个量来预置定义优先级的值。
线程优先级的继承特性:
在java中线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
优先级具有规则性:
线程的执行顺序与线程代码的执行顺序无关,与线程的优先级有关,优先级越高越先执行。
优先级具有随机性:
随机性意味着优先级高的线程不一定总是能优先执行完。
优先级越高的线程执行速度越快。
1.11守护线程
在Java中有两种线程,一种为用户线程,一种为守护线程。
守护线程是一种特殊的线程,它具有“陪伴”的含义,当进程中不存在非守护线程时,则守护线程自动销毁。
典型的守护线程就是垃圾回收线程。
当进程中没有线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。
任何一个守护线程,都是JVM中所有的非守护线程的保姆,只要当前JVM实例中存在任何一个非守护线程,且没有结束,守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束工作。
通过setDaemon(Boolean)方法来设置守护线程。
注:该方法一定要在start()方法前调用,不然会抛出异常。
本文内容是书中内容兼具自己的个人看法所成。可能在个人看法上会有诸多问题(毕竟知识量有限,导致认知也有限),如果读者觉得有问题请大胆提出,我们可以相互交流、相互学习,欢迎你们的到来,心成意足,等待您的评价。