MENU

Java并发基础

April 9, 2017 • Read: 427 • Java

Synchronized

synchronized关键字是用来控制线程同步的;可以加在代码块上,也可以加在方法上。

不使用synchronized

public class SynchronizedDemo {
    private int count = 10;
    public void m() {
        count--;
        System.out.println(Thread.currentThread().getName() + "->" + count);
    }

    public static void main(String[] args) {
        SynchronizedDemo sync = new SynchronizedDemo();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(sync::m, "thread" + i);
            thread.start();
        }
    }
}

执行结果:

thread0->8
thread2->7
thread1->8
thread3->6
thread4->5
thread5->4
thread6->3
thread7->2
thread8->1
thread9->0

synchronized语法

任何线程要执行代码块的代码,必须先拿到obj的锁:

public class SynchronizedDemo {
    private int count = 10;
    private final Object obj = new Object();

    public void m() {
        synchronized (obj) {
            count--;
            System.out.println(count);
        }
    }
}

如果锁住的对象没有其他用途,可以锁住this(本身):

public class SynchronizedDemo {
    private int count = 10;
    public void m() {
        synchronized (this) {
            count--;
            System.out.println(count);
        }
    }
}

当需要锁住本身,并且执行的代码块是整个方法的时候,可以将synchronized加到方法上。

public class SynchronizedDemo {
    private int count = 10;
    public synchronized void m() {
        count--;
        System.out.println(count);
    }
}

synchronized如果加在静态方法上,相当于锁住类的class对象,任何线程执行时候必须拿到类的class的锁。

public class SynchronizedDemo {
    private static int count = 10;
    public static void m() {
        synchronized (SynchronizedDemo.class) {
            count--;
            System.out.println(count);
        }
    }
}    

等价于

public class SynchronizedDemo {
    private static int count = 10;
    public static synchronized void m() {
        count--;
        System.out.println(count);
    }
}

模拟脏读

当只在“写”方法上加锁,而“读”方法上不加锁,可能会产生脏读问题。

set()方法是synchronized;但是get()方法不是,它不需要获得锁就可以执行,在set()方法未执行完成时,get()方法可以执行,此时就会产生脏读。

public class SynchronizedDemo2 {
    private User user = new User();

    public synchronized void set(String name, int score) {
        user.setName(name);
        //模拟中间耗时操作
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        user.setScore(score);
    }

    public User get() {
        return this.user;
    }

    public static void main(String[] args) {
        SynchronizedDemo2 synchronizedDemo2 = new SynchronizedDemo2();
        //写操作
        new Thread(() -> synchronizedDemo2.set("zhangsan", 98)).start();
        //sleep1秒为了让set()先执行
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        User user = synchronizedDemo2.get();
        //此时set()线程未执行完成
        System.out.println(user.getName() + "->" + user.getScore()); 
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //此时set()线程执行完成
        System.out.println(user.getName() + "->" + user.getScore());
    }
}

class User {
    private String name;
    private int score;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }
}

模拟程序的直接结果为:

zhangsan->0
zhangsan->98    

将get()方法也设置为synchronized,两次get()结果一致。

模拟死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放;由于线程被无限期地阻塞,因此程序不能正常终止。

public class DeadlockDemo {
    private final Object obj1 = new Object();
    private final Object obj2 = new Object();

    public void m1() {
        synchronized (obj1) {
            System.out.println("m1获取obj1的锁");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj2) {
                System.out.println("m1获取obj2的锁");
            }
        }
    }

    public void m2() {
        synchronized (obj2) {
            System.out.println("m2获取obj2的锁");
            synchronized (obj1) {
                System.out.println("m2获取obj1的锁");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DeadlockDemo deadlockDemo = new DeadlockDemo();
        new Thread(deadlockDemo::m1).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(deadlockDemo::m2).start();
    }
}

执行步骤:

  1. 线程1调用m1(),锁住obj1,sleep 2秒。
  2. 线程2调用m2(),锁住obj2,并企图锁住obj1。
  3. 线程1企图锁住obj2。
  4. 线程1和线程2都被阻塞,程序死锁。

可重入

使用synchronized时,当一个线程得到一个对象锁后,再次请求该对象锁时,是可以再次得到该对象锁的;也就是说在一个synchronized方法或块的内部调用本类的其他synchronized方法或块时,是永远可以得到锁的。

public class SynchronizedDemo3 {
    public synchronized void m1() {
        System.out.println("m1执行了");
        m2();
    }

    public synchronized void m2() {
        System.out.println("m2执行了");
    }

    public static void main(String[] args) {
        SynchronizedDemo3 synchronizedDemo3 = new SynchronizedDemo3();
        new Thread(synchronizedDemo3::m1).start();
    }
}

异常释放锁

在代码执行出现异常时,当前线程的锁会被释放;而且可能会产生不一致问题。

public class SynchronizedDemo4 {
    int count = 0;
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " start");
        while (true) {
            count++;
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                int temp = 1 / 0;
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo4 synchronizedDemo4 = new SynchronizedDemo4();
        new Thread(synchronizedDemo4::m1).start();
        new Thread(synchronizedDemo4::m1).start();

    }
}

执行结果:

Thread-0 start
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at SynchronizedDemo4.m1(SynchronizedDemo4.java:23)
    at SynchronizedDemo4$$Lambda$1/1407343478.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)
Thread-1 start    

当Thread-0发生异常后,释放锁;Thread-1获得锁开始执行。

Volatile

保证可见性

代码中,主线程将flag修改为true后,线程t1并没有读到最新的flag值。

public class VolatileDemo {
    private boolean flag = false;

    public void m() {
        System.out.println("m start");
        while (true) {
            if (flag) {
                System.out.println("flag changed!");
                break;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo volatileDemo = new VolatileDemo();
        new Thread(volatileDemo::m, "t1").start();
        //休眠1秒后将flag
        TimeUnit.SECONDS.sleep(1);
        volatileDemo.flag = true;
    }
}

将flag用volatile 修饰后再次执行代码,t1读到了flag修改后的值。
执行结果:

m start
flag changed!

volatile修饰的成员变量在被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。所以任何时刻,不同线程直接总是能看到成员变量的同一个值。

不保证原子性

volatile只保证可见性,并不能保证原子性;代码的执行结果并不一定是20000。

当多个线程访问变量时,读到的值是一致的,但是回写时不会检查当前值;假设t1和t2线程读到count值为10,t1执行完成将count修改该11;而t2也是在读到的10的基础上加1,t2执行完成又将结果修改为11;此时,count就少加了一次1。

public class VolatileDemo {
    private volatile int count = 0;
    
    public void m() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo volatileDemo = new VolatileDemo();
        Thread t1 = new Thread(volatileDemo::m, "t1");
        Thread t2 = new Thread(volatileDemo::m, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(volatileDemo.count);
    }
}


Last Modified: December 9, 2017