Java多线程编程核心技术(一)Java多线程技能
1、进程和线程
一个程序就是一个进程,而一个程序中的多个任务则被称为线程。
进程是表示资源分配的基本单位,线程是进程中执行运算的最小单位,亦是调度运行的基本单位。
举个例子:
打开你的计算机上的任务管理器,会显示出当前机器的所有进程,QQ,360等,当QQ运行时,就有很多子任务在同时运行。比如,当你边打字发送表情,边好友视频时这些不同的功能都可以同时运行,其中每一项任务都可以理解成“线程”在工作。
2、使用多线程
在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便地进行多线程编程。实现多线程编程的方式有两种,一种是继承 Thread 类,另一种是实现 Runnable 接口。使用继承 Thread 类创建线程,最大的局限就是不能多继承,所以为了支持多继承,完全可以实现 Runnable 接口的方式。需要说明的是,这两种方式在工作时的性质都是一样的,没有本质的区别。如下所示:
1.继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
//...
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2.实现 Runnable 接口
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
//...
}
}).start();
}
Thread.java 类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用 Thread 中的 run() 方法,也就是使线程得到运行,多线程是异步的,线程在代码中启动的顺序不是线程被调用的顺序。
Thread构造方法
Thread() 分配新的 Thread 对象。 |
Thread(Runnable target) 分配新的 Thread 对象。 |
Thread(Runnable target, String name) 分配新的 Thread 对象。 |
Thread(String name) 分配新的 Thread 对象。 |
Thread(ThreadGroup group, Runnable target) 分配新的 Thread 对象。 |
Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。 |
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。 |
Thread(ThreadGroup group, String name) 分配新的 Thread 对象。 |
3、实例变量与线程安全
自定义线程类中的实例变量针对其他线程可以有共享与不共享之分。当每个线程都有各自的实例变量时,就是变量不共享。共享数据的情况就是多个线程可以访问同一个变量。来看下面的示例:
public class MyThread implements Runnable {
private int count = 5;
@Override
public void run() {
count--;
System.out.println("线程"+Thread.currentThread().getName()+" 计算 count = "+count);
}
}
以上代码定义了一个线程类,实现count变量减一的效果。运行类Runjava代码如下:
public class Ruu {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread,"A");
Thread b = new Thread(myThread,"B");
Thread c = new Thread(myThread,"C");
Thread d = new Thread(myThread,"D");
Thread e = new Thread(myThread,"E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
打印结果如下:
线程C 计算 count = 3
线程B 计算 count = 3
线程A 计算 count = 2
线程D 计算 count = 1
线程E 计算 count = 0
线程C,B的打印结果都是3,说明C和B同时对count进行了处理,产生了“非线程安全问题”。而我们想要的得到的打印结果却不是重复的,而是依次递减的。
在某些JVM中,i--的操作要分成如下3步:
- 取得原有变量的值。
- 计算i-1。
- 对i进行赋值。
在这三个步骤中,如果有多个线程同时访问,那么一定会出现非线程安全问题。
解决方法就是使用 synchronized 同步关键字 使各个线程排队执行run()方法。修改后的run()方法:
public class MyThread implements Runnable {
private int count = 5;
@Override
synchronized public void run() {
count--;
System.out.println("线程"+Thread.currentThread().getName()+" 计算 count = "+count);
}
}
打印结果:
线程B 计算 count = 4
线程C 计算 count = 3
线程A 计算 count = 2
线程E 计算 count = 1
线程D 计算 count = 0
关于System.out.println()方法
先来看System.out.println()方法源码:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
虽然println()方法内部使用 synchronized 关键字,但如下所示的代码在执行时还是有可能出现非线程安全问题的。
System.out.println("线程"+Thread.currentThread().getName()+" 计算 count = "+count--);
原因在于println()方法内部同步,但 i-- 操作却是在进入 println()之前发生的,所以有发生非线程安全问题的概率。
4、多线程方法
1. currentThread()方法
currentThread()方法可返回代码段正在被哪个线程调用的信息。
Thread.currentThread().getName()
2. isAlive()方法
方法isAlive()的功能是判断当前的线程是否处于活动状态。
thread.isAlive();
3. sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行)。这个"正在执行的线程"是指this.currentThread()返回的线程。
Thread.sleep()
4. getId()方法
getId()方法的作用是取得线程的唯一标识。
thread.getId()
5、停止线程
停止线程是在多线程开发时很重要的技术点。停止线程并不像break语句那样干脆,需要一些技巧性的处理。
在Java中有以下3种方法可以终止正在运行的线程:
1)使用退出标志,使线程正常退出,也就是当run()方法完成后线程停止。
2)使用stop()方法强行终止线程,但是不推荐使用这个方法,因为该方法已经作废过期,使用后可能产生不可预料的结果。
3)使用interrupt()方法中断线程。
1.暴力法停止线程
调用stop()方法时会抛出 java.lang.ThreadDeath 异常,但在通常的情况下,此异常不需要显示地捕捉。
try {
myThread.stop();
} catch (ThreadDeath e) {
e.printStackTrace();
}
方法stop()已经被作废,因为如果强制让线程停止线程则有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的情况。示例如下:
public class UserPass {
private String username = "aa";
private String password = "AA";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
synchronized public void println(String username, String password){
this.username = username;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.password = password;
}
public static void main(String[] args) throws InterruptedException {
UserPass userPass = new UserPass();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
userPass.println("bb","BB");
}
});
thread.start();
Thread.sleep(500);
thread.stop();
System.out.println(userPass.getUsername()+" "+userPass.getPassword());
}
}
运行结果:
bb AA
2.异常法停止线程
使用interrupt()方法并不会真正的停止线程,调用interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。
那我们如何判断该线程是否被打上了停止标记,Thread类提供了两种方法。
interrupted() 测试当前线程是否已经中断。
isInterrupted() 测试线程是否已经中断。
interrupted() 方法 不止可以判断当前线程是否已经中断,而且可以会清除该线程的中断状态。而对于isInterrupted() 方法,只会判断当前线程是否已经中断,不会清除线程的中断状态。
仅靠上面的两个方法可以通过while(!this.isInterrupted()){}
对代码进行控制,但如果循环外还有其它语句,程序还是会继续运行的。这时可以抛出异常从而使线程彻底停止。示例如下:
public class MyThread extends Thread {
@Override
public void run() {
try {
for (int i=0; i<50000; i++){
if (this.isInterrupted()) {
System.out.println("已经是停止状态了!");
throw new InterruptedException();
}
System.out.println(i);
}
System.out.println("不抛出异常,我会被执行的哦!");
} catch (Exception e) {
// e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread =new MyThread();
myThread.start();
Thread.sleep(100);
myThread.interrupt();
}
}
打印结果:
...
2490
2491
2492
2493
已经是停止状态了!
注意
如果线程在sleep()状态下被停止,也就是线程对象的run()方法含有sleep()方法,在此期间又执行了thread.interrupt()
方法,则会抛出java.lang.InterruptedException: sleep interrupted
异常,提示休眠被中断。
3.return法停止线程
return法很简单,只需要把异常法中的抛出异常更改为return即可。代码如下:
public class MyThread extends Thread {
@Override
public void run() {
for (int i=0; i<50000; i++){
if (this.isInterrupted()) {
System.out.println("已经是停止状态了!");
return;//替换此处
}
System.out.println(i);
}
System.out.println("不进行return,我会被执行的哦!");
}
}
不过还是建议使用“抛异常”来实现线程的停止,因为在catch块中可以对异常的信息进行相关的处理,而且使用异常能更好、更方便的控制程序的运行流程,不至于代码中出现多个return,造成污染。
6、暂停线程
暂停线程意味着此线程还可以恢复运行。在Java多线程中,可以使用 suspend() 方法暂停线程,使用 resume()方法恢复线程的执行。
这俩方法已经和stop()一样都被弃用了,因为如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。示例如下:
public class MyThread extends Thread {
private Integer i = 0;
@Override
public void run() {
while (true) {
i++;
System.out.println(i);
}
}
public Integer getI() {
return i;
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread =new MyThread();
myThread.start();
Thread.sleep(100);
myThread.suspend();
System.out.println("main end");
}
}
打印结果:
...
3398
3399
3400
3401
执行上段程序永远不会打印main end。出现这样的原因是,当程序运行到 println() 方法内部停止时,PrintStream对象同步锁未被释放。方法 println() 源代码如下:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
这导致当前PrintStream对象的println() 方法一直呈“暂停”状态,并且锁未被myThread线程释放,而主线程中的代码System.out.println("main end") 还在傻傻的排队等待,导致迟迟不能运行打印。
使用 suspend() 和 resume() 方法也容易因为线程的暂停而导致数据不同步的情况,示例如下:
public class UserPass2 {
private String username = "aa";
private String password = "AA";
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setValue(String username, String password){
this.username = username;
if (Thread.currentThread().getName().equals("a")) {
Thread.currentThread().suspend();
}
this.password = password;
}
public static void main(String[] args) throws InterruptedException {
UserPass2 userPass = new UserPass2();
new Thread(new Runnable() {
@Override
public void run() {
userPass.setValue("bb","BB");
}
},"a").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(userPass.getUsername()+" "+userPass.getPassword());
}
},"b").start();
}
}
打印结果:
bb AA
7、yield()方法
yield() 方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
8、线程的优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是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抛出异常。
从JDK定义的3个优先级常量可知,线程优先级默认为5。
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
线程优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
线程优先级具有规则性,线程的优先级与在代码中执行start()方法的顺序无关,与优先级大小有关。
线程优先级具有随机性,CPU尽量使线程优先级较高的先执行完,但无法百分百肯定。也就是说,线程优先级较高的不一定比线程优先级较低的先执行。
9、守护线程
在Java中有两种线程,一种是用户线程,一种守护线程。
什么是守护线程?守护线程是一种特殊的线程,当进程中不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有了存在的必要了,自动销毁。可以简单地说:任何一个守护线程都是非守护线程的保姆。
如何设置守护线程?通过Thread.setDaemon(false)设置为用户线程,通过Thread.setDaemon(true)设置为守护线程。如果不设置属性,默认为用户线程。
thread.setDaemon(true);
示例如下:
public class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true){
i++;
System.out.println("i="+i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("我离开后thread对象也就不再打印了");
}
}
打印结果:
i=1
i=2
i=3
i=4
i=5
我离开后thread对象也就不再打印了
参考与总结
《Java多线程编程核心技术》高洪岩 著
本文主要介绍了Thread类的API,算是为学习多线程更深层次知识打下一些基础,文章若有错误请在评论区指正。
扩展
Java多线程编程核心技术(一)Java多线程技能的更多相关文章
- Java多线程编程核心技术---单例模式与多线程
立即加载/饿汉模式 立即加载就是使用类的时候已经将对象创建完毕. public class MyObject { //立即加载方式==饿汉模式 private static MyObject myOb ...
- Java多线程编程核心技术(三)多线程通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
- Java多线程编程核心技术(二)对象及变量的并发访问
本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...
- Java多线程编程核心技术
Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...
- 《Java多线程编程核心技术》知识梳理
<Java多线程编程核心技术> @author ergwang https://www.cnblogs.com/ergwang/ 文章末尾附pdf和png下载链接 第1章 Java多线程技 ...
- Java多线程编程核心技术---学习分享
继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...
- Java多线程编程核心技术---对象及变量的并发访问(二)
数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...
- 《Java多线程编程核心技术》推荐
写这篇博客主要是给猿友们推荐一本书<Java多线程编程核心技术>. 之所以要推荐它,主要因为这本书写得十分通俗易懂,以实例贯穿整本书,使得原本抽象的概念,理解起来不再抽象. 只要你有一点点 ...
- 《java多线程编程核心技术》(一)使用多线程
了解多线程 进程和多线程的概念和线程的优点: 提及多线程技术,不得不提及"进程"这个概念.百度百科对"进程"的解释如下: 进程(Process)是计算机中的程序 ...
随机推荐
- 学习用Node.js和Elasticsearch构建搜索引擎(6):实际项目中常用命令使用记录
1.检测集群是否健康. curl -XGET 'localhost:9200/_cat/health?v' #后面加一个v表示让输出内容表格显示表头 绿色表示一切正常,黄色表示所有的数据可用但是部分副 ...
- JS 文本框格式化
页面: <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script> & ...
- [20180918]文件格式与sql_id.txt
[20180918]文件格式与sql_id.txt --//记录测试中遇到的一个问题.这是我在探究SQL*Net more data from client遇到的问题.--//就是实际oracle会把 ...
- [福大软工] Z班 团队Beta阶段成绩汇总
Beta敏捷冲刺得分 队伍名 1 2 3 4 5 总分 Dipper 10 10 10 10 10 50 SWSD 9 9 9 9 7 43 五成胜算 10 10 10 10 10 50 人月神教 0 ...
- 修改mysql默认端口
最初,我将mysql端口改成了3307,现在需要将其改3306端口,已改好,做个记录 首先:借助资源监视器,找到对应的端口,查看对应的Pid,然后打开任务管理器,点击服务,找到对应的服务器,将其服务停 ...
- DBUtils温习1
1.简介 Commons DBUtIls是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的简单封装,学习成本极低,但是使用DBUtils却极大的简化了dao层的开发,少些了很多的jdb ...
- 基于mycat高可用方案——数据库负载
引言 传统企业级应用一般采取单台数据库,吞吐所有应用的读写,随着互联网的高速发展,以及微服务架构越来越普及,往往采用分库分表来支撑高速增长的大量业务数据吞吐.分库分表主要有两种方式:水平分表和垂直分库 ...
- ElasticSearch(二):允许外网连接服务配置
上一篇文章的配置,只能在本机使用,但是要想为集群或者其他的机器连接,则需要做以下配置: 一.修改/opt/elasticsearch-6.4.0/config/elasticsearch.yml文件 ...
- java 关于打断点
比如:前台传过来参数中文乱码,需要decode才可以使用, 判断问题. debug 在 DispatcherServlet OncePerRequestFilter 打断点, 查看前台过来的中文在哪里 ...
- [MCM] K-mean聚类与DBSCAN聚类 Python
import matplotlib.pyplot as plt X=[56.70466067,56.70466067,56.70466067,56.70466067,56.70466067,58.03 ...