zoukankan      html  css  js  c++  java
  • Java并发机制(2)--synchronized与Lock

    本内容整理自:博客园-海 子-java并发编程系列-http://www.cnblogs.com/dolphin0520/category/602384.html

    1、基础:

      1、什么时候出现线程安全问题?

        在多线程编程中,可能会出现多个线程同时访问一个资源(共享资源)的情况,由于每个线程的过程不可控,导致程序实际运行结果与愿望相违背。

      2、如何解决线程安全问题?

        一般而言,所有的并发模式都采用“序列化访问临界资源”的方案:即在同一时刻,只允许一个线程访问临界资源。

        上述方式也称为:同步互斥访问:在访问临界资源的代码前加一个所,访问完以后释放锁。java中有两种方式实现互斥同步:synchronizedLock

      3、线程创建的两种方式:来自 博客园-海 子-http://www.cnblogs.com/dolphin0520/p/3913517.html

    2、synchronized实现方式 

      1、synchronized加锁的三种方式及对应原理:

        (1):synchronized修饰普通方法,锁范围是实例范围:所有的对象都自动含有单一的锁(监视器monitor),调用对象中的synchronized方法时,该对象会被加锁,该对象内的其他加锁方法不能被其他线程访问。如:  Person(){synchronized eat(){} synchronized run()};当线程thread1调用该对象的eat时,thread2不能调用run方法; 

          底层原理:如:以常量池中ACC_SYNCHRONIZED标示符,为访问标识,方法调用时,先检查该访问标志是否被设置,若是,则线程先获取monitor,方法执行完释放monitor。

    public synchronized void method(){
       System.out.println("Hello Word") ;    
    }
    

         (2):synchronized修饰static方法,锁范围是类范围:所有的Person类对象都会被锁住。线程1和线程2的eat1方法只能被一个调用。而eat2方法是实例同步的,线程1锁住对象1,线程2锁住对象2,eat2可以被线程1和线程2同步调用。

    public class Person implements Runnable{
         static int apple=0;
           //锁住当前的Class类对象,即Person类。 
         public static synchronized void eat1(){
                 i++;//非原子性操作;
         } 
        //锁住当前实例,即只对Person的一个实例对象加锁;
         public synchronized void eat2(){
            i++;
       }
    }

        (3): synchronized修饰同步代码块,所范围是实例对象:有时对整个方法进行加锁,显得不必要,同时也会降低执行效率,需要的仅仅是对方法中某几步操作进行加锁。

    public class Person{
        private int apple=0;
        public void eat(){
            synchronized(this){
                apple++;
            }
        }
    }  

      

    注:一个线程可以获取多次该对象的锁:如对象o1调用eat方法,而eat又调用了该对象的run方法,jvm会自动递增加锁的次数,每次离开一个synchronized方法,计数减一,一直到0。

     注:synchronized的优化等内容见以后整理(待补充。。。)。

    3、Local的实现方式(java.util.concurrent.locks包下Local接口)

      1、有了synchronized为什么还要使用Local?

        (1)synchronized的缺陷:synchronized只有两种执行结果[1]执行完释放锁。[2]异常,jvm释放锁。但是当一个持有锁的线程被阻塞(如IO),那么其他线程也只能等。再比如,多线程同时读取文件时,不会造成冲突,但是为了避免读写冲突,读操作也被synchronized,效率很低。

        (2)使用Local能解决上述问题,而且,Local是java中的一个类(接口),实现多种方法来获取处理锁;synchronized是java语言内置关键字,无别功能。

       2、Local的使用方式:

      (1):获取锁的方式:

    Lock中有四种获取锁的方法:    
    
    (1):lock():获取锁,若锁被其他线程获取,等待。常用try&catch包围,用完后需要主动释放,unlock();
    (2):tryLock():尝试获取锁,锁为空闲状态才获取该锁,若成功返回true;失败返回false;
    (3):tryLock(long time, TimeUnit unit):尝试获取锁,并在等待时间内尝试获取,,获取到返回true;
    (4):lockInterruptibly():使用该方法获取锁,若正在等待获取,改线程可以相应interrupt()方法,结束等待。

      (2):方法内声明并lock方法外声明,方法内lock

    public class Test {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
        }  
         
        public void insert(Thread thread) {
            Lock lock = new ReentrantLock();//Lock在方法内,两个线程两次new
            lock.lock();
            try {
                System.out.println(thread.getName()+"得到了锁");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        }
    }
    Thread-0得到了锁
    Thread-1得到了锁
    Thread-0释放了锁
    Thread-1释放了锁
    //线程0获取锁不影响线程1的获取
     
    public class Test {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        private Lock lock = new ReentrantLock();    //注意这个地方
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
        }  
         
        public void insert(Thread thread) {
            if(lock.tryLock()) {
                try {
                    System.out.println(thread.getName()+"得到了锁");
                    for(int i=0;i<5;i++) {
                        arrayList.add(i);
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                }finally {
                    System.out.println(thread.getName()+"释放了锁");
                    lock.unlock();
                }
            } else {
                System.out.println(thread.getName()+"获取锁失败");
            }
        }
    }

    Thread-0得到了锁
    Thread-1得到了锁
    Thread-1释放了锁
    Thread-0释放了锁

     

      3、ReentrantLock:是接口Lock的唯一直接实现类,一般使用 Lock lock = new ReentrantLock(); 来获取一个锁对象。

     4、锁的几种类型

      1、可重入锁:thread1执行锁方法method1,metho1调用锁方法method2时不必重新获取锁(同一对象);synchronized与reentrantLock都是可重入锁,

      2、可中断锁:synchronized不可中断,Lock是可以中断的(如lockInterruptibly()

      3、公平锁:尽量以请求锁的顺序来获取锁。synchronized不公平锁,ReentrantLock默认不公平,可以设为公平。

      4、读写锁:读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。

      具体事例参见大神ref:博客园-海 子-http://www.cnblogs.com/dolphin0520/p/3923167.html

    5、Lock和synchronized的比较

    (1):Lock是一个接口,主要实现类有:ReentrantLock(直接实现类)和ReentrantReadWriteLock。synchronized是Java中的关键字,是内置的语言实现。
    (2):synchronized异常时会自动释放锁,Lock必须主动释放(必须放在finally{*.unlock()});
    (3):Lock可以实现让等待锁的线程相应中断(interrupt);synchronized不可以。
    (4):Lock可以获知有没有获取到锁。
    (5):Lock可以提高多个线程进行『读』操作的效率。资源竞争不激烈,两者差不多;资源竞争激烈,Lock性能远好于synchronized。

    更详细请移步:博客园-海 子-java并发编程系列-http://www.cnblogs.com/dolphin0520/category/602384.html    

            

      

        

      

  • 相关阅读:
    how to uninstall devkit
    asp.net中bin目录下的 dll.refresh文件
    查找2个分支的共同父节点
    Three ways to do WCF instance management
    WCF Concurrency (Single, Multiple, and Reentrant) and Throttling
    检查string是否为double
    How to hide TabPage from TabControl
    获取当前系统中的时区
    git svn cygwin_exception
    lodoop打印控制具体解释
  • 原文地址:https://www.cnblogs.com/whtblog/p/8874564.html
Copyright © 2011-2022 走看看