• Java多线程的同步方式和锁机制


    Object.wait(miliSec)/notify()/notifyAll()

    线程调用wait()之后可以由notify()唤醒,如果指定了miliSec的话也可超时后自动唤醒。wait方法的调用会让当前线程放弃已经获取的object锁标志位,比如在同步代码块synchronized中调用wait(),则表示当前线程被唤醒之后需要重新获取同步代码块的锁。另外wait/notify由于要操作对象的锁标志位,因此必须在synchronized代码块中调用,否则会抛出运行时异常IllegalMonitorStateException。


    wait/notify机制出现之前,生产/消费实现模型的同步一般通过while(true)轮询实现,弊端是极大耗用CPU资源做无用的轮询。在调用wait方法之前,线程需要获取当前实例对象的锁,执行wait方法返回之后,线程释放掉对象锁并进入block状态;其他线程在调用notify方法之前,也需要获取当前实例对象的锁,执行notify方法时,如果有多个线程处理block状态则从中按某规则选择一个唤醒,notify方法调用之后不会立即释放锁,要等线程的同步方法执行完毕之后才释放对象锁,因此一次notify调用只会唤醒一个线程,其他block的线程依旧处理block状态。

     1 public class App1 extends Thread {
     2     private Object lock;
     3     public App1(Object lock) {
     4         super();
     5         this.lock = lock;
     6     }
     7     @Override
     8     public void run() {
     9         try {
    10             synchronized (lock) {
    11                 System.out.println(Thread.currentThread().getName()
    12                                   + " : start to wait.");
    13                 lock.wait();
    14                 System.out.println(Thread.currentThread().getName() 
    15                                                + " : wait ends, execute again.");
    16             }
    17         } catch (Exception e) {}
    18     }
    19 }
    20 public class App2 extends Thread {
    21     private Object lock;
    22     public App2(Object lock) {
    23         super();
    24         this.lock = lock;
    25     }
    26     @Override
    27     public void run() {
    28         synchronized (lock) {
    29             System.out.println(Thread.currentThread().getName() 
    30                                     + " : Start notify.");
    31             lock.notify();
    32             System.out.println(Thread.currentThread().getName() 
    33                                     + " : notify ends, start to execute again.");
    34         }
    35     }
    36     public static void main(String[] args) {
    37         try {
    38             Object lock = new Object();
    39             App1 app1 = new App1(lock);
    40             app1.start();
    41             Thread.sleep(5000);
    42             App2 app2 = new App2(lock);
    43             app2.start();
    44         } catch (Exception e) {}
    45     }
    46 }
    47          

    Thread.sleep(miliSec)

    线程释放CPU使用权,并进入休眠状态一段时间miliSec,不会放弃线程的锁标志位,比如如果在同步代码块synchronized中调用sleep(),表示线程将一直持有当前同步代码块的锁,其他线程将一直等待。

    Thread.suspend()/resume()

    两个方法配套使用,suspend进入的状态必须有resume调用恢复。跟sleep()方法类似,suspend方法也不会放弃线程已经获取的object锁标志位。这对方法已经不推荐使用,因为容易造成线程自己将自己suspend起来。


    Thread.yield()

    表示当前线程已经获得了充分的CPU执行时间,释放CPU使用权,并重新进入队列等待执行,yield()调用不会阻塞当前线程,也不会放弃当前线程已经获取的锁标志位 ,因此yield方法仅能让跟当前线程具有同样优先级的线程有限执行。


    Thread.join(miliSec)

    表示当前线程需要等到join方法的调用线程执行完毕之后才能继续执行,或者是等待join方法的调用线程执行一段时间之后当前线程才能执行,内部由wait方法实现,所以线程等待开始的时候就会释放持有的对象锁。


    使用synchronized关键字修饰代码块或者方法

    表示这块代码为互斥区或者临界区。有两种类型的锁可以通过synchronized加到代码块或者方法上,一种是实例Object锁,一种是class锁。对于同一个ClassLoader下加载的类而言,一个类只有一把class锁,所有这个类的实例都共享一把锁;同一个类可以实现多个实例对象,也就存在多把Object锁。
    synchronized是语言自带的内置独享锁(非公平锁,不管race thread排队的时间先后,通过编排字节码实现,锁为对象或者类的头标记位),而Java语言的ReentraintReadWriteLock机制是基于Abstract Queued Synchronizer的一种实现(公平/非公平锁,state加CLH队列实现),主要的实现类是ReentrantLock;Java的数据主要会在CPU、Register、Cache、Heap和Thread stack之间进行复制操作,而前面四个都是在Java Threads之间共享,因此Java的锁机制主要用于解决Racing Threads的数据一致性;


    另外通过synchronized添加的锁具有可重入性,也就是只要一个线程已经获取了锁,这样只要共享同一把锁的其他synchronized修饰的代码块或者方法都可以进入,换句话说其他线程访问对其他synchronized修饰的代码块或者方法也需要等待锁的释放,因此synchronized还支持任意对象的锁,这样同一个类的不同方法可以添加不同的对象锁。

     1 public class App1 {
     2     private Object lock1 = new Object();
     3     private static Object lock2 = new Object();
     4     
     5     synchronized public void funcA() {
     6         //this object lock
     7     }
     8     public void funcB() {
     9         synchronized(this) {
    10             //this object lock
    11         }
    12         //run something without lock
    13     }
    14     public void funcC(List<String> list) {
    15         synchronized(list) {
    16             //list object lock
    17         }
    18     }
    19     public void funcD() {
    20         synchronized(lock1) {
    21             //lock1 object lock
    22         }
    23     }
    24     public void funcE() {
    25         synchronized(lock2) {
    26             //lock2 static object lock
    27         }
    28     }
    29     public void funcF() {
    30         synchronized(App1.class) {
    31             //App1 class lock
    32         }
    33     }
    34     synchronized public static void funcG() {
    35         //App1 class lock
    36     }
    37 }

    使用ReentraintLock和ReentraintReadWriteLock实现线程的同步

    java的lock机制基于Abstract Queued Synchronizer (AQS)的实现,AQS定义了多线程访问共享资源的同步器框架,常见的如ReentraintLock/Semaphore/CountDownLatch等都依赖于AQS的实现;

    AQS通过维护一个FIFO队列,并且通过一个由volatile修饰的int状态值来实现锁的获取。FIFO队列中每一个Node表示一个排队线程,其保存着线程的引用和状态,然后通过三个方法分别对获取或者设置状态。

     1 private volatile int state;
     2 static final class Node {
     3     int waitStatus;
     4     Node prev;
     5     Node next;
     6     Node nextWaiter;
     7     Thread thread;
     8 }
     9 protected final int getState() {
    10         return state;
    11     }
    12 protected final void setState(int newState) {
    13         state = newState;
    14     
    15 protected final boolean compareAndSetState(int expect, int update) {
    16         // See below for intrinsics setup to support this
    17         return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    18 }
    19 protected boolean tryAcquire(int arg) 
    20 protected boolean tryRelease(int arg)
    21 protected int tryAcquireShared(int arg)
    22 protected boolean tryReleaseShared(int arg) 
    23 protected boolean isHeldExclusively() 

    通过对getState,setState和compareAndSetState的封装,AQS的继承类需要试下如下几个方法,前面两个表示获取和释放独占锁(如ReentraintLock),后面两个表示获取和释放共享锁(如Semaphore和CountDownLatch)。ReentrantLock初始化状态state=0,线程A访问同步代码的时候使用ReentrantLock.lock(),内部会调用tryAcquire尝试获取独占锁,状态变成state+1;其他线程调用ReentrantLock.lock()的时候就会失败,直到线程A调用unlock(内部为tryRelease)将状态编程state=0;如果线程A在持有独占锁的同时访问其他同步代码块,这时候state的值就会累加,需要调用unlock(内部为tryRelease)减少state的值。ReentrantLock也提供了类似wait/notify的方法,await/signal,同样的线程在调用这两个方法之前需要获得对象锁监视,也就是执行lock.lock()方法。


    ReentrantLock是纯粹的独占锁,为了提升效率引入了ReentrantReadWriteLock.readLock/writeLock,读读共享,读写互斥,写写互斥。CLH队列中的节点模式分为shared和exclusive两种,当一个线程修改了state状态则表示成功获取了锁,如果线程的模式是shared则会执行一个传递读锁的过程,策略是从CLH队列的头到尾依次传递读锁,直到遇到一个模式为exclusive的写锁模式的节点,这个exclusive模式的节点需要等之前所有shared模式的节点对应的操作都执行完毕之后才会获取到锁,这就是读写锁的模式。

     1 public class App1 extends Thread {
     2     private Lock lock = new ReentrantLock();
     3     private Condition condition = lock.newCondition();
     4     public App1() {
     5         super();
     6     }
     7     @Override
     8     public void run() {
     9         try {
    10             lock.lock();
    11             System.out.println(Thread.currentThread().getName() + " : start to wait.");
    12             condition.await();//condition.signal();
    13             System.out.println(Thread.currentThread().getName()
    14                    + " : wait ends, execute again.");
    15         } catch (Exception e) {
    16             
    17         } finally {
    18             lock.unlock();
    19         }
    20     }
    21 }
  • 相关阅读:
    纯JS实现中国行政区域上下联动选择地址
    java解析中国行政区域并在页面显示实现动态逐级筛选
    使用HttpClient 发送get、post请求,及其解析xml返回数据
    JS实现动态提示文本框可输入剩余字数(类似发表微博数字提示)
    webApi 数据绑定 获取
    EF Code First 常用命令
    [解决WebClient或HttpWebRequest首次连接缓慢问题]
    【转】Entity Framework技术系列之7:LINQ to Entities
    Ajax方法提交整个表单的信息
    【转】MVC中处理Json和JS中处理Json对象
  • 原文地址:https://www.cnblogs.com/leo-chen-2014/p/9345796.html
Copyright © 2020-2023  润新知