Java并发编程以内置锁(synchronized)

简介

synchronized在JDK5.0的早期版本中是重量级锁,效率很低,但从JDK6.0开始,JDK在关键字synchronized上作了大量的优化,如偏向锁、轻量级锁等,使它的效率有了很大的提高。java

synchronized的做用是实现线程间的同步,当多个线程都须要访问共享代码区域时,对共享代码区域进行加锁,使得每一次只能有一个线程访问共享代码区域,从而保证线程间的安全性安全

由于没有显式的加锁和解锁过程,因此称之为隐式锁,也叫做内置锁监视器锁app

以下实例,在没有使用synchronized的状况下,多个线程访问共享代码区域时,可能会出现与预想中不一样的结果。ide

public class Apple implements Runnable {
    private int appleCount = 5;

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

    public void eatApple(){
        appleCount--;
        System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Thread t1 = new Thread(apple, "小强");
        Thread t2 = new Thread(apple, "小明");
        Thread t3 = new Thread(apple, "小花");
        Thread t4 = new Thread(apple, "小红");
        Thread t5 = new Thread(apple, "小黑");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

可能会输出以下结果:优化

小强吃了一个苹果,还剩3个苹果
小黑吃了一个苹果,还剩3个苹果
小明吃了一个苹果,还剩2个苹果
小花吃了一个苹果,还剩1个苹果
小红吃了一个苹果,还剩0个苹果

输出结果异常的缘由是eatApple方法里操做不是原子的,如当A线程完成appleCount的赋值,尚未输出,B线程获取到appleCount的最新值,并完成赋值操做,而后A和B同时输出。(A,B线程分别对应小黑、小强)this

若是改下eatApple方法以下,还会不会有线程安全问题呢?线程

public void eatApple(){
	System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + --appleCount + "个苹果");
}

仍是会有的,由于--appleCount不是原子操做,--appleCount能够用另一种写法表示:appleCount = appleCount - 1,仍是有可能会出现以上的异常输出结果。code

synchronized的使用

synchronized分为同步方法和同步代码块两种用法,当每一个线程访问同步方法或同步代码块区域时,首先须要得到对象的锁,抢到锁的线程能够继续执行,抢不到锁的线程则阻塞,等待抢到锁的线程执行完成后释放锁。对象

1.同步代码块继承

锁的对象是object:

public class Apple implements Runnable {
    private int appleCount = 5;
    private Object object = new Object();

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

    public void eatApple(){
	//同步代码块,此时锁的对象是object
        synchronized (object) {
            appleCount--;
            System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
        }
    }

      //...省略main方法
}

2.同步方法,修饰普通方法

锁的对象是当前类的实例对象:

public class Apple implements Runnable {
    private int appleCount = 5;

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

    public synchronized void eatApple() {
        appleCount--;
        System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
    }

    //...省略main方法
}

等价于如下同步代码块的写法:

public void eatApple() {
	synchronized (this) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
	}
}

3.同步方法,修饰静态方法

锁的对象是当前类的class对象:

public class Apple implements Runnable {
    private static int appleCount = 5;

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

    public synchronized static void eatApple() {
        appleCount--;
        System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
    }

    //...省略main方法
}

等价于如下同步代码块的写法:

public static void eatApple() {
	synchronized (Apple.class) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
	}
}

4.同步方法和同步代码块的区别

a.同步方法锁的对象是当前类的实例对象或者当前类的class对象,而同步代码块锁的对象能够是任意对象。

b.同步方法是使用synchronized修饰方法,而同步代码块是使用synchronized修饰共享代码区域。同步代码块相对于同步方法来讲粒度更细,锁的区域更小,通常锁范围越小效率就越高。以下状况显然同步代码块更适用:

public static void eatApple() {
	//不须要同步的耗时操做1
	//...
	synchronized (Apple.class) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
	}
	//不须要同步的耗时操做2
	//...
}

内置锁的可重入性

内置锁的可重入性是指当某个线程试图获取一个它已经持有的锁时,它老是能够获取成功。以下:

public static void eatApple() {
	synchronized (Apple.class) {
		synchronized (Apple.class) {
			synchronized (Apple.class) {
				appleCount--;
				System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
			}
		}
	}
}

若是锁不是可重入的,那么假如某线程持有了该锁,而后又须要等待持有该锁的线程释放锁,这不就形成死锁了吗?

synchronized能够被继承吗?

synchronized不能够被继承,若是子类中重写后的方法须要实现同步,则须要手动添加synchronized关键字。

public class AppleParent {
    public synchronized void eatApple(){

    }
}

public class Apple extends AppleParent implements Runnable {
    private int appleCount = 5;

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

    @Override
    public void eatApple() {
        appleCount--;
        System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");
    }

    //...省略main方法
}

基于内置锁的等待和唤醒

基于内置锁的等待和唤醒是使用Object类中的wait()和notify()或notifyAll()来实现的。这些方法的调用前提是已经持有对应的锁,因此只能在同步方法或者同步代码块里调用。若是在没有获取到对应锁的状况下调用则会抛出IllegalMonitorStateException异常。下面介绍下相关的几个方法:

  1. wait():使当前线程无限期地等待,直到另外一个线程调用notify()或notifyAll()。

  2. wait(long timeout):指定一个超时时间,超时时间事后线程将会被自动唤醒。线程也能够在超时时间以前被notify()或notifyAll()唤醒。注意,wait(0)等同于调用wait()

  3. wait(long timeout, int nanos):相似于wait(long timeout),主要区别是wait(long timeout, int nanos)提供了更高的精度。

  4. notify():随机唤醒一个在相同锁对象上等待的线程。

  5. notifyAll():唤醒全部在相同锁对象上等待的线程。

一个简单的等待唤醒实例:

public class Apple {
    //苹果数量
    private int appleCount = 0;

    /**
     * 买苹果
     */
    public synchronized void getApple() {
        try {
            while (appleCount != 0) {
                wait();
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "买了5个苹果");
        appleCount = 5;
        notify();
    }

    /**
     * 吃苹果
     */
    public synchronized void eatApple() {
        try {
            while (appleCount == 0) {
                wait();
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "吃了1个苹果");
        appleCount--;
        notify();
    }
}
/**
 * 生产者,买苹果
 */
public class Producer extends Thread{
    private Apple apple;

    public Producer(Apple apple, String name){
        super(name);
        this.apple = apple;
    }

    @Override
    public void run(){
        while (true)
        apple.getApple();
    }
}

/**
 * 消费者,吃苹果
 */
public class Consumer extends Thread{
    private Apple apple;

    public Consumer(Apple apple, String name){
        super(name);
        this.apple = apple;
    }

    @Override
    public void run(){
        while (true)
        apple.eatApple();
    }
}
public class Demo {
    public static void main(String[] args) {
        Apple apple = new Apple();
        Producer producer = new Producer(apple,"小明");
        Consumer consumer = new Consumer(apple, "小红");
        producer.start();
        consumer.start();
    }
}

输出结果:

小明买了5个苹果
小红吃了1个苹果
小红吃了1个苹果
小红吃了1个苹果
小红吃了1个苹果
小红吃了1个苹果
小明买了5个苹果
小红吃了1个苹果
    ......
相关文章
相关标签/搜索