搜索词>>排队锁 耗时0.0110
  • Java多线程编程排队锁(Ticket Lock)详解

    本文将讲述排队锁的使用场景,什么情况适合使用排队锁?Java 怎么使用排队锁?一、什么是排队锁Ticket Lock?Ticket Lock 是为了解决自旋锁的公平性问题,类似于现实中银行柜台的排队叫号:锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。二、JAVA 多线程编程中一种排队锁Ticket Lock使用案例import java.util.concurrent.atomic.AtomicInteger;public class TicketLock {   private AtomicInteger serviceNum = new AtomicInteger(); // 服务号   private AtomicInteger ticketNum = new AtomicInteger(); // 排队号   public int lock() {         // 首先原子性地获得一个排队号         int myTicketNum = ticketNum.getAndIncrement();              // 只要当前服务号不是自己的就不断轮询       while (serviceNum.get() != myTicketNum) {       }       return myTicketNum;    }    public void unlock(int myTicket) {        // 只有当前线程拥有者才能释放锁        int next = myTicket + 1;        serviceNum.compareAndSet(myTicket, next);    }}三、排队锁Ticket Lock缺点Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。后续将会介绍的CLH锁和MCS锁,解决这个问题。
  • Java 多线程编程自旋锁详解

    本文将讲述什么是自旋锁?自旋锁的使用场景,什么情况适合使用自旋锁?Java 怎么使用自旋锁?一、什么是自旋锁?计算机系统资源总是有限的,有些资源需要互斥访问,因此就有了锁机制,只有获得锁的线程才能访问资源。锁保证了每次只有一个线程可以访问资源。当线程申请一个已经被其他线程占用的锁,就会出现两种情况。一种是没有获得锁的线程会阻塞自己,等到锁被释放后再被唤起,这就是互斥锁;一种是没有获得锁的线程一直循环在那里看是否该锁的保持者已经释放了锁,这就是自旋锁。二、自旋锁可能引起的问题1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间的占据cpu时间片,导致CPU资源的浪费,因此可以设定一个时间,当锁持有者超过这个时间不释放锁时,等待者会放弃CPU时间片阻塞;2.死锁问题:试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。三、JAVA 多线程编程中一种自旋锁使用案例import java.util.concurrent.atomic.AtomicReference;public class SpinLock {    //java中原子(CAS)操作    AtomicReference<Thread> owner = new AtomicReference<Thread>();//持有自旋锁的线程对象    private int count;    public void lock() {        Thread cur = Thread.currentThread();        //lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。当有第二个线程调用lock操作时由于owner值不为空,导致循环        //一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。        while (!owner.compareAndSet(null, cur)) {        }    }    public void unLock() {        Thread cur = Thread.currentThread();        owner.compareAndSet(cur, null);    }}上述代码说明:调用lock方法时,如果owner当前值为null,说明自旋锁还没有被占用,将owner设置为currentThread,并进行锁定。调用lock方法时,如果owner当前值不为null,说明自旋锁已经被其他线程占用,当前线程就会在while中继续循环检测。调用unlock方法时,会将owner置为空,相当于释放自旋锁。public class TestThread extends Thread {    static int sum;    private SpinLock lock;    public TestThread(SpinLock lock) {        this.lock = lock;    }    @Override    public void run() {        this.lock.lock();        sum++;        System.out.println("当前值:=====>"+sum);        this.lock.unLock();    }    public static void main(String[] args) throws InterruptedException {        SpinLock lock = new SpinLock();        for (int i = 0; i < 100; i++) {            TestThread test = new TestThread(lock);            Thread t = new Thread(test);            t.start();        }        Thread.currentThread().sleep(1000);        System.out.println(sum);    }}上述代码执行的输出结果一定是顺序的1-100输出。四、名词解释java CAS原子操作:Compare And Set 在java.util.concurrent.atomic包下很多类都具有compareAndSet方法。五、自旋锁的使用场景,什么时候适合使用自旋锁由于自旋锁只是在当前线程不停地执行循环体,不进行线程状态的切换,因此响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要占用CPU时间。如果线程竞争不激烈,并且保持锁的时间很短,则适合使用自旋锁。
  • Java多线程编程CLH锁详解

    本文将讲述CLH锁的使用场景,什么情况适合使用CLH锁?Java 怎么使用CLH锁?一、什么是CLH锁?CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。二、JAVA 多线程编程中一种CLH锁使用案例import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class CLHLock {     public static class CLHNode {         private volatile boolean isLocked = true; // 默认是在等待锁     }     @SuppressWarnings("unused" )     private volatile CLHNode tail ;     private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater                   . newUpdater(CLHLock.class, CLHNode .class , "tail" );     public void lock(CLHNode currentThread) {         CLHNode preNode = UPDATER.getAndSet( this, currentThread);         if(preNode != null) {//已有线程占用了锁,进入自旋             while(preNode.isLocked ) {             }         }     }     public void unlock(CLHNode currentThread) {         // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。         if (!UPDATER .compareAndSet(this, currentThread, null)) {             // 还有后续线程             currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋         }     } }​​​​​​​三、CLH锁 与 MCS锁 的比较代码段 小部件
  • Java多线程编程MCS锁详解

    本文将讲述MCS锁的使用场景,什么情况适合使用MCS锁?Java 怎么使用MCS锁?一、什么是MCS锁?MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。二、JAVA 多线程编程中一种MCS锁使用案例import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class MCSLock {     public static class MCSNode {         volatile MCSNode next;         volatile boolean isBlock = true; // 默认是在等待锁     }     volatile MCSNode queue;// 指向最后一个申请锁的MCSNode     private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater             .newUpdater(MCSLock.class, MCSNode.class, "queue");     public void lock(MCSNode currentThread) {         MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1         if (predecessor != null) {             predecessor.next = currentThread;// step 2             while (currentThread.isBlock) {// step 3             }         }else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己为非阻塞                currentThread. isBlock = false;           }     }     public void unlock(MCSNode currentThread) {         if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义             return;         }         if (currentThread.next == null) {// 检查是否有人排在自己后面             if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4                 // compareAndSet返回true表示确实没有人排在自己后面                 return;             } else {                 // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者                 // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完                 while (currentThread.next == null) { // step 5                 }             }         }         currentThread.next.isBlock = false;         currentThread.next = null;// for GC     } }
  • java多线程编程_java多线程安全_java多线程实现安全锁CAS机制

    java多线程编程_java多线程安全_java多线程实现安全锁CAS机制,CAS在java多线程中相当于数据库的乐观锁,synchronized相当于数据库的乐观锁。<h2>引言</h2>     java多线程编程中难免会遇到资源共享。这里将会讲解一下java多线程中的CAS机制和锁的基础概念。java多线程编程_java多线程安全_java多线程实现安全锁CAS机制,CAS在java多线程中相当于数据库的乐观锁,synchronized相当于数据库的乐观锁。 <h2>一.java多线程编程常见问题</h2> <strong><a rel="external nofollow" target="_blank" id="java多线程1" name="java多线程1">java多线程示例程序</a>:</strong>启动两个线程,每个线程中让静态共享变量count循环累加100次<br /> <br /> <a rel="external nofollow" target="_blank" id="java多线程代码" name="java多线程代码"><strong>java多线程示例代码</strong></a>: <pre> <code class="language-java">package xqlee.net.project.demo.thread.synchronize; public class ForTest { public static int count = 0; public static void main(String[] args) { // 开启两个线程 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } //每个线程累计加100 for (int j = 0; j < 100; j++) { count++; // System.out.println(count); } } }).start(); } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("count=" + count); } } </code></pre> <strong>疑惑</strong>:<span style="color:#6600ff">最终输出的count结果是多少呢?两个线程执行完毕一定是200吗?</span><br /> <br /> <strong>答案</strong>:因为这段代码不是线程安装的,所以最终的自增结果很有可能会小于200。<br /> <strong>尝试执行结果</strong>:<br /> <img alt="java多线程演示代码执行结果" class="img-thumbnail" src="/assist/images/blog/00053be4566b4b93acc2f6e7b13e93bf.png" /><br /> 接下来,修改上面的演示代码。通过关键字synchronized在java多线程编程实现中添加一个锁。这个是个重量级的锁。后续详说。<br /> 修改后演示代码如下: <pre> <code class="language-java">package xqlee.net.project.demo.thread.synchronize; public class ForTest { public static int count = 0; public static void main(String[] args) { // 开启两个线程 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } //每个线程累计加100 for (int j = 0; j < 100; j++) { synchronized (ForTest.class) { count++; } // System.out.println(count); } } }).start(); } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("count=" + count); } } </code></pre> <br /> 再次执行演示代码,查看结果:<br /> <img alt="java多线程编程加锁后的执行结果" class="img-thumbnail" src="/assist/images/blog/0ef74f747d6c440c8f62513431d9b462.png" /><br />     加了同步锁之后,count自增的操作变成了原子性操作,所以最终的输出一定是<strong>count=200</strong>,代码实现了线程安全。<br /> <br />     但是注意,虽然synchronized确保了线程的安全,但是在某些情况下,却不是一个最优选择。为啥这么说呢?关键在于synchronized存在性能问题。synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后再争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统<strong>用户模式</strong>和<strong>内核模式</strong>的转换,代价比较高。尽管Java1.6为Synchronized做了优化,增加了从<strong>偏向锁</strong>到<strong>轻量级锁</strong>再到<strong>重量级锁</strong>的过度,但是在最终转变为重量级锁之后,性能仍然较低。<br /> <br /> <br /> 那么有啥好的其他解决办法嘛?<br /> 有:原子操作类;<br /> <img alt="JDK中的原子操作类" class="img-thumbnail" src="/assist/images/blog/4a065fca722e4577982931e44fe71839.png" /> <p>  所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如<strong>AtomicBoolean</strong>,<strong>AtomicInteger</strong>,<strong>AtomicLong</strong>。它们分别用于Boolean,Integer,Long类型的原子性操作。</p> <p>现在我们尝试在代码中引入AtomicInteger类:</p> <pre> <code class="language-java">package xqlee.net.project.demo.thread.synchronize; import java.util.concurrent.atomic.AtomicInteger; public class ForTest2 { public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { // 开启两个线程 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } //每个线程累计加100 for (int j = 0; j < 100; j++) { count.incrementAndGet(); // System.out.println(count); } } }).start(); } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("count=" + count); } } </code></pre> <br /> 演示执行结果:<br /> <img alt="原子操作类进行乐观锁" class="img-thumbnail" src="/assist/images/blog/be44242a5e9340abbe97aa8cd621a55f.png" /><br /> 使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在<strong>某些情况下</strong>,代码的性能会比Synchronized更好。<br /> <br /> 那么Atomic操作类底层到底利用了什么手段呢?<br /> 其实Atomic就是用到了我们要讲的[CAS机制]<br />   <p><strong>什么是CAS?</strong></p> <p> </p> <p>CAS是英文单词<strong>Compare And Swap</strong>的缩写,翻译过来就是比较并替换。</p> <p> </p> <p>CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。</p> <p>更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。</p> <p>这样说或许有些抽象,我们来看一个例子:</p> <p>1.在内存地址V当中,存储着值为10的变量。</p> <img alt="内存地址V" class="img-thumbnail" src="/assist/images/blog/9da6331c557f46d3ad148fe777a91e5a.png" /><br /> 2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。<br /> <img alt="内存地址V2" class="img-thumbnail" src="/assist/images/blog/761fcd40fb074a66915e845d08139997.png" /><br /> <br /> 3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。<br /> <br /> <img alt="内存地址V3" class="img-thumbnail" src="/assist/images/blog/e247329a723f45c39dd4db9f50b59122.png" /><br /> 4.线程1开始提交更新,首先进行<strong>A和地址V的实际值比较(Compare)</strong>,发现A不等于V的实际值,提交失败。<br /> <img alt="内存地址V4" class="img-thumbnail" src="/assist/images/blog/ea99b78cf5fa414fa481ff39437f310e.png" /><br /> 5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为<strong>自旋</strong>。<br /> <br /> <img alt="内存地址V5" class="img-thumbnail" src="/assist/images/blog/f03a795b87884530a02d4247f2e1bd26.png" /><br /> 6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行<strong>Compare</strong>,发现A和地址V的实际值是相等的。<br /> <br /> <img alt="内存地址6" class="img-thumbnail" src="/assist/images/blog/f1d8e07fac864ef9845aed9436d17c7f.png" /><br /> 7.线程1进行<strong>SWAP</strong>,把地址V的值替换为B,也就是12。<br /> <img alt="内存地址7" class="img-thumbnail" src="/assist/images/blog/c407d63f332043bc8c09f4582d66e6dc.png" /><br />     所以从思想上来说,Synchronized属于<strong>悲观锁</strong>,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于<strong>乐观锁</strong>,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。<br /> <br /> 两种机制CAS机制和Synchronized,没有绝对的好坏。那么如何抉择呢?<br /> 在并发量非常搞的情况下使用Synchronized同步锁更适合一些。<br /> <br /> <strong>CAS缺点:</strong> <p><strong>1.CPU开销较大</strong></p> <p>在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。</p> <p><strong>2.不能保证代码块的原子性</strong></p> <p>CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。</p> <p><strong>3.ABA问题</strong></p> <p>这是CAS机制最大的问题所在。</p> <p>什么是<strong>ABA</strong>问题?怎么解决?我们后面来详细介绍。</p>
  • 针对MyISAM高并发锁表的解决方案

    针对MyISAM高并发锁表的解决方案最近服务器上经常出现mysql进程占CPU100%的情况,使用命令 <pre> <code class="language-sql">show processlist</code></pre> 后,看到出现了很多状态为LOCKED的sql。使用 <pre> <code class="language-sql">show status like 'table%'</code></pre> 检查Table_locks_immediate和Table_locks_waited,发现Table_locks_waited偏 大。出问题的表是MyISAM,分析大概是MyISAM的锁表导致。<br /> MyISAM适合于读频率远大于写频率这一情况。而我目前的应用可能会出现在某一时段读写频率相当。大致如下:<br /> <br /> 一个客户端发出需要长时间运行的SELECT<br /> 其他客户端在同一个表上发出INSERT或者UPDATE,这个客户将等待SELECT完成<br /> 另一个客户在同一个表上发出另一个SELECT;因UPDATE或INSERT比SELECT有更高有优先级,该SELECT将等待UPDATE或INSERT完成,也将等待第一个SELECT完成<br /> <br /> 也就是说对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;<span style="color:#ff0000"><strong>MyISAM表的读操作与写操作之间,以及写操作之间是串行的!</strong></span><br /> 解决方案大概有如下几种:<br /> <br /> MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。 <ul> <li>0 不允许并发操作</li> <li>1 如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。</li> <li>2 无论MyISAM表中有没有空洞,都允许在表尾并发插入记录</li> </ul> 使用–low-priority-updates启用mysqld。这将给所有更新(修改)一个表的语句以比SELECT语句低的优先级。在这种情况下,在先前情形的最后的SELECT语句将在INSERT语句前执行。<br /> 为max_write_lock_count设置一个低值,使得在一定数量的WRITE锁定后,给出READ锁定<br /> 使用LOW_PRIORITY属性给于一个特定的INSERT,UPDATE或DELETE较低的优先级<br /> 使用HIGH_PRIORITY属性给于一个特定的SELECT<br /> 使用INSERT DELAYED语句<br /> <br /> 附加(一):<br /> mysql的myisam解决并发读写解决方法MyISAM在读操作占主导的情况下是很高效的。可一旦出现大量的读写并发,同InnoDB相比,MyISAM的效率就会直线下降,而且,MyISAM和InnoDB的数据存储方式也有显著不同:通常,在MyISAM里,新数据会被附加到数据文件的结尾,可如果时常做一些 UPDATE,DELETE操作之后,数据文件就不再是连续的,形象一点来说,就是数据文件里出现了很多洞洞,此时再插入新数据时,按缺省设置会先看这些洞洞的大小是否可以容纳下新数据,如果可以,则直接把新数据保存到洞洞里,反之,则把新数据保存到数据文件的结尾。之所以这样做是为了减少数据文件的大小,降低文件碎片的产生。但InnoDB里则不是这样,在InnoDB里,由于主键是cluster的,所以,数据文件始终是按照主键排序的,如果使用自增ID做主键,则新数据始终是位于数据文件的结尾。<br /> <br /> 了解了这些基础知识,下面说说MyISAM几个容易忽视的配置选项:<br /> concurrent_insert:<br /> 通常来说,在MyISAM里读写操作是串行的,但当对同一个表进行查询和插入操作时,为了降低锁竞争的频率,根据concurrent_insert的设置,MyISAM是可以并行处理查询和插入的: <ul> <li>当concurrent_insert=0时,不允许并发插入功能。</li> <li>当concurrent_insert=1时,允许对没有洞洞的表使用并发插入,新数据位于数据文件结尾(缺省)。</li> <li>当concurrent_insert=2时,不管表有没有洞洞,都允许在数据文件结尾并发插入。</li> </ul> <br /> 这样看来,把concurrent_insert设置为2是很划算的,至于由此产生的文件碎片,可以定期使用OPTIMIZE TABLE语法优化。<br /> max_write_lock_count:<br /> 缺省情况下,写操作的优先级要高于读操作的优先级,即便是先发送的读请求,后发送的写请求,此时也会优先处理写请求,然后再处理读请求。这就造成一个问题:一旦我发出若干个写请求,就会堵塞所有的读请求,直到写请求全都处理完,才有机会处理读请求。此时可以考虑使用>有了这样的设置,当系统处理一个写操作后,就会暂停写操作,给读操作执行的机会。<br /> low-priority-updates:<br /> 我们还可以更干脆点,直接降低写操作的优先级,给读操作更高的优先级。<br /> low-priority-updates=1<br /> 综合来看,concurrent_insert=2是绝对推荐的,至于max_write_lock_count=1和low-priority- updates=1,则视情况而定,如果可以降低写操作的优先级,则使用low-priority-updates=1,否则使用 max_write_lock_count=1。<br /> <br /> <br /> 附加(二):查看和修改concurrent_insert <pre> <code class="language-sql">show variables like 'concurrent_insert';</code></pre> <br /> <img alt="" class="img-thumbnail" src="/assist/images/blog/5a7f853bb5d049c98967aa1cf20cd329.png" /> <pre> <code class="language-sql">set global concurrent_insert=2; show variables like 'concurrent_insert';</code></pre> <img alt="" class="img-thumbnail" src="/assist/images/blog/24e87b2ca08b4e2d80af337af31a4138.png" /><br /> concurrent_insert:值  <table border="1" cellpadding="1" cellspacing="1" class="table table-bordered table-hover" style="width:500px"> <tbody> <tr> <td>值</td> <td>说明</td> </tr> <tr> <td>NEVER(或者0)</td> <td>不允许并发插入功能</td> </tr> <tr> <td>AUTO(或者1)</td> <td>(默认值)允许对没有洞洞的表使用并发插入,新数据位于数据文件结尾</td> </tr> <tr> <td>ALWAYS(或者2)</td> <td>不管表有没有洞洞,都允许在数据文件结尾并发插入</td> </tr> </tbody> </table> <br /> <span style="color:#ff0000">提示:MySQL中使用set设置的参数在MySQL服务重启后需要重新设定。如果需要永久生效配置MySQL配置文件my.ini/my.cnf</span>
  • Java中的五种单例模式

    Java中的五种单例模式解法一:只适合单线程环境(不建议) <pre> <code class="language-java">public class Singleton { private static Singleton instance=null; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }</code></pre> <p>注解:Singleton的静态属性instance中,只有instance为null的时候才创建一个实例,构造函数私有,确保每次都只创建一个,避免重复创建。<br /> 缺点:只在单线程的情况下正常运行,在多线程的情况下,就会出问题。例如:当两个线程同时运行到判断instance是否为空的if语句,并且instance确实没有创建好时,那么两个线程都会创建一个实例。</p> <p>解法二:多线程的情况可以用。(懒汉式,不建议)<br />  </p> <pre> <code class="language-java">public class Singleton { private static Singleton instance=null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }</code></pre> 注解:在解法一的基础上加上了同步锁,使得在多线程的情况下可以用。例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一个线程发现实例没有创建,创建之。第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。<br /> 缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。而众所周知,加锁是很耗时的。能避免则避免。<br /> <br /> 解法三:加同步锁时,前后两次判断实例是否存在(可行) <pre> <code class="language-java">public class Singleton { private static Singleton instance=null; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ synchronized(Singleton.class){ if(instance==null){ instance=new Singleton(); } } } return instance; } }</code></pre> <p>注解:只有当instance为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。<br /> 缺点:用双重if判断,复杂,容易出错。</p> <p>解法四:饿汉式(建议使用)<br />  </p> <pre> <code class="language-java">public class Singleton { private static Singleton instance=new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } }</code></pre>   <p>注解:初试化静态的instance创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。</p> <p>缺点:没有lazy loading的效果,从而降低内存的使用率。</p> <p>解法五:静态内部内。(建议使用)<br />  </p> <pre> <code class="language-java">public class Singleton { private Singleton(){ } private static class SingletonHolder{ private final static Singleton instance=new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.instance; } }</code></pre> 注解:定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。<br /> 优点:达到了lazy loading的效果,即按需创建实例。
  • Linux登录输入错误密码多次锁定账号时间PAM实现方式

    Linux(centos)系统远程(SSH)登录输入错误密码多次锁定账号时间PAM实现方式Linux(centos)系统远程(SSH)登录输入错误密码多次锁定账号时间PAM实现方式<br /> <br /> 1.登录系统,切换至root用户<br /> 2.编辑文件 <pre> <code>/etc/pam.d/login #该文件用于终端登录作用 /etc/pam.d/sshd #该文件用于SSH远程登录作用</code></pre>  <br /> 编辑文件。写入内容:auth    required    pam_tally2.so    deny=3    unlock_time=30 even_deny_root root_unlock_time=1200<br /> <img alt="1" class="img-thumbnail" src="/assist/images/blog/709899de-5c87-4803-9691-4e8c18d187c1.png" style="height:91px; width:768px" /><br /> 注意:<span style="color:#FF0000"><strong>添加内容必须在#%PAM-1.0后面第一行</strong></span><br /> <br /> 参数说明:<br /> deny                    设置普通用户和root用户连续错误登陆的最大次数,超过最大次数,则锁定该用户<br /> unlock_time             设定普通用户锁定后,多少时间后解锁,单位是秒;<br /> root_unlock_time        设定root用户锁定后,多少时间后解锁,单位是秒;<br /> <strong><span style="color:#FF0000">此处使用的是 pam_tally2 模块,如果不支持 pam_tally2 可以使用 pam_tally 模块。另外,不同的pam版本,设置可能有所不同,具体使用方法,可以参照相关模块的使用规则</span> </strong><br /> <br /> 查看所有用户密码错误次数 <pre> <code>#pam_tally2 --user</code></pre> <br /> 查看指定用户密码错误次数 <pre> <code>#pam_tally2 --user username</code></pre> <img alt="s" class="img-thumbnail" src="/assist/images/blog/5f51166b-e2e0-4ce5-bf7c-32cd1327a831.png" style="height:112px; width:493px" /><br /> 解除某个用户锁定 <pre> <code>#pam_tally2 -r -u username</code></pre> <strong><span style="color:#FF0000">注意:所有操作均需要root权限</span></strong>
  • 趣图分享

    趣图分享,GIF<strong>大熊猫的濒危,是因为有这家长,长大太不容易</strong><br /> <img alt="" class="img-thumbnail" src="/assist/images/blog/6bd4454b566749bf86c7715f6ddd3f27.gif" /><br /> <br /> <strong><img src="file:///C:\Users\xq\AppData\Local\Temp\SGPicFaceTpBq\20928\5BE422A6.png" />喊你多少次没关门没关门,非得让本神亲自动手</strong><br /> <img alt="喊你多少次没关门没关门,非得让本神亲自动手" class="img-thumbnail" src="/assist/images/blog/c436c40118c24eaf82d60ea1771e1625.gif" /><br /> <br /> <strong>看这武术的功底,一定是只中国功夫犬</strong><br /> <img alt="看这武术的功底,一定是只中国功夫犬" class="img-thumbnail" src="/assist/images/blog/67efc98173a74e3e97adc691ee44f2f2.gif" /><br /> <br /> <strong>有时候,你的人生也是这么掉下去的</strong><br /> <img alt="有时候,你的人生也是这么掉下去的" class="img-thumbnail" src="/assist/images/blog/b32f46207e8541ada7bc01e086b4cf99.gif" /><br /> <br /> <strong>有缘千里来相会 这就是命啊​​​​​​​</strong><br /> <img alt="有缘千里来相会 这就是命啊" class="img-thumbnail" src="/assist/images/blog/39e4bd53708041bc9629024238554133.gif" /><br /> <br /> <strong>你想用一把破锁就拦住老夫吗​​​​​​​</strong><br /> <img alt="你想用一把破锁就拦住老夫吗" class="img-thumbnail" src="/assist/images/blog/57f56e678baa4103b50ae89c42695234.gif" /><br /> <br />  
  • Java基础JVM中堆和栈理解

    Java基础JVM中堆和栈理解,收集各大网站各大牛人的语录,在Java中,内存主要分为两种,一种是栈(stack)内存,另一种就是堆内存(heap)(某些情况下说的堆栈内存是指栈内存)。<h2>一.概述</h2> 在Java中,内存主要分为两种,一种是栈(<span style="color:#ff0000"><strong>stack</strong></span>)内存,另一种就是堆内存(<strong><span style="color:#ff0000">heap</span></strong>)(<span style="color:#ff0000">某些情况下说的堆栈内存是指<strong>栈内存</strong></span>)。 <h2>二.堆内存</h2> <h3>2.1什么是堆内存?</h3> 堆内存(<strong><span style="color:#ff0000">heap</span></strong>)是是Java内存中的一种,它的作用是用于存储Java中的对象和数组,当我们new一个对象或者创建一个数组的时候,就会在堆内存中开辟一段空间给它,用于存放。 <h3>2.2.堆内存的特点</h3> <ul> <li><span style="color:#3f3f3f"><span style="font-family:"microsoft yahei""><span style="background-color:#ffffff">堆其实可以类似的看做是管道,或者说是平时去排队买票的的情况差不多,所以堆内存的特点就是:</span></span></span><strong><span style="color:#ff0000">先进先出,后进后出</span></strong><span style="color:#3f3f3f"><span style="font-family:"microsoft yahei""><span style="background-color:#ffffff">,也就是你先排队,好,你先买票。</span></span></span></li> <li>堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。</li> </ul> <h2>三.栈内存</h2> <h3>3.1什么是栈内存</h3>   栈内存是Java的另一种内存,主要是用来执行程序用的,比如:基本类型的变量和对象的引用变量<br />   实际上是<span style="color:#ff0000">只有一个出入口的队列</span>,即后进先出(First In Last Out),先分配的内存必定后释放。一般由,由系统自动分配,存放存放函数的参数值,局部变量等,自动清除。 <br />   还有,堆是全局的,堆栈是每个函数进入的时候分一小块,函数返回的时候就释放了,静态和全局变量,new 得到的变量,都放在堆中,局部变量放在堆栈中,所以函数返回,局部变量就全没了。 <br />   其实在实际应用中,堆栈多用来存储方法的调用。而对则用于对象的存储。 <br />   JAVA中的基本类型,其实需要特殊对待。因为,在JAVA中,通过new创建的对象存储在“堆”中,所以用new 创建一个小的、简单的变量,如基本类型等,往往不是很有效。因此,在JAVA中,对于这些类型,采用了与C、C++相同的方法。也就是说,不用new 来创建,而是创建一个并非是“引用”的“自动”变量。这个变量拥有它的“值”,并置于堆栈中,因此更高效。 <h3>3.2栈内存的特点</h3> <ul> <li><span style="color:#3f3f3f"><span style="font-family:"microsoft yahei""><span style="background-color:#ffffff">栈内存就好像一个矿泉水瓶,像里面放入东西,那么先放入的沉入底部,所以它的特点是:</span></span></span><span style="color:#ff0000"><strong>先进后出,后进先出;</strong></span></li> <li>存取速度比堆要快,仅次于寄存器,栈数据可以共享,但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性;</li> <li>栈内存可以称为一级缓存,由垃圾回收器自动回收;</li> <li>栈数据可以共享;</li> </ul> <h2>四.区别</h2>   JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。<br /> <br /> <strong>相同点:</strong> <ul> <li>都是属于Java内存的一种 </li> <li>系统都会自动去回收它,但是对于堆内存一般开发人员会自动回收它</li> </ul> <strong>不同点:</strong> <ul> <li>堆内存用来存放由new创建的对象和数组。</li> <li>栈内存用来存放方法或者局部变量等 </li> <li>堆是先进先出,后进后出 </li> <li>栈是后进先出,先进后出</li> </ul> <h2>五.举个栗子说明</h2> <ul> <li> 比较类里面的数值是否相等时,用equals()方法;</li> <li> 当测试两个包装类的引用是否指向同一个对象时,用==</li> </ul> <pre> <code class="language-java">String str1 = "abc";  String str2 = "abc";  System.out.println(str1==str2); //true </code></pre> <pre> <code class="language-java">String str1 =new String ("abc"); String str2 =new String ("abc"); System.out.println(str1==str2); // false </code></pre> <pre> <code class="language-java">String s1 = "ja"; String s2 = "va"; String s3 = "java"; String s4 = s1 + s2; System.out.println(s3 == s4);//false System.out.println(s3.equals(s4));//true </code></pre> <br />