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

猜你喜欢

转载自www.cnblogs.com/donghang/p/9233824.html