Synchronize与JAVA内存模型

 2019-12-10 15:57  阅读(746)
文章分类:Java Core

synchronized 来控制安全性, synchronizedsynchronized 方法 和synchronized 快,使用synchronized 块时, 一定要显示的获得该对象的锁(如synchronized (object))而方法则不需要。

  JAVA 的内存模型是对每一个进程有一个主内存, 每个线程有自己的内存, 他们从主内存中取数据,然后计算, 再存入主内存中。

  并发问题如下:如果多个线程同事操作同一数据, A线程从主内存中取的I的值为1, 然后进行加1操作,这时B线程也取I的值, 进行加2操作, 然后A存入2到主内存中, B也存入, 这样就覆盖了A的值(同数据库中的并发问题一样)。

  解决办法是用synchronized , 如用synchronized(I)。被synchronized 修饰的方法(块)把以下三步操作当成一个原子操作:取数据, 操作数据, 存数据。 我们知道原子操作是不可以被打断的, 所以其保证了数据一致性,这样同一时间只有一个线程再执行, 对性能有一定的影响。这也是synchronized 的第二个作用:保证同一时间只有一个线程在运行。当实现SOCKET连接的时候经常用到.

  JAVA中规定对非FLOAT, LONG的原始类型的取和存操作为原子操作。其实就是对一个字(32位)的取,存位原始操作, 因为FLOAT, LONG为两个字节的长度,所以其取, 存为非原子操作。 如果想把他们也变为原子操作, 可以用VOLATILE关键字来修饰.

**synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。

  1. synchronized 方法**:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
    public synchronized void accessVal(int newVal);
    synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
    在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
    synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。**
    2. synchronized块:通过 synchronized关键字来声明synchronized 块。语法如下:
    synchronized(syncObject) {
    //允许访问控制的代码
    }
      synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
    notify()及notifyAll()是Object的方法,与Object的wait()方法配合使用,而且这三个方法必须在同步块中调用.
    如下:
    在线程1中执行如下代码

    synchronized(obj1) //1.进入同步块
    {
    try {

    obj1.wait(); //2.进入暂停状态
    }catch (InterruptedException exp) {}
    }
    1.当前同步对象(monitor)为obj1,obj1是任一实例,若是同步方法,则同步对象是this.进入同步块后,obj1为锁定状态,锁定状态对obj1本身无任何影响,而其它线程执行到同一代码时,因不能获得锁,处于Block状态,一旦解锁,被block的线程自动继续执行.
    2.调用obj1.wait()有两个作用,一是使线程进入等待状态,二是解锁obj1,这时被block的线程将获得锁.线程1要退出等待必须要由其它线程显式的调用obj1.notify()或notifyAll()方法.


    synchronized(obj1)
    {

    obj1.notify(); //3. 向wait的线程发通知信息

    }

    若其它线程执行到此时,线程1处于wait状态,则wait状态解除,解除后,若线程1若得到锁就能继续执行,若有多个线程处于obj1的wait状态,notify将随机选一个线程激活,而notifyAll是同时解除所有的wait状态.
    notifyAll()让等待某个对象K的所有线程离开阻塞状态,
    notify()随机地选取等待某个对象K的线程,让它离开阻塞状态。
    notify(),notifyAll()非常有用,在一个synchronized(lockObj)块中当调用lockObj.wait()时,线程就已经将锁放开来了,这时当另外一个线程调用lockObj.notify()时,等待的线程就会继续执行下去了。这是一种非常高效的线程同步机制。如果没有他,用sleep()来同步的话就太浪费时间了。
    一个简单的例子:
    thread1 receive data
    thread2 pase received data
    lockObj是buf
    当buf中没有数据时,thread2中调用buf.wait释放锁,让thread1有机会执行。
    当thread1收到数据时,调用buf.notify()通知thread1去处理收到的数据。
    如果在同步块入口点阻塞,不须其它线程调用notify(),调了也没效果,同步块能自动获得锁
    如果是wait造成了阻塞,须用notfiy()激活,这两个方法是配合使用

    notify****、notifyAll、wait :**
    主要是为了解决持有监视器钥匙的线程暂停等待另一线程完成时可能发生死锁的问题。wait()方法使调用线程等待,直到发生超时或另一线程调用同一对象的notify()或notifyAll()方法。wait() 方法的用法如下:wait()或wait(long timeoutPeriodInMilliseconds)前者等待被通知,后者等待超时或通知。线程调用wait()方法时,释放所持有的钥匙让另一等待线程进入监视区。notify()方法只唤醒一个等待线程,而notifyAll()唤醒所有等待该监视器的线程。注意,这些方法只能在监视区内调用。否则会输出一种RuntimeException类型的IllegaMonitorStateException异常状态。
    够详细清楚的吧。
    总之wait()让线程等待,notify()和notifyall()激活某个等待线程,其实就是撤销该线程的中断状态,从而使他们有机会再次运行
    有时会遇到如下问题,程序的一部分在写数据,另一部分读数据,但有时会出现读部分超前写部分,
    这就是典型的产生者/消耗者问题.
    .wait:是一个线程睡眠,直到在相同的对象上调用notify或notifyAll
    .notify:启动第一个在相同对象调用wait的线程
    .notifyAll:启动在相同的对象调用wait的所有线程
点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Synchronize与JAVA内存模型

相关推荐