`
zjeers
  • 浏览: 36699 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

如何实现线程安全?

阅读更多
四种方式    sychronized关键字

   1. sychronized method(){}

   2. sychronized (objectReference) {/*block*/}

   3. static synchronized method(){}

   4. 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

   1. immutable   不可变对象

   2. thread-safe 线程安全的,可以放心使用,如java.util.Timer

   3. conditionally thread-safe 条件线程安全的,如Vector和Hashtable,一般是安全的,除非存在几个方法调用之间的顺序不能被打断,这时可以用额外的锁来完成

   4. thread-compatible 可以使用synchronized (objectReference)来协助完成对线程的调用

   5. 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最新高薪面试题库.docx

    在Java中如何实现线程安全? 什么是继承?Java中的继承有哪些特点? 什么是多态?Java中的多态有哪些实现方式? 什么是抽象类?Java中的抽象类有哪些特点? 什么是接口?Java中的接口有哪些特点? 什么是泛型?Java...

    c# 线程安全队列的用法原理及使用示例

    什么是线程安全? 答:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等...

    线程安全型双向链表的实现

    操作系统c++编程实现安全型双向链表,线程的创建,利用线程对链表进行增删改操作,并检验结果是否正确

    使用C++11实现线程安全的单例模式

    C++11实现线程安全的单例代码和测试代码,包含singleton.h,main.cpp,希望帮助到大家。

    c++11 多线程编程——如何实现线程安全队列

    线程安全队列的接口文件如下: #include template class threadsafe_queue { public: threadsafe_queue(); threadsafe_queue(const threadsafe_queue&); threadsafe_queue& operator=(const threadsafe_queue&...

    线程安全型栈

    编写一个线程安全的“栈”,该“栈”能够实现多个线程同时正确的入栈或出栈,通过创建线程实现对“栈”这个临界资源的保护。实现多个线程对该栈的读写操作。

    C++实现线程安全队列

    使用互斥变量技术完成C++安全队列,同时编写测试代码进行此队列的测试。

    Qt两种方法实现多线程并安全结束线程及QMutex加锁Qt单例化实现

    Qt两种方法实现多线程的开启,及子线程的安全结束线程,及QMutex加锁,Qt单例化实现

    c#高效的线程安全队列ConcurrentQueueT的实现

    众所周知,在普通的非线程安全队列有两种实现方式: 1.使用数组实现的循环队列。 2.使用链表实现的队列。 先看看两种方式的优劣:  .Net Farmework中的普通队列Queue的实现使用了第一种方式,缺点是当队列空间不足会...

    C++日志库-线程安全

    C++线程安全日志库-Win32接口实现,博客讲解:https://www.cnblogs.com/swarmbees/ -&gt;C++线程安全日志库-Win32接口实现

    线程安全型队列的实现

    操作系统课程设计线程安全型队列,利用读者优先机制实现互斥。

    Java进阶学习——Java多线程知识的理解

    4.2.如何实现线程安全?5.线程池5.1.为什么需要线程池?5.2.创建线程池5.2.1.创建可缓存线程池5.2.2.创建定时周期性的任务执行线程池5.2.3.创建带有返回值的线程池5.2.4.创建ForkJoinPool线程池5.2.4.1.使用...

    基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip

    基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip基于...

    c++单例日志类(c++11,线程安全)

    c++单例日志类,c++11跨平台,线程安全,自己一直在用在改善。有好的指导,可以留言学习交流。

    java多线程安全性基础介绍.pptx

    java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...

    vb6实现安全多线程的ActiveX组件

    一个用于vb6实现安全多线程的ActiveX组件,非常值得下载。

    什么是线程?Java中如何创建和管理线程?(java面试题附答案).txt

    synchronized 关键字:用于实现线程的同步,确保多个线程之间的安全访问共享资源。 Lock 接口和 ReentrantLock 类:提供更灵活的线程同步机制。 Executor 框架和线程池:用于管理和调度线程的执行。 通过合理地创建...

    c_safe_lib:c数据结构线程安全库

    c_safe_lib c线程安全库,这个项目的目的是创造一套可以工程使用的c数据结构库,在实际工程中一套优秀高效的数据结构库是必不可少的。主要解决俩个方面:1.线程安全2.跨平台 linux 、windows实现原理功能状态实现原理...

Global site tag (gtag.js) - Google Analytics