您的位置:首页 > 编程语言 > Java开发

java多线程小探

2016-07-14 16:15 411 查看
递增共享计数器

https://www6.software.ibm.com/developerworks/cn/education/java/j-threads/tutorial/j-threads-6-5.html

通常,如果正在保护一个基本变量(如一个整数),有时只使用 volatile 就可以侥幸过关。但是,如果变量的新值派生自以前的值,就必须使用同步。为什么?考虑这个类:

public class Counter {

private int counter = 0;

public int get() { return counter; }

public void set(int n) { counter = n; }

public void increment() {

set(get() + 1);

}

}

当我们要递增计数器时,会发生什么?请看 increment() 的代码。它很清楚,但不是线程安全的。如果两个线程试图同时执行 increment(),会发生什么?计数器也许会增加 1,也许增加 2。令人惊奇的是,把 counter 标记成 volatile 没有帮助,使 get() 和 set() 都变成 synchronized 也没有帮助。

设想计数器是零,而两个线程同时执行递增操作代码。这两个线程会调用 Counter.get(),并且看到计数器是零。现在两个线程都对它加一,然后调用 Counter.set()。如果我们的计时不太凑巧,那么这两个线程都看不到对方的更新,即使 counter 是 volatile,或者 get() 和 set() 是 synchronized。现在,即使计数器递增了两次,得到的值也许只是一,而不是二。

要使递增操作正确运行,不仅 get() 和 set() 必须是 synchronized,而且 increment() 也必需是 synchronized!否则,调用 increment() 的线程可能会中断另一个调用 increment() 的线程。如果您不走运,最终结果将会是计数器只增加了一次,不是两次。同步 increment() 防止了这种情况的发生,因为整个递增操作是原子的。

当循环遍历 Vector 的元素时,同样如此。即使同步了 Vector 的方法,但在循环遍历时,Vector 的内容仍然会更改。如果要确保 Vector 的内容在循环遍历时不更改,必须同步整个代码块。

=======================================================================================================

不变性和 final 字段

许多 Java 类,包括 String、Integer 和 BigDecimal,都是不可改变的:一旦构造之后,它们的状态就永远不会更改。如果某个类的所有字段都被声明成 final,那么这个类就是不可改变的。(实际上,许多不可改变的类都有非 final 字段,用于高速缓存以前计算的方法结果,如 String.hashCode(),但调用者看不到这些字段。)

不可改变的类使并发编程变得非常简单。因为不能更改它们的字段,所以就不需要担心把状态的更改从一个线程传递到另一个线程。在正确构造了对象之后,可以把它看作是常量。

同样,final 字段对于线程也更友好。因为 final 字段在初始化之后,它们的值就不能更改,所以当在线程之间共享 final 字段时,不需要担心同步访问。

=======================================================================================================

什么时候不需要同步

在某些情况中,您不必用同步来将数据从一个线程传递到另一个,因为 JVM 已经隐含地为您执行同步。这些情况包括:

由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时

访问 final 字段时

在创建线程之前创建对象时

线程可以看见它将要处理的对象时

=======================================================================================================

同步准则

当编写 synchronized 块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险的风险方面大有帮助:

使代码块保持简短。Synchronized 块应该简短 ― 在保证相关数据操作的完整性的同时,尽量简短。把不随线程变化的预处理和后处理移出 synchronized 块。

不要阻塞。不要在 synchronized 块或方法中调用可能引起阻塞的方法,如 InputStream.read()。

在持有锁的时候,不要对其它对象调用方法。这听起来可能有些极端,但它消除了最常见的死锁源头。

=======================================================================================================

每个 Java 程序都使用线程,不论您知道与否。如果您正在使用 Java UI 工具箱(AWT 或 Swing)、Java Servlet、RMI、JavaServer Pages 或 Enterprise JavaBeans 技术,您可能没有意识到您正在使用线程。

在许多情况中,您可能想要显式地使用线程以提高程序的性能、响应速度或组织。这些情况包括:

在执行耗时较长的任务时,使用户界面的响应速度更快

利用多处理器系统以并行处理多个任务

简化仿真或基于代理的系统的建模

执行异步或后台处理

虽然线程 API 很简单,但编写线程安全的程序并不容易。在线程之间共享变量时,必须非常小心,以确保正确同步了对它们的读写访问。当写一个可能接下来由另一个线程读取的变量,或者读取可能由另一个线程写过的变量时,必须使用同步以确保对数据的更改在线程之间是可见的。

当使用同步保护共享变量时,必须确保不仅使用了同步,而且读取器和写入器在同一个监控器上同步。而且,如果依赖对象的状态在多个操作中保持相同,或者依赖多个变量互相保持一致(或者,与它们过去的值一致),那么必须使用同步来强制实现这一点。但简单地同步一个类中的每一个方法并不能使它变成线程安全的 ― 只会使它更容易发生死锁。

