1. 并发
并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力。如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善。现代的PC都有多个CPU或一个CPU中有多个核。是否能合理运用多核的能力将成为一个大规模应用程序的关键。
之所以并发号称能较快的完成某些任务是因为这些任务能被分组成多个子任务,并且这些子任务能被并行的执行。所以一个任务的实际运行效率将受限于该任务中能并行执行的部分。
一个程序理论上可达到的最高并发性能可通过以下一个被称为Amdahl 定律来计算出:
设F为一个程序中不能被并行执行的百分比,N是处理器的数量,那么理论上该程序能获得的最高并发性能将可能是1/ (F+ ((1-F)/n)).
一个JAVA程序默认以一个线程运行在自己的进程中。JAVA语言通过Thread相关代码来支持线程。JAVA程序可通过Thread这个类来创建线程。从JAVA1.5起,在java.util.concurrent中提供了改进的并发库。
JAVA提供了“锁” 机制来保护代码片段免于被多个线程在同一时刻运行。最简单的锁住一个方法或一个类就是在该方法或类前添加synchronized关键字
在Java中synchronized关键字保证了:
- 在同一时刻,只有单个线程能访问这块代码
- 每个进入这同步代码块的线程都将能看到之前持有相同锁进入的线程的所做的改动
对于线程间的可靠通讯及互斥访问 来说,同步是非常必要的
你可以在定义方法时使用synchronized关键字。这个关键字保证了同一时刻只有一个线程能运行这个方法。其他同样调用了这个方法的线程将被阻塞直到第一个线程离开这个方法。
1 | public synchronized void critial() { |
2 | // some thread critical stuff |
3 | // here |
4 | } |
你同样也能用synchronized关键字来保护方法中的代码块。这块代码将由一个关键对象来保护,该关键对象可以是个string或其他object。这个关键对象就被称为 lock。所有被相同lock保护的代码在同一时刻只能被单个线程执行。
举例来说,以下的数据结构将保证同时只有单个线程可以访问add和next方法
01 | package de.vogella.pagerank.crawler; |
02 |
03 | import java.util.ArrayList; |
04 | import java.util.List; |
05 | /*** Data structure for a web crawler. Keeps track of the visited sites and keeps |
06 | * a list of sites which needs still to be crawled. |
07 | * @author Lars Vogel |
08 | */ |
09 | <pre> public class CrawledSites { |
10 | private List<String> crawledSites = new ArrayList<String>(); |
11 | private List<String> linkedSites = new ArrayList<String>(); |
12 |
13 | public void add(String site) { |
14 | synchronized ( this ) { |
15 | if (!crawledSites.contains(site)) { |
16 | linkedSites.add(site); |
17 | } |
18 | } |
19 | } |
20 | /** * Get next site to crawl. Can return null (if nothing to crawl) */ |
21 | public String next() { |
22 | if (linkedSites.size() == 0 ) { |
23 | return null ; |
24 | } |
25 | synchronized ( this ) { |
26 | <em> // Need to check again if size has changed</em> |
27 | if (linkedSites.size() > 0 ) { |
28 | String s = linkedSites.get( 0 ); |
29 | linkedSites.remove( 0 ); |
30 | crawledSites.add(s); |
31 | return s; |
32 | } |
33 | return null ; |
34 | } |
35 | } |
36 |
37 | } |
如果一个变量声明时使用了volatile 关键字,那么该关键字保证了,任何读取该变量的线程都将读到最新写进该变量的值。但volatile 关键字不会保证变量上的任何互斥访问
As of Java 5 write access to a volatile variable will also update non-volatile variables which were modified by the same thread. This can also be used to update values within a reference variable, e.g. for a volatile variable person. In this case you must use a temporary variable person and use the setter to initialize the variable and then assign the temporary variable to the final variable. This will then make the address changes of this variable and the values visible to other threads.
因为在JAVA 5中,对一个声明了volatile 的变量进行写操作会导致该写操作所在该线程之前所有的对非volatile 变量的修改进行同步更新。这也可用来更新引用类型变量。例如,有个volatile 类型的变量person。你必须创建一个临时变量person,然后调用SETTER方法来初始化这变量然后将临时变量赋值给final变量。 这会使这个变量的地址发生改变并且此变量的值对于其他线程变为可见。
JAVA内存模型描述了线程内存与主存间的通讯关系。
JAVA内存模型定义了线程内的内存改变将怎样传递到其他线程的规则,同样也定义了线程内存与主存进行同步的场景,也描述了哪些操作属于原子操作及操作间的顺序。
一个原子操作是指一个执行时不会被其他操作影响到的最小单位的操作。
JAVA语言规范保证了对一个变量的读和写操作都是原子的(除了LONG和DOUBLE类型的变量)。对于LONG或DOUBLE类型的变量,只有当这些变量声明时使用了volatile关键字才是原子的
假设定义了一个INT变量I,那么在java中,I++操作不是一个原子操作。同样,这对于其他数字类型的变量也都不是一个原子操作。
I++操作先从I中读取了当前值(这是个原子操作)然后再让它加上1写回(原子操作)。但是在读和写这两个操作间,I的值有可能被改变。
从JAVA1.5起,JAVA提供了原子变量,例如AtomicInteger 或 AtomicLong 都提供了类似 getAndDecrement()
, getAndIncrement()
及 getAndSet()等原子方法。
(全文完)如果您喜欢此文请点赞,分享,评论。
- 原创文章转载请注明出处:Java并发编程基础