• Java多线程简析——Synchronized(同步锁)、Lock以及线程池


    Java多线程

    Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。
    最简单的一个进程,会包括mian线程以及GC线程。

    线程的状态

    线程状态由以下一张网上图片来说明:

    在图中,红框标识的部分方法,可以认为已过时,不再使用。
    (1)wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。
    这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。
    (2)join方法。该方法主要作用是在该线程中的run方法结束后,才往下执行。如以下代码:

    public class ThreadJoin {  
    
        public static void main(String[] args) {  
      
            Thread thread= new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    System.err.println("线程"+Thread.currentThread().getId()+" 打印信息");  
                }  
            });  
            thread.start();  
              
            try {  
                thread.join();  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }
            
            System.err.println("主线程打印信息");  
            
        }
    }

    该方法显示的信息是:
    线程8 打印信息
    主线程打印信息

    如果去掉其中的join方法,则显示如下:
    主线程打印信息
    线程8 打印信息
    (3)yield方法。这个是线程本身的调度方法,使用时你可以在run方法执行完毕时,调用该方法,告知你已可以出让内存资源。
    其他的线程方法,基本都会在日常中用到,如start、run、sleep,这里就不再介绍。

    Synchronized(同步锁)

    在Java中使用多线程,你就不能绕过同步锁这个概念。这在多线程中是十分重要的。
    在Java多线程的使用中,你必然会遇到一个问题:多个线程共享一个或者一组资源,这资源包括内存、文件等。
    很常见的一个例子是,张三在银行账户存有9999元,经过多次的取100,存100后,账户还有多少钱?
    看代码:
    以下表示账户信息:

    import java.sql.Time;  
    import java.util.concurrent.TimeUnit;  
      
    public class Account {  
      
        private String name;  
        private float amt;  
        public Account(String name,float amt) {  
            this.name=name;  
            this.amt=amt;  
        }  
      
        public  void  increaseAmt(float increaseAmt){  
            try {  
                TimeUnit.SECONDS.sleep(1);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            amt+=increaseAmt;  
        }  
          
        public  void decreaseAmt(float decreaseAmt){  
            try {  
                TimeUnit.SECONDS.sleep(1);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            amt-=decreaseAmt;  
        }  
          
        public void printMsg(){  
            System.out.println(name+"账户现有金额为:"+amt);  
        }  
    }  

    以下是我们操作账户的方法:

    final int NUM=100;  
    
    Thread[] threads=new Thread[NUM];  
    for(int i=0;i<NUM;i++){  
        if(threads[i]==null){  
            threads[i]=new Thread(new Runnable() {  
                  
                @Override  
                public void run() {  
                    account.increaseAmt(100f);  
                    account.decreaseAmt(100f);  
                }  
            });  
            threads[i].start();  
        }  
    }  
      
    for(int i=0;i<NUM;i++){  
        try {  
            threads[i].join();  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
      
    account.printMsg();  

    你会发现,每次打印出来的账户余额都不一定是一样的。这就是同步锁的必要性。
    java中,提供了多种使用同步锁的方式。

    (1)对动态方法的修饰。
    作用的是调用该方法的对象(或者说对象引用)。

    public synchronized void doSomething(){}  

    (2)对代码块的修饰。
    作用的是调用该方法的对象(或者说对象引用)。

    public void increaseAmt(float increaseAmt){  
              
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        synchronized (this) {  
            System.out.println(this);  
            amt+=increaseAmt;  
        } 
        
    }

    (3)对静态方法的修饰。
    作用的是静态方法所在类的所有对象(或者说对象引用)。

    public synchronized static void increaseAmt(float increaseAmt){  
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        amt+=increaseAmt;  
    }

    (4)对类的修饰。
    作用的是静态方法所在类的所有对象(或者说对象引用)。

    synchronized (AccountSynchronizedClass.class) {  
        amt-=decreaseAmt;  
    }

    以修饰代码块的方式为例,我们重新运行以上代码后,得到了正确的结果。代码如下:

    import java.util.concurrent.TimeUnit;  
    /**  
     * Synchronized 代码块  
     * @author 战国  
     *  
     */  
    public class AccountSynchronizedBlock {  
      
        private String name;  
        private float amt;  
        public AccountSynchronizedBlock(String name,float amt) {  
            this.name=name;  
            this.amt=amt;  
        }  
      
        public  void  increaseAmt(float increaseAmt){  
              
            try {  
                TimeUnit.SECONDS.sleep(1);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            synchronized (this) {  
                System.out.println(this);  
                amt+=increaseAmt;  
            }  
        }  
          
        public  void decreaseAmt(float decreaseAmt){  
            try {  
                TimeUnit.SECONDS.sleep(1);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            synchronized (this) {  
                System.out.println(this);  
                amt-=decreaseAmt;  
            }  
              
        }  
          
        public void printMsg(){  
            System.out.println(name+"账户现有金额为:"+amt);  
        }  
    }  
    //多线程synchronized修饰代码块 ,每次计算的值都一样  
    final AccountSynchronizedBlock account=new AccountSynchronizedBlock("张三", 9999.0f);  
    final int NUM=50;  
    
    Thread[] threads=new Thread[NUM];  
    for(int i=0;i<NUM;i++){  
        if(threads[i]==null){  
            threads[i]=new Thread(new Runnable() {  
                  
                @Override  
                public void run() {  
                    account.increaseAmt(100f);  
                    account.decreaseAmt(100f);  
                }  
            });  
            threads[i].start();  
        }  
    }  
    
    for(int i=0;i<NUM;i++){  
        try {  
            threads[i].join();  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
    account.printMsg();  

    以上是同步锁的简单说明。
    在JDK5中,Java又引入了一个相似的概念Lock,也就是锁。功能与synchronized是类似的。

    Lock

    Lock对比synchronized有高手总结的差异如下:
    总结来说,Lock和synchronized有以下几点不同:

      1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
      2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
      3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
      4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
      5)Lock可以提高多个线程进行读操作的效率。

      在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

    (参考http://www.cnblogs.com/dolphin0520/p/3923167.html)。
    Lock的操作与synchronized相比,灵活性更高,而且Lock提供多种方式获取锁,有Lock、ReadWriteLock接口,以及实现这两个接口的ReentrantLock类、ReentrantReadWriteLock类。
    对Lock的简单操作代码如下:

    import java.util.ArrayList;  
    import java.util.List;  
    import java.util.concurrent.locks.Lock;  
    import java.util.concurrent.locks.ReadWriteLock;  
    import java.util.concurrent.locks.ReentrantLock;  
    import java.util.concurrent.locks.ReentrantReadWriteLock;  
      
    public class LockImp {  
      
          
        private Lock lock=new ReentrantLock();  
        private ReadWriteLock rwLock=new ReentrantReadWriteLock();  
          
        private List<Integer> list=new ArrayList<Integer>();  
          
        public void doReentrantLock(Thread thread){  
            lock.lock();  
            System.out.println(thread.getName()+"获取锁");  
            try {  
                  for(int i=0;i<10;i++){  
                        list.add(i);  
                    }  
            } catch (Exception e) {  
                  
            }finally{  
                lock.unlock();  
                System.out.println(thread.getName()+"释放锁");  
            }  
              
        }  
        public void doReentrantReadLock(Thread thread){  
            rwLock.readLock().lock();  
            System.out.println(thread.getName()+"获取读锁");  
            try {  
                for(int i=0;i<10;i++){  
                    list.add(i);  
                }  
            } catch (Exception e) {  
                  
            }finally{  
                rwLock.readLock().unlock();  
                System.out.println(thread.getName()+"释放读锁");  
            }  
              
        }  
        public void doReentrantWriteLock(Thread thread){  
            rwLock.writeLock().lock();  
            System.out.println(thread.getName()+"获取写锁");  
            try {  
                for(int i=0;i<10;i++){  
                    list.add(i);  
                }  
            } catch (Exception e) {  
                  
            }finally{  
                rwLock.writeLock().unlock();  
                System.out.println(thread.getName()+"释放写锁");  
            }  
              
        }  
          
          
          
        /**  
         * @param args  
         */  
        public static void main(String[] args) {  
      
            final LockImp lockImp=new LockImp();  
              
            final Thread thread1=new Thread();  
            final Thread thread2=new Thread();  
            final Thread thread3=new Thread();  
              
            new Thread(new Runnable() {  
                  
                @Override  
                public void run() {  
                    lockImp.doReentrantLock(thread1);  
                }  
            }).start();  
              
            new Thread(new Runnable() {  
                          
                        @Override  
                        public void run() {  
                            lockImp.doReentrantLock(thread2);  
                        }  
                    }).start();  
              
            new Thread(new Runnable() {  
                  
                @Override  
                public void run() {  
                    lockImp.doReentrantLock(thread3);  
                }  
            }).start();  
          
              
            lockImp.doReentrantReadLock(thread1);  
            lockImp.doReentrantReadLock(thread2);  
            lockImp.doReentrantReadLock(thread3);  
              
            lockImp.doReentrantWriteLock(thread1);  
            lockImp.doReentrantWriteLock(thread2);  
            lockImp.doReentrantWriteLock(thread3);  
        }  
      
    }  

    Lock的使用中,务必需要lock、unlock同时使用,避免死锁。

    线程池的使用

    为什么使用线程池?
    因为使用它有好处:(1)在界面上,简化了写法,代码更简洁(2)对程序中的线程可以进行适度的管理(3)有效较低了多个线程的内存占有率等。
    这是一篇讲述线程池非常好的文章:http://www.cnblogs.com/dolphin0520/p/3932921.html
    如果对线程池有不了解的同学,可以参考链接中的文章,讲的深入浅出。
    在这里只是简单的封装一个线程池的工具类,仅供参考:

    import java.util.concurrent.ExecutorService;  
    import java.util.concurrent.Executors;  
      
    public class ThreadPoolUtil {  
      
         private volatile static ThreadPoolUtil instance;  
         private ThreadPoolUtil(){}  
         private static ExecutorService threadPool;  
           
           
         public static ThreadPoolUtil getInstance(){  
             if(instance==null){  
                 synchronized (ThreadPoolUtil.class) {  
                      instance=new ThreadPoolUtil();  
                     threadPool=Executors.newCachedThreadPool();  
                }  
             }  
             return instance;  
         }  
           
        public void excute(Runnable runnable){  
            threadPool.execute(runnable);  
        }  
          
        public void shutdown(){  
            threadPool.shutdown();  
        }  
          
        public boolean isActive(){  
            if(threadPool.isTerminated()){  
                return false;  
            }  
            return true;  
        }  
    }  

    转载至 http://blog.csdn.net/yangzhaomuma/article/details/51236976

     

  • 相关阅读:
    vue获取当前v-for里当前点击元素
    js利用正则替换图片路径问题
    undefined null 各种值比较(面试题)
    SSE两个页面的相互通信
    微信小程序导航栏,下面内容滑动,上册导航栏跟着滑动,内容随着导航栏滑动
    微信小程序缓存滑动距离,当页面浏览到一定位置,滑动其他页面后返回该页面记录之前的滑动距离
    ajax拖拽上传文件
    Java 面向对象(四)
    关于Scanner调用 sc.nextInt() 异常try后不能二次输入导致死循环问题
    Java 面向对象(三)
  • 原文地址:https://www.cnblogs.com/rinack/p/7832119.html
Copyright © 2020-2023  润新知