java线程的并发加锁控制

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