看了文章一,应该对线程有个初步的认识了,我再简单的介绍一下线程使用中的一些内容.
线程通过几种机制进入 Java 程序。除了用 Thread 构造器中显式创建线程之外,还可以用许多其它机制创建线程:
· AWT 和 Swing
· RMI
· java.util.TimerTask 工具
· servlet 和 JSP 技术
我只是简单的提一下后两种.
TimerTask.
JDK 1.3 中,TimerTask 工具被引入到 Java 语言。这个便利的工具让您可以稍后在某个时间执行任务(例如,即从现在起十秒后运行一次任务),或者定期执行任务(即,每隔十秒运行任务)。
实现 Timer 类非常简单:它创建一个计时器线程,并且构建一个按执行时间排序的等待事件队列。
TimerTask 线程被标记成守护程序线程,这样它就不会阻止程序退出。
servlet 容器创建多个线程,在这些线程中执行 servlet 请求。作为 servlet 编写者,您不知道(也不应该知道)您的请求会在什么线程中执行;如果同时有多个对相同 URL 的请求入站,那么同一个 servlet 可能会同时在多个线程中是活动的。
当编写 servlet 或 JavaServer Pages (JSP) 文件时,必须始终假设可以在多个线程中并发地执行同一个 servlet 或 JSP 文件。必须适当同步 servlet 或 JSP 文件访问的任何共享数据;这包括servlet 对象本身的字段。
下面说一下共享数据的访问,这也是线程的一个很重要的部分.
要使多个线程在一个程序中有用,它们必须有某种方法可以互相通信或共享它们的结果。
让线程共享其结果的最简单方法是使用共享变量。它们还应该使用同步来确保值从一个线程正确传播到另一个线程,以及防止当一个线程正在更新一些相关数据项时,另一个线程看到不一致的中间结果。有关两个关键字:synchronized 和volatile以及java锁,我已经在上一篇文章里面提到了,这里我再深入的介绍一下.
先复述一下java锁,
每个 Java 对象都有一个相关的锁。同一时间只能有一个线程持有 Java 锁。当线程进入
synchronized 代码块时,线程会阻塞并等待,直到锁可用,当它可用时,就会获得这个锁,然后执行代码块。当控制退出受保护的代码块时,即到达了代码块末尾或者抛出了没有在 synchronized 块中捕获的异常时,它就会释放该锁。
Java 锁定合并了一种互斥形式。每次只有一个线程可以持有锁。锁用于保护代码块或整个方法,必须记住是锁的身份保护了代码块,而不是代码块本身,这一点很重要。一个锁可以保护许多代码块或方法。
反之,仅仅因为代码块由锁保护并不表示两个线程不能同时执行该代码块。它只表示如果两个线程正在等待相同的锁,则它们不能同时执行该代码。
下面这个实例代码展示就是后面这种情况. ,两个线程可以同时不受限制地执行 setLastAccess() 中的 synchronized 块,因为每个线程有一个不同的 thingie 值。因此,synchronized 代码块受到两个正在执行的线程中不同锁的保护。
public class SyncExample {
public static class Thingie {
private Date lastAccess;
public synchronized void setLastAccess(Date date) {
this.lastAccess = date;
}
}
public static class MyThread extends Thread {
private Thingie thingie;
public MyThread(Thingie thingie) {
this.thingie = thingie;
}
public void run() {
thingie.setLastAccess(new Date());
}
}
public static void main() {
Thingie thingie1 = new Thingie(),
thingie2 = new Thingie();
new MyThread(thingie1).start();
new MyThread(thingie2).start();
}
}
同步方法
创建 synchronized 块的最简单方法是将方法声明成 synchronized。这表示在进入方法主体之前,
调用者必须获得锁:
public class Point {
public synchronized void setXY(int x, int y) {
this.x = x;
this.y = y;
}
}
对于普通的 synchronized 方法,这个锁是一个对象,将针对它调用方法。对于静态 synchronized方法,这个锁是与 Class 对象相关的监控器,在该对象中声明了方法。
仅仅因为 setXY() 被声明成 synchronized 并不表示两个不同的线程不能同时执行 setXY(),只要它们调用不同的 Point 实例的 setXY() 就可同时执行。对于一个 Point 实例,一次只能有一个线程执行 setXY(),或 Point 的任何其它 synchronized 方法。
如以下代码样本所示,SimpleCache.java 使用 HashMap 为对象装入器提供了一个简单的高速缓存。
load() 方法知道怎样按对象的键装入对象。在一次装入对象之后,该对象就被存储到高速缓存中,这样以后的访问就会从高速缓存中检索它,而不是每次都全部地装入它。对共享高速缓存的每个访问都受到 synchronized 块保护。由于它被正确同步,所以多个线程可以同时调用 getObject 和clearCache 方法,而没有数据损坏的风险。
public class SimpleCache {
private final Map cache = new HashMap();
public Object load(String objectName) {
// load the object somehow
}
public void clearCache() {
synchronized (cache) {
cache.clear();
}
}
public Object getObject(String objectName) {
synchronized (cache) {
Object o = cache.get(objectName);
if (o == null) {
o = load(objectName);
cache.put(objectName, o);
}
}
return o;
}
}
由于线程执行的计时是不确定的,我们需要小心,以控制线程对共享数据的访问。否则,多个并发线程会互相干扰对方的更改,从而损坏数据,或者其它线程也许不能及时看到对共享数据的更改。通过使用同步来保护对共享变量的访问,我们可以确保线程以可预料的方式与程序变量进行交互。每个 Java 对象都可以充当锁,synchronized 块可以确保一次只有一个线程执行由给定锁保护的synchronized 代码。
那么什么时候需要使用同步呢?
要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。
可见性同步的基本规则是在以下情况中必须同步:
· 读取上一次可能是由另一个线程写入的变量
· 写入下一次可能由另一个线程读取的变量
许多 Java 类,包括 String、Integer 和 BigDecimal,都是不可改变的:一旦构造之后,它们的状态就永远不会更改。如果某个类的所有字段都被声明成 final,那么这个类就是不可改变的。(实际上,许多不可改变的类都有非 final 字段,用于高速缓存以前计算的方法结果,如String.hashCode(),但调用者看不到这些字段。)不可改变的类使并发编程变得非常简单。因为不能更改它们的字段,所以就不需要担心把状态的更
改从一个线程传递到另一个线程。在正确构造了对象之后,可以把它看作是常量。
同样,final 字段对于线程也更友好。因为 final 字段在初始化之后,它们的值就不能更改,所以当在线程之间共享 final 字段时,不需要担心同步访问。
什么时候不需要同步呢?
在某些情况中,您不必用同步来将数据从一个线程传递到另一个,因为 JVM 已经隐含地为您执行同步。这些情况包括:
· 由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时
· 访问 final 字段时
· 在创建线程之前创建对象时
· 线程可以看见它将要处理的对象时
再介绍一个概念,死锁.
只要您拥有多个进程,而且它们要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。
最常见的死锁形式是当线程 1 持有对象 A 上的锁,而且正在等待与 B 上的锁,而线程 2 持有对象 B 上的锁,却正在等待对象 A 上的锁。这两个线程永远都不会获得第二个锁,或者释放第一个锁。它们只会永远等待下去。要避免死锁,应该确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。
同步准则
当编写 synchronized 块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险的风险方面大有帮助:
代码块要简短Synchronized 块应该简短 — 在保证相关数据操作的完整性的同时,
尽量简短。把不随线程变化的预处理和后处理移出 synchronized 块。
不要阻塞。不要在 synchronized 块或方法中调用可能引起阻塞的方法,如
则呢arInputStream.read()。
在持有锁的时候,不要对其它对象调用方法。这听起来可能有些极端,但它消除了最常见的死锁源头。
有关线程的编程还有很多内容,我只是介绍一个初级的入门知识,我也是才学习线程了,文章主要内容是根据网上流传的资料改变整理.
核心: 勇敢进取年轻的心