前言

  对于某些问题,如果能够并行的执行程序中的多个部分,则回变得非常方便甚至必要,这些部分要么看起来是并发执行,要么是在多处理环境下同时执行。并行编辑可以使程序执行速度得到极大提高,或者为设计某些类型的程序提供更易用的模型。当并行执行的任务彼此开始产生互相干涉时,实际的并发问题就发生了。

一、并发的多面性

  并发解决的问题答题上可以分为“速度”和“设计可管理新”两种。

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章 并发的更多相关文章

  1. Java编程思想 第21章 并发

    这是在2013年的笔记整理.现在重新拿出来,放在网上,重新总结下. 两种基本的线程实现方式 以及中断 package thread; /** * * @author zjf * @create_tim ...

  2. Java编程思想——第17章 容器深入研究 读书笔记(三)

    七.队列 排队,先进先出. 除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: ad ...

  3. Java编程思想——第17章 容器深入研究(two)

    六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add ...

  4. Java编程思想学习(十六) 并发编程

    线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础. 多核心CPU可以真正实现多个任务并行执行,单核心CPU程序其实不是 ...

  5. Java编程思想-第四章练习题

    练习1:写一个程序,打印从1到100的值 public class Print1To100{ public static void main(String args[]){ for(int i = 1 ...

  6. java编程思想笔记(第一章)

    Alan Kay 第一个定义了面向对象的语言 1.万物皆对象 2.程序是对象的集合,他们彼此通过发送消息来调用对方. 3.每个对象都拥有由其他对象所构成的存储 4.每个对象都拥有其类型(TYpe) 5 ...

  7. Java编程思想笔记(第二章)

    第二章  一切都是对象 尽管Java是基于C++的,但相比之下,Java是一种更纯粹的面向对象程序设计语言. c++和Java都是杂合型语言(hybird language) 用引用(referenc ...

  8. Java编程思想第七章复用类

    7.1组合语法 在一个类中引入多个对象,以提高代码的复用性与功能. 7.2继承语法 使用继承子类可以获得,导出类可以获得基类的成员(变量与方法). 注:这里注意权限控制,若基类中的成员为默认权限,只有 ...

  9. Java编程思想——第14章 类型信息(一)

    运行时类型信息使得你可以在程序运行时发现和使用类型信息.Java是如何让我们在运行时识别对象和类的信息得呢? 主要有两种方式:1.传统RTTI,他假定我们在编译期间已经知道了所有类型:2.反射,它允许 ...

随机推荐

  1. MarkDown快速入门(typora)

    MarkDown快速入门(typora) 1.代码块: //代码块语法: ​```java ​```shell 1.java代码 package com.yjx.jdbc import java.sq ...

  2. 小而美的GIF生成神器ScreenToGif

    起因 在写计算机图形学博客时,需要讲解一个算法,课本上抽象的语言未免让人读着头大,还在老师给的PPT中有代码的演示,我就想将演示做出GIF动图帮助读者理解算法,其实之前浪迹博客园的时候就发现有许多博主 ...

  3. 五分钟学会conda常用命令

    文章目录 conda常用命令 1. 获取版本号 2. 获取帮助 3. 环境管理 4. 分享环境 5. 包管理 conda常用命令 1. 获取版本号 conda --version 或 conda -V ...

  4. 线上服务器CPU彪高的调试方式

    原文内容来自于LZ(楼主)的印象笔记,如出现排版异常或图片丢失等问题,可查看当前链接:https://app.yinxiang.com/shard/s17/nl/19391737/2fee7b91-f ...

  5. Go语言底层知识总结【新手必学】

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:YID_152接下来我们来给大家分享想go的基础知识要点:如果你也刚学 ...

  6. SpringMVC生成的验证码图片不显示

    近期用SSM框架写一个项目,登录模块需要生成验证码图片,我把相关的代码写好了之后传到 jsp ,但是图片不显示,查看控制台显示404,反复查询了一下代码并没有发现任何问题,代码如下: @Control ...

  7. 【GZOI 2019】特技飞行

    Problem Description 公元 \(9012\) 年,Z 市的航空基地计划举行一场特技飞行表演.表演的场地可以看作一个二维平面直角坐标系,其中横坐标代表着水平位置,纵坐标代表着飞行高度. ...

  8. Ubuntu Server 16.04 LTS上怎样安装下载安装Nginx并启动

    场景 Linux-安装 Ubuntu Server 16.04 X64(图文教程详细版): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...

  9. Android动态添加碎片

    我们编写一个能够用过按钮动态更替碎片的APP,首先在主页上显示第一个碎片,点击按钮后可以替换到第二个碎片,或者删除已经替换掉的第二个碎片. 一.MainActivity.java import and ...

  10. Sql: Oracle paging

    --书分类目录kind --涂聚文 Geovin Du create table geovindu.BookKindList ( BookKindID INT PRIMARY KEY, BookKin ...