=======================================================================================================

例子:

(一)即时显示当前时间:

import java.util.*;

import java.text.*;

import java.applet.Applet;

import java.awt.Graphics;

public class Clock extends Applet implements Runnable {

Thread clockThread;

public void start() {

if (clockThread == null) {

clockThread = new Thread(this, "Clock");

clockThread.start();

}

}

public void run() {

while (clockThread != null) {

repaint();

try {

clockThread.sleep(1000);

} catch (InterruptedException e) {

}

}

}

public void paint(Graphics g) {

//Date now = new Date();

// now.getHours()+now.getMinutes()+now.getSeconds()(过时的方法)

DateFormat df = new SimpleDateFormat("EEEE-MMMM-dd-yyyy HH:mm:ss");

Calendar c = Calendar.getInstance();

g.drawString(df.format(c.getTime()), 5, 10);

g.drawString(DateFormat.getTimeInstance().format(c.getTime()), 5, 30);

g.drawString(DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA)

.format(c.getTime()), 5, 50);

g.drawString(DateFormat.getInstance().format(c.getTime()), 5, 70);

g.drawString(DateFormat.getDateTimeInstance(DateFormat.FULL,

DateFormat.FULL).format(c.getTime()), 5, 90);

g.drawString(DateFormat.getDateTimeInstance().format(c.getTime()), 5,

110);

}

public void stop() {

clockThread.stop();

clockThread = null;

}

public static void main(String[] args) {

new Clock().start();

}

}



(二)生产者、消费者问题

class CubbyHole {

private int seq;

private boolean available = false; // 信号量

/**

* 用synchronized来标识的区域或方法即为对象互斥锁锁住的部分。

*

* 如果一个程序内有两个或以上的方法使用synchronized标志,

*

* 则它们 在同一个对象互斥锁管理之下。

*

* 一般情况下,都使用synchronized关键字在方法的层次上实现对共享资源操作的同步,

*

* 很少使用volatile关键字声明共享变量。

*/

public synchronized int get() {

while (available == false) {

try {

// wait()方法的作用是让当前线程释放其所持有的对象互斥锁,进入wait队列(等待队列)

wait();

// waits for notify() call from Producer

} catch (InterruptedException e) {

}

}

available = false;

// notify()/notifyAll()方法的作用是唤醒一个或所有正在等待队列中等待的线程,

// 并将它(们)移入等待同一个对象互斥锁的队列。

notify();

// 需要指出的是: notify()/notifyAll()方法和wait()方

// 法都只能在被声明为synchronized的方法或代码段中调用。

return seq;

}

public synchronized void put(int value) {

while (available == true) {

try {

wait();

// waits for notify() call from consumer

} catch (InterruptedException e) {

}

}

seq = value;

available = true;

notify();

}

}

class Producer extends Thread {

private CubbyHole cubbyhole;

private int number;

public Producer(CubbyHole c, int number) {

cubbyhole = c;

this.number = number;

}

public void run() {

for (int i = 0; i < 10; i++) {

cubbyhole.put(i);

System.out.println("Producer @" + this.number + " put: " + i);

try {

sleep((int) (Math.random() * 100));

} catch (InterruptedException e) {

}

}

}

}

class Consumer extends Thread {

private CubbyHole cubbyhole;

private int number;

public Consumer(CubbyHole c, int number) {

cubbyhole = c;

this.number = number;

}

public void run() {

int value = 0;

for (int i = 0; i < 10; i++) {

value = cubbyhole.get();

System.out.println("Consumer #" + this.number + " got: " + value);

}

}

}

public class ProducerConsumerTest {

public static void main(String args[]) {

CubbyHole c = new CubbyHole();

Producer p1 = new Producer(c, 1);

Consumer c1 = new Consumer(c, 1);

//Consumer c2 = new Consumer(c, 2);

p1.setPriority(Thread.MAX_PRIORITY);

c1.setPriority(Thread.NORM_PRIORITY);

//c2.setPriority(Thread.NORM_PRIORITY);

p1.start();

c1.start();

//c2.start();

}

}

输出结果:

Producer @1 put: 0

Consumer #1 got: 0

Producer @1 put: 1

Consumer #1 got: 1

Producer @1 put: 2

Consumer #1 got: 2

Producer @1 put: 3

Consumer #1 got: 3

Producer @1 put: 4

Consumer #1 got: 4

Producer @1 put: 5

Consumer #1 got: 5

Producer @1 put: 6

Consumer #1 got: 6

Producer @1 put: 7

Consumer #1 got: 7

Producer @1 put: 8

Consumer #1 got: 8

Producer @1 put: 9

Consumer #1 got: 9

收藏于 2009-11-24
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 java