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()方法。