Java编程思想——第21章 并发
前言
对于某些问题,如果能够并行的执行程序中的多个部分,则回变得非常方便甚至必要,这些部分要么看起来是并发执行,要么是在多处理环境下同时执行。并行编辑可以使程序执行速度得到极大提高,或者为设计某些类型的程序提供更易用的模型。当并行执行的任务彼此开始产生互相干涉时,实际的并发问题就发生了。
一、并发的多面性
并发解决的问题答题上可以分为“速度”和“设计可管理新”两种。
1.更快的执行
想要更快的执行,需要多处理器,并发是用于多处理器编程的基本工具。这是使用强有力的多处理器Web服务器的常见情况,在为每个请求分配一个线程的程序中,它可以将大量的用户请求分布到多个CPU上。
当并发运行在单处理器时,开销可能要比顺序执行开销大,因为增加了上下文切换的代价。但是阻塞使得问题变得不同:如果程序中的某个任务因为该程序控制范围之外的某些条件(如:I/O)而导致不能继续执行,那么这个任务线程阻塞了。如果没有并发,则整个程序都将停止下来。因此,如果没有任务会阻塞,在单线程处理器机器上使用并发就没有任何意义。单线程并发一般使用在窗口操作。
Java所使用的这种并发系统会共享诸如内存和I/O这样的资源,因此编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。
2.改进代码设计
简单举个例子吧,游戏里面多个npc,各自走各自的。
二、基本的线程机制
并发编程是我们可以将程序划分为多个分离的、独立运行的任务。通过多线程机制,这些独立任务中每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单进程可以拥有多个并发执行的任务,但是程序使得每个人物都想有自己的CPU。其底层机制是切分CPU时间。
1.定义任务
线程可以驱动任务,因此你需要一种描述任务的方式,这可以由Runnable接口来提供。要想定义任务,只需实现Runnable接口并编写run()方法,使得该任务可以执行你的命令。
public class RunnableDemo implements Runnable {
int i =100;
@Override
public void run() {
while (i-->0){
Thread.yield();
}
}
}
任务的run()方法总会以循环的形式使任务一直进行下去,在run()中对静态方法Thread.yield()的调用是对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)的一种建议,它声明:“我已经完成生命周期中最重要的部分,此刻是切换给其他任务执行一段时间的大好时机。
当Runnable导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处——它不会产生任何内在的线程能力。要实现县城行为,你必须显式地将一个任务附着到线程了。
2.Thread类
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器:
public static void main(String[] args) {
Thread t = new Thread(new RunnableDemo());
t.start();
//其他方法
}
Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必须的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。start()方法实际上,产生的是对Runnable.run()的调用。程序会同时运行两个方法,main()里面的其他方法和Runnable.run()是程序中与其他线程“同时”执行代码。
3.使用Executor
执行器(Excutor)将为你管理Thread对象,简化了并发编程。相当于中介。但是由于一下原因不是很推荐
推荐:ThreadPoolExecutor使用 。
4.从任务中产生返回值
Runnable是执行工作的独立任务,但是它不返回任何值。如果希望任务中返回值那么应当实现Callable接口。Callable具有泛型,它的类型参数标识从call()方法中返回的值,并且必须使用ExectorService.submit()方法调用:
public class CallableDemo {
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1));
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(executorService.submit(new TaskWithResult(i)));
}
for (Future<String> fs : results) {
try {
//得到返回值
System.out.println(fs.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
} class TaskWithResult implements Callable<String> {
private int id; TaskWithResult(int id) {
this.id = id;
} @Override
public String call() {
return "result of TaskWithResult" + id;
}
}
5.休眠
影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行对应的时间。
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
6.优先级
线程的优先级将该线程的重要性传递给调度器,调度器倾向于让优先权最高的线程先执行。但这并不意味着优先级低的线程得不到执行(优先权高的等待不会导致死锁),优先权低的线程仅仅是执行频率较低。在绝大多数时间里,所有程序都应该是默认优先级,试图操作线程优先级通常是一种错误。
@Override
public void run() {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY );
Thread.currentThread().getPriority();
}
最好在run方法里面设置优先级,而且最好就用那三种常用的级别 :
Thread.MAX_PRIORITY
Thread.NORM_PRIORITY Thread.MIN_PRIORITY
7.让步
当工作做了一段时间可以让别的线程使用cpu了。此时可以使用Thread.yield()给线程调度一个暗示(只是一个暗示,不一定被采纳)。
8.后台线程
所谓后台线程,是指在程序运行时,在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。当所有非后台线程结束时,程序也就终止了,同时会杀死进程中所有的后台线程。
设置后台线程:
public static void main(String[] args) {
Thread t = new Thread(new RunnableDemo());
//这句设置线程为后台线程
t.setDaemon(true);
t.start();
}
9.编码的变体
在非常简单的情况下,你可能会希望使用直接哦那个Thread继承这种可替换的方式:
public class SimpleThrad extends Thread {
private int countDown = 5; /**
* 依然需要实现run方法
*/
@Override
public void run() {
while (true) {
System.out.println(this);
if (--countDown == 0) {
return;
}
}
}
}
但是不提倡还是提倡使用ThreadPoolExecutor实现线程管理。
10.术语
从上面的各种情况中你可以看到实际你没有对Thread的控制权。你创建任务,并通过某种方式将一个线程附着到任务上,以使得这个线程可以驱动任务。在Java中Thread类自身不执行任何操作,它只是驱动赋予给他的任务,将任务和线程区分开能让你更好的理解线程。
11.加入一个线程
一个线程可以在其他线程上调用join()方法,其效果是等待一段时间直到第二线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,知道目标线程t结束才恢复。
也可也在join()加上超时参数(毫秒),使得目标函数在参数时间外还未结束,join()方法依旧能返回。对join()方法的调用可以被中断,做法是在调用线程上调用interrppt()方法,并加try-catch。这里不举例子了因为在使用过程中CycliBarrier要比join更好。
三、共享受限资源
对于并发任务,你需要某种方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种情况。
1.解决共享资源竞争
防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。通常这是通过在代码前面加上以挑锁语句来实现的,这使得在一段时间内只有一个任务可以运行这段代码。因为锁语句产生了一种互相排斥的效果,所以这种机制撑场成为互斥量(mutex)。
synchronized,当代码要执行被synchronized保护的代码块时,先检查锁是否可用,再获得锁,执行代码块,释放锁。共享资源一般是以对象形式存在的内存片段,也可以是文件,I/O,打印机等。要控制对共享资源的访问,需要先把它包装进一个对象。然后把所有调用这个资源的方法标记为synchronized。如果某个任务在调用标记为synchronized的方法,那么那么在这个线程从该方法返回前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。
对所有对象,自动含有单一锁,当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放锁后才能被调用。注意,在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止其他任务直接访问域。
对每个类,也有一个锁。所以 synchronized static 方法可以在类的范围内防止对static数据的并发访问。
Java编程思想——第21章 并发的更多相关文章
- Java编程思想 第21章 并发
这是在2013年的笔记整理.现在重新拿出来,放在网上,重新总结下. 两种基本的线程实现方式 以及中断 package thread; /** * * @author zjf * @create_tim ...
- Java编程思想——第17章 容器深入研究 读书笔记(三)
七.队列 排队,先进先出. 除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: ad ...
- Java编程思想——第17章 容器深入研究(two)
六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add ...
- Java编程思想学习(十六) 并发编程
线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础. 多核心CPU可以真正实现多个任务并行执行,单核心CPU程序其实不是 ...
- Java编程思想-第四章练习题
练习1:写一个程序,打印从1到100的值 public class Print1To100{ public static void main(String args[]){ for(int i = 1 ...
- java编程思想笔记(第一章)
Alan Kay 第一个定义了面向对象的语言 1.万物皆对象 2.程序是对象的集合,他们彼此通过发送消息来调用对方. 3.每个对象都拥有由其他对象所构成的存储 4.每个对象都拥有其类型(TYpe) 5 ...
- Java编程思想笔记(第二章)
第二章 一切都是对象 尽管Java是基于C++的,但相比之下,Java是一种更纯粹的面向对象程序设计语言. c++和Java都是杂合型语言(hybird language) 用引用(referenc ...
- Java编程思想第七章复用类
7.1组合语法 在一个类中引入多个对象,以提高代码的复用性与功能. 7.2继承语法 使用继承子类可以获得,导出类可以获得基类的成员(变量与方法). 注:这里注意权限控制,若基类中的成员为默认权限,只有 ...
- Java编程思想——第14章 类型信息(一)
运行时类型信息使得你可以在程序运行时发现和使用类型信息.Java是如何让我们在运行时识别对象和类的信息得呢? 主要有两种方式:1.传统RTTI,他假定我们在编译期间已经知道了所有类型:2.反射,它允许 ...
随机推荐
- lnmp1.2支持ThinkPhp pathinfo及rewrite
一.pathinfo支持方法 1.2版本系统已经自动生成了一个pathinfo的配置文件,但实测不可用,所以我们先找打这个文件并修改其内容,文件路径为:/usr/local/nginx/pathinf ...
- 如何用Jmeter做接口测试
Jmeter介绍&测试准备: Jmeter介绍:Jmeter是软件行业里面比较常用的接口.性能测试工具,下面介绍下如何用Jmeter做接口测试以及如何用它连接MySQL数据库. 前期准备:测试 ...
- django基础之day09,创建一个forms表单组件进行表单校验,知识点:error_messages,label,required,invalid,局部钩子函数,全局钩子函数, forms_obj.cleaned_data,forms_obj.errors,locals(), {{ forms.label }}:{{ forms }},{{ forms.errors.0 }}
利用forms表单组件进行表单校验,完成用户名,密码,确认密码,邮箱功能的校验 该作业包含了下面的知识点: error_messages,label,required,invalid,局部钩子函数,全 ...
- 在 Java 中如何比较日期?
在 Java 中有多种方法可以比较日期,日期在计算机内部表示为(long型)时间点--自1970年1月1日以来经过的毫秒数.在Java中,Date是一个对象,包含多个用于比较的方法,任何比较两个日期的 ...
- iOS Privacy Policy
This application respects and protects the privacy of all users who use the service. In order to pro ...
- Python—解析HTML页面(HTMLParser)
HTMLParser类的定义及常用方法 类的定义 HTMLParser主要是用来解析HTML文件(包括HTML中无效的标记). 参数convert_charrefs表示是否将所有的字符引用自动转化为U ...
- GetPrivateProfileString() 当 key 包含空格时,需要进行转义
使用 GetPrivateProfileString() 方法可以方便的读取 ini 格式文件中的内容,如: [section] tommy = worker 使用 C# 读取如下: 1. 先引入 G ...
- Oracle11g在虚拟机win7上的详细安装过程(包括win7在虚拟机上的安装)
http://www.imsdn.cn/这个是镜像文件的下载地址,之前下载雨林和深度的VM识别不了. 这个好了之后就可以去这个网址下看安装教程很详细.https://blog.csdn.net/u01 ...
- Vue自定义指令使用场景
当你第一次接触vue的时候,一定会使用到其中的几个指令,比如:v-if.v-for.v-bind...这些都是vue为我们写好的,用起来相当的爽.如果有些场景不满足,需要我们自己去自定义,那要怎么办呢 ...
- 解决Entity 实体类中加了@Id 注解后仍然出现org.hibernate.AnnotationException: No identifier specified for entity 错误
启动报错如下图所示: 解决方案: 查看网上的资料,大部分都说在实体类中没有添加加主键的注解@Id,这个是必须的.但是我的实体类中明明已经添加了@Id,为什么还会报这个错误呢? 后来检查了很久,发现是我 ...