快精灵印艺坊 您身边的文印专家
广州名片 深圳名片 会员卡 贵宾卡 印刷 设计教程
产品展示 在线订购 会员中心 产品模板 设计指南 在线编辑
 首页 名片设计   CorelDRAW   Illustrator   AuotoCAD   Painter   其他软件   Photoshop   Fireworks   Flash  

 » 彩色名片
 » PVC卡
 » 彩色磁性卡
 » 彩页/画册
 » 个性印务
 » 彩色不干胶
 » 明信片
   » 明信片
   » 彩色书签
   » 门挂
 » 其他产品与服务
   » 创业锦囊
   » 办公用品
     » 信封、信纸
     » 便签纸、斜面纸砖
     » 无碳复印纸
   » 海报
   » 大篇幅印刷
     » KT板
     » 海报
     » 横幅

java 线程安全

四种方法 sychronized要害字
sychronized method(){}
sychronized (objectReference) {/*block*/}
static synchronized method(){}
sychronized(classname.class)
其中1和2是代表锁当前对象,即一个对象就一个锁,3和4代表锁这个类,即这个类的锁
要注重的是sychronized method()不是锁这个函数,而是锁对象,即:假如这个类中有两个方式都是sychronized,那么只要有两个线程共享一个该类的reference,每个调用这两个方式之一,不管是否同一个方式,都会用这个对象锁进行同步。锁类的3和4类推,即该类的不同reference调用了sychronized区段的咚咚就会受类锁的控制

还有,假如两个函数调用的先后顺序不能被打断,那么可以有个专门的锁对象来完成这个任务:
class MyLock
{
synchronized getLock()
{
//####还没写完
}
}


五个等级 参见effective java Item 52 : Document thread safety

immutable 不可变对象
thread-safe 线程安全的,可以放心使用,如java.util.Timer
conditionally thread-safe 条件线程安全的,如Vector和Hashtable,一般是安全的,除非存在几个方式调用之间的顺序不能被打断,这时可以用额外的锁来完成
thread-compatible 可以使用synchronized (objectReference)来协助完成对线程的调用
thread-hostile 不安全的

wait & notifyAll

在循环中使用wait 使用notifyAll而不是notify

pipe

java中也有pipe的,四个类:PipedInputStream, PipedInputReader, PipedOutputStream, PipedOutputWriter 下面是一段生产者消费者的代码(摘自core javaII):

/* set up pipes */
PipedOutputStream pout1 = new PipedOutputStream();
PipedInputStream pin1 = new PipedInputStream(pout1);
PipedOutputStream pout2 = new PipedOutputStream();
PipedInputStream pin2 = new PipedInputStream(pout2);
/* construct threads */
Producer prod = new Producer(pout1);
Filter filt = new Filter(pin1, pout2);
Consumer cons = new Consumer(pin2);
/* start threads */
prod.start(); filt.start(); cons.start();



注重

long 和double是简朴类型中两个特别的咚咚:java读他们要读两次,所以需要同步
死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。如果线程 "A" 获得了刀,而线程 "B" 获得了叉。线程 A 就会进入阻塞状态来等待获得叉,而线程 B 则阻塞来等待 A 所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是异常困难的,但只要按照下面几条规则去设计系统,就能够避免死锁问题:

让所有的线程按照同样的顺序获得一组锁。这种方式消除了 X 和 Y 的拥有者分别等待对方的资源的问题。

将多个锁组成一组并放到同一个锁下。前面死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。

将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判定是否整个银器集合中的对象锁都可获得。假如是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。

最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前具体设计系统能够帮助你避免难以发现死锁的问题。
Volatile 变量. volatile 要害字是 Java 语言为优化编译器设计的。以下面的代码为例:
class VolatileTest {

public void foo() {
boolean flag = false;

if(flag) {
//this could happen
}
}
}




一个优化的编译器可能会判定出 if 部分的语句永远不会被执行,就根本不会编译这部分的代码。假如这个类被多线程访问, flag 被前面某个线程设置之后,在它被 if 语句测试之前,可以被其他线程重新设置。用 volatile 要害字来声明变量,就可以告诉编译器在编译的时候,不需要通过猜测变量值来优化这部分的代码。




