synchronized和Lock的加锁机制
synchronized关键字加锁。
第一,我们先明确几个概念。
我们使用synchronized关键字时,锁不是加在代码上,而是加在对象上。
我先准备了一个测试类来验证信息,Counter类是一个公共线程信息类
package thread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { public final Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private volatile int a = 1; private Students stu = new Students(); public int geta() { return a; } public synchronized void add() { a++; try { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("add开始执行" + df.format(new Date())); Thread.sleep(10000); System.out.println("add is ing"); System.out.println("add执行结束" + df.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void add1() { a++; try { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("add1开始执行" + df.format(new Date())); Thread.sleep(10000); System.out.println("add1 is ing"); System.out.println("add1开始执行" + df.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } public void add2() { synchronized (stu) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("add2开始执行" + df.format(new Date())); System.out.println("add2 is ing"); System.out.println("add2开始执行" + df.format(new Date())); } } public static synchronized void addStatic() { try { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("addStatic开始执行" + df.format(new Date())); Thread.sleep(10000); System.out.println("addStatic is ing"); System.out.println("addStatic开始执行" + df.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } public void add3() { lock.lock(); try { System.out.println("进入add3"); a++; Thread.sleep(10000); System.out.println("add3"); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } public void add4() { lock.lock(); System.out.println("进入add4"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("add4"); condition.signal(); lock.unlock(); } }创建一个Counter类的对象,这个对象里面一共有4个锁,第一个就是counter对象锁,第二个就是students对象锁,第三个就是Students这个类的锁,因为每个类都是Class类的对象。第四个就是ReentrantLock这个可重入锁。 这几个概念明确后,写几个demo来测试说明下:
package thread; public class Test003 { public class Thread1 extends Thread { private Counter counter; public Thread1(Counter counter) { this.counter = counter; } public void run() { counter.add(); } } public class Thread2 extends Thread { private Counter counter; public Thread2(Counter counter) { this.counter = counter; } public void run() { counter.add1(); } } public class Thread3 extends Thread { private Counter counter; public Thread3(Counter counter) { this.counter = counter; } public void run() { counter.add2(); } } public class ThreadStatic extends Thread { public void run() { Counter.addStatic(); } } public class Thread4 extends Thread { private Counter counter; public Thread4(Counter counter) { this.counter = counter; } public void run() { counter.add3(); } } public class Thread5 extends Thread { private Counter counter; public Thread5(Counter counter) { this.counter = counter; } public void run() { counter.add4(); } } }这些是即将要调用的线程类,已来方便我们使用测试。 第一种情况:
public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Test003 test003 = new Test003(); Thread1 thread1 = test003.new Thread1(counter); Thread2 thread2 = test003.new Thread2(counter); thread1.start(); thread2.start(); }结果如下:
add1开始执行2017-12-21 14:28:48 add1 is ing add1开始执行2017-12-21 14:28:58 add开始执行2017-12-21 14:28:58 add is ing add执行结束2017-12-21 14:29:08结果解析:无论执行多少次,要么add1先执行,要么add先执行,但是任意一个没有执行完,另一个是不会执行的,即便调用了sleep方法,为什么呢。因为这2个线程公用一个对象,而调用的方法都加了synchronized的方法,由于第一个执行执行方法的将获得对象锁。其余的线程就无法获取到对象锁,只能进入等待队列。 第二种情况:
public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Test003 test003 = new Test003(); Thread1 thread1 = test003.new Thread1(counter); Thread3 thread3 = test003.new Thread3(counter); thread1.start(); thread3.start(); }结果如下
add2开始执行2017-12-21 14:41:10 add2 is ing add开始执行2017-12-21 14:41:10 add2开始执行2017-12-21 14:41:10 add is ing add执行结束2017-12-21 14:41:20结果解析:2个线程交叉执行,虽然都加了锁,但是是不同的对象锁,因此交叉执行。 第三种情况:
public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Counter counter1 = new Counter(); Test003 test003 = new Test003(); Thread1 thread1 = test003.new Thread1(counter); Thread2 thread2 = test003.new Thread2(counter1); thread1.start(); thread2.start(); }结果如下:
add开始执行2017-12-21 14:45:03 add1开始执行2017-12-21 14:45:03 add1 is ing add is ing add1开始执行2017-12-21 14:45:13 add执行结束2017-12-21 14:45:13结果解析:虽然都是一个类,一个代码,但是是2个对象,synchronized这个关键字加锁是加到对象身上去了。而这个2个线程锁持有的Counter对象时不一样的,因此锁也是不一样的,故交叉执行很正常。 第四种情况:
public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Test003 test003 = new Test003(); Thread1 thread1 = test003.new Thread1(counter); ThreadStatic threadStatic = test003.new ThreadStatic(); thread1.start(); threadStatic.start(); }结果如下:
addStatic开始执行2017-12-21 18:35:55 add开始执行2017-12-21 18:35:55 addStatic is ing add is ing addStatic开始执行2017-12-21 18:36:05 add执行结束2017-12-21 18:36:05结果解析:结果交叉执行为何,因为静态方法是属于类本身的,那么在这个静态方法上加的synchronized,那么这个锁则对应的是类本身,那么2个线程执行普通方法和静态方法(都加了synchronized关键字)时是并发执行的了。 第五种情况:
public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Test003 test003 = new Test003(); Thread4 thread4 = test003.new Thread4(counter); Thread5 thread5 = test003.new Thread5(counter); thread4.start(); thread5.start(); }结果如下:
进入add4 add4 进入add3 add3结果解析:同一个Counter对象那么对应同一个ReentrantLock锁。那么这2个线程所执行的方法都加了ReentrantLock,那么执行的时候注定要加锁等待,所以无法并发执行,结果按顺序执行。 ## 并发条件控制 正如大家所知,synchronized关键字对应了wait()方法和notify()方法来控制并发控制,那么lock呢,也对应一系列的方法来处理。我们使用Condition这个类的方法来加以控制,下面,我将展示这2个加锁方式的控制的简单实现 第一个:lock的条件控制
public final Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private volatile int a = 1; public void add3() { System.out.println("进入add3"); lock.lock(); try { a++; System.out.println("add3"); condition.await(); System.out.println("add31"); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } public void add4() { System.out.println("add4"); while (true) { if (a > 1) { System.out.println("进入循环"); lock.lock(); System.out.println("add4"); System.out.println("add41"); condition.signal(); lock.unlock(); break; } } } public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Test003 test003 = new Test003(); Thread4 thread4 = test003.new Thread4(counter); Thread5 thread5 = test003.new Thread5(counter); thread4.start(); thread5.start(); }结果如下
add4 进入add3 add3 进入循环 add4 add41 add31结果解析:2个线程无论谁先执行,先获取到锁的总是add3()方法。因为a=1,无法获取到锁,因此先执行add3方法,在放弃锁并进入到等待队列,然后a>1了,那么add4有机会执行代码了。在唤醒add3()方法,恩,完美。 第二种情况:使用Object类的wait方法。
private volatile int a = 1; public void add() { synchronized (this) { try { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("add开始执行" + df.format(new Date())); // Thread.sleep(10000); a++; wait(); System.out.println("add is ing"); System.out.println("add执行结束" + df.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } public void add1() { while (true) { if (a > 1) { synchronized (this) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("add1开始执行" + df.format(new Date())); // Thread.sleep(10000); System.out.println("add1 is ing"); System.out.println("add1开始执行" + df.format(new Date())); notify(); } break; } } } public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Test003 test003 = new Test003(); Thread1 thread1 = test003.new Thread1(counter); Thread2 thread2 = test003.new Thread2(counter); thread1.start(); thread2.start(); thread1.join(); }
结果如下
add开始执行2017-12-21 19:32:23
add1开始执行2017-12-21 19:32:23
add1 is ing
add1开始执行2017-12-21 19:32:23
add is ing
add执行结束2017-12-21 19:32:23
结果解析:逻辑和上面一样,只是不同的实现方式罢了。
wait()方法在这里要再次说下,他是在加了synchronized这个关键字的对象调用的,不然报错。还有Thread类的join()方法源码里面的wait()方法是让主线程进入等待队列,并且等待这个锁,当这个子线程执行完后,则会自动调用notify方法,让等待子线程对象锁的主线程可以继续执行,恩,完美解释join()方法。