java · 2019-06-30 0

Java线程同步

一、问题引入

要实现三个线程,对变量count执行++操作,每个线程加完之后,输出count的值

class MyRunnable implements Runnable{

    private int count = 0;

    @Override
    public void run() {
        addCount();
    }

    private void addCount(){
        count++;
//        此处睡眠100ms,模拟可能有多个线程执行到这
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

}

public class ThreadTest {

    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
//        启动三个线程
        Thread t1 = new Thread(r, "t1");
        Thread t2 = new Thread(r, "t2");
        Thread t3 = new Thread(r, "t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

问题结果如下:

线程t3执行完count++后,cpu轮询到t1操作count++,t1执行完count++后,cpu轮询为t2操作count++,所以就会出现三个线程打印count值时读取count值为3

要想使出现理想的效果,就要使count++和打印count后,才允许别的线程执行count++操作

注意:count也有可能最后不为3,一个线程如果count++后,没有及时写入到内存,另一个线程读到修改前的count值,从count最终的值小于3

result

二、对象锁

锁是任何一个类的对象,需要多个线程共用一把锁

synchronized (this),this代表MyRunnable的对象,在主方法中,创建了一个MyRunnable对象,所以可以保证只有一个MyRunnable对象,即多个线程共用一把锁

当第一个线程执行synchronized (this)时,此时线程会拿到MyRunnable对象锁,当第二个线程执行到synchronized (this)时,拿不到锁,只有当第一个线程执行完同步代码块,释放锁,第二个线程拿到锁,进入同步代码块

同步代码块:

    private void addCount(){
        synchronized (this) {
            count++;
//        此处睡眠100ms,模拟可能有多个线程执行到这
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }

同步方法:

直接在非静态方法上加上synchronized与同步代码块相同,也相当于synchronized (this)

    private synchronized void addCount() {
        count++;
//        此处睡眠100ms,模拟可能有多个线程执行到这
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

三、类锁

如果在主线程创建三个MyRunnable对象,用对象锁没什么作用了,因为此时有三个对象,不能保证对象唯一

同步代码块:

使用任何一个类的类锁,每个类,都有唯一一个Class对象,如MyRunnale.class,虚拟机运行期间,只有唯一一个Class对象

class MyRunnable implements Runnable {

    private static int count = 0;

    @Override
    public void run() {
        addCount();
    }

    private void addCount() {
        synchronized (MyRunnable.class) {
            count++;
//        此处睡眠100ms,模拟可能有多个线程执行到这
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }

}

public class ThreadTest {

    public static void main(String[] args) {
        MyRunnable r1 = new MyRunnable();
        MyRunnable r2 = new MyRunnable();
        MyRunnable r3 = new MyRunnable();
//        启动三个线程
        Thread t1 = new Thread(r1, "t1");
        Thread t2 = new Thread(r2, "t2");
        Thread t3 = new Thread(r3, "t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

同步方法:

作用在静态方法上,相当于synchronized (MyRunnable.class)

    private static synchronized void addCount() {
        count++;
//        此处睡眠100ms,模拟可能有多个线程执行到这
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

四、ReentrantLock锁

synchronized锁定后,执行完同步代码块,自动的释放锁;

ReentrantLock需要手动的启动锁,手动的释放锁;

class MyRunnable implements Runnable {

    private int count = 0;

//    ReentrantLock(boolean fair) fair参数是否公平,true,先来的线程先进入同步代码块;false,线程共同竞争锁
//    1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        addCount();
    }

    private void addCount() {
        try {
//            2.调用锁定方法lock()
            lock.lock();
            count++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        } finally {
//            3.调用解锁方法unlock()
            lock.unlock();
        }
    }

}

public class ThreadTest {

    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
//        启动三个线程
        Thread t1 = new Thread(r, "t1");
        Thread t2 = new Thread(r, "t2");
        Thread t3 = new Thread(r, "t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

线程同步执行的结果:

result

五、synchronized与ReentrantLock

synchronized和ReentrantLock都是可重入锁,避免死锁。

1.synchronized

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。