无法访问的线程 有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。在 Java 编程中 IO 就是这类问题最好的例子。当线程因为对象内的 IO 调用而阻塞时,此对象应当仍能被其他线程访问。该对象通常有责任取消这个阻塞的 IO 操作。造成阻塞调用的线程经常会令同步任务失败。假如该对象的其他方式也是同步的,当线程被阻塞时,此对象也就相称于被冷冻住了。其他的线程由于不能获得对象的锁,就不能给此对象发消息(例如,取消 IO 操作)。必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方式。尽管这种方式需要花费一些注重力来保证结果代码安全运行,但它答应在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。

调用 yield() 方式能够将当前的线程从处理器中移出到预备就绪队列中。另一个方式则是调用 sleep() 方式,使线程放弃处理器,并且在 sleep 方式中指定的时间间隔内睡眠。
正如你所想的那样,将这些方式随意放在代码的某个地方,并不能够保证正常工作。假如线程正拥有一个锁(因为它在一个同步方式或代码块中),则当它调用 yield() 时不能够释放这个锁。这就意味着即使这个线程已经被挂起,等待这个锁释放的其他线程依然不能继承运行。为了缓解这个问题,最好不在同步方式中调用 yield 方式。将那些需要同步的代码包在一个同步块中,里面不含有非同步的方式,并且在这些同步代码块之外才调用 yield。

另外一个解决方式则是调用 wait() 方式,使处理器放弃它当前拥有的对象的锁。假如对象在方式级别上使同步的,这种方式能够很好的工作。因为它仅仅使用了一个锁。假如它使用 fine-grained 锁,则 wait() 将无法放弃这些锁。此外,一个因为调用 wait() 方式而阻塞的线程,只有当其他线程调用 notifyAll() 时才会被唤醒。

在进行多线程编程时,常常要使用同步互斥机构,但java本身没有提供的同步互斥机构,仅提供了两个与同步互斥有关的方式:wait()和notify(),可以用来设计信号量类:mySemaphore,它是按照Dijkstra提出的计数信号量的思想设计的。

mySemaphore有两个最重要的成员方式:P()和V()。这两个方式实际就实现了信号量的P操作和V操作。详细描述如下:

public synchronized void P(){

semaphore--;

if(semaphore<0){

try{

wait();

}catch(InterruptedException ie){}

}

}

public synchronized void V(){

semaphore++;

if(semaphore<=0)

notify();

}

其中,semaphore变量记录了信号量的状态,wait()方式相称于block原语,用于阻塞线程的执行,notify()方式相称于wakeup原语,用于唤醒线程恢复运行。由于这两个方式定义为synchronized,这样java虚拟机可保证这两个方式的原子执行,从而实现了P、V操作。

二、管道

并发程序的多个线程之间的通讯通常是使用管道进行,jdk提供了两个管道类:PipedInpuStream和PipedOutputStream,前者用于输入,后者用于输出。这两种管道应该是能够多次连接和关闭,在实现过程中,却发现它们在关闭后,不能重新建立连接。经过仔细调试后,发现jdk的源代码在处理关闭时释放资源存在着缺陷,因此需要编写自己的管道类:MyPipedInputStream和MyPipedOutputStream。这两个类直接从InputStream和OutputStream继续而来,其成员方式与实现基本与PipedInputStream和PipedOutputStream一致,只是在处理关闭时,将类中的成员变量的值恢复成未连接时的初始值。另外,原有的管道了提供的管道容量只有1024个字节,在传输数据量较大时,可能会发生溢出,而在自己的管道类中可以任意设置管道容量,例如可以根据需要把管道容量设为64KB。以下仅给出了相应的关闭例程:

1.MyPipedInputStream

public void close() throws IOException {

in = -1;

out = 0;

closedByReader = true;

connected = false;

closed = true;

buffer = new byte[PIPE_SIZE];

}

2.MyPipedOutputStream

public void close() throws IOException {

if (sink != null) {

sink.receivedLast();

sink.closed = true;

}

sink = null;

connected = false;

}






返回类别: 教程
上一教程: Java学习过程应该深入理解的一些重点
下一教程: DODS学习日记(六)

您可以阅读与"java 线程安全"相关的教程:
· Java开发中的线程安全选择与Swing[Z]
· 如何准确实现多线程安全的singleton patterns
· Java服务器端编程安全必读
· 编写线程安全的JSP程序
· JAVA何以保网络安全
    微笑服务 优质保证 索取样品