多线程之互斥锁(synchronized关键字)

来源:互联网 发布:加密锁软件下载 编辑:程序博客网 时间:2024/06/10 20:37

synchronized关键字经常被用来做线程互斥锁,但是使用不当的话,经常达不到目的。初学者常对锁住的是对象还是类有疑问。
原理:无论是对象还是类都有唯一的锁,synchronized只是声明了函数调用时需要什么锁,每个锁同一时间只能由一个线程获取,借此实现了线程互斥。
(1)分析对象锁
A.synchronized修饰非静态函数
接下来看实例:

public enum Person {    Alice,    Bob;    public synchronized void say() {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " say hello world! " +TimeUtil.getCurrentTime());    }    public synchronized void speak() {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " speak hello world! " + TimeUtil.getCurrentTime());    }    public void print() {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " print hello world! " + TimeUtil.getCurrentTime());    }}

这是一个Person类,其中有三个函数,say()和speak()这种声明方式意味着调用该函数需要持有Person实例的对象锁,即用哪个实例调用,则需要持有哪个对象的锁。print()函数无需任何锁。

public class ThreadA implements Runnable {    @Override    public void run() {        Person.Alice.say();    }}public class ThreadB implements Runnable {    @Override    public void run() {        Person.Bob.say();    }}public class ThreadC implements Runnable {    @Override    public void run() {        Person.Alice.print();    }}public static String getCurrentTime() {    SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");    return sdf.format(new Date());}

创建多个线程,然后执行函数:

public class Main {    public static void main(String[] args) {        Thread threadA = new Thread(new ThreadA());        threadA.setName("threadA");        Thread threadB = new Thread(new ThreadB());        threadB.setName("threadB");        Thread threadC = new Thread(new ThreadC());        threadC.setName("threadC");        threadA.start();        System.out.println("A启动了 " + TimeUtil.getCurrentTime());        threadB.start();        System.out.println("B启动了 " + TimeUtil.getCurrentTime());        threadC.start();        System.out.println("C启动了 " + TimeUtil.getCurrentTime());    }}

输出结果为:
A启动了 20:26
B启动了 20:26
C启动了 20:26
threadA say hello world! 20:28
threadC print hello world! 24:21
threadB say hello world! 20:28

可以看出,用两个实例分别调用say()函数是不会出现互斥的。函数执行时,每个函数都可以拿到调用对象的锁。
接下来我们进行改动,将ThreadB改为:

public class ThreadB implements Runnable {    @Override    public void run() {        Person.Alice.say();    }}

执行输出:
A启动了 25:33
B启动了 25:33
C启动了 25:33
threadA say hello world! 25:35
threadC print hello world! 25:35
threadB say hello world! 25:37
可以明显看出线程B和A存在对象锁竞争,A持有Alice锁的时候,B等待。
改动ThreadB为

public class ThreadB implements Runnable {    @Override    public void run() {        Person.Alice.speak();    }}

执行输出:
A启动了 27:29
B启动了 27:29
C启动了 27:29
threadA say hello world! 27:31
threadC print hello world! 27:31
threadB speak hello world! 27:33
可以看出即使用Alice调用不同的函数,还是会出现等待.
总结:对象锁同一时间只能由一个线程持有,此时其余线程无法再用此对象调用需要此对象锁的函数。
B.synchronized修饰代码块
实例如下:

public enum Person {    Alice,    Bob;    public synchronized void say() {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " say hello world! " + TimeUtil.getCurrentTime());    }    public void speak() {        synchronized (Alice) {            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " speak hello world! " + TimeUtil.getCurrentTime());        }    }}public class ThreadA implements Runnable {    @Override    public void run() {        Person.Alice.say();    }}public class ThreadB implements Runnable {    @Override    public void run() {        Person.Alice.speak();    }}public class Main {    public static void main(String[] args) {        Thread threadA = new Thread(new ThreadA());        threadA.setName("threadA");        Thread threadB = new Thread(new ThreadB());        threadB.setName("threadB");        threadA.start();        System.out.println("A启动了 " + TimeUtil.getCurrentTime());        threadB.start();        System.out.println("B启动了 " + TimeUtil.getCurrentTime());    }}

执行输出:
A启动了 35:26
B启动了 35:26
threadA say hello world! 35:28
threadB speak hello world! 35:30
可以看出A和B出现了互斥,A调用的say()函数用
synchronized关键字修饰,所以此时A占用Alice锁,speak中的代码块用synchronized (Alice)修饰,说明同样需要Alice锁,因此互斥。
改动speak函数:

public void speak() {    synchronized (Bob) {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " speak hello world! " + TimeUtil.getCurrentTime());    }}

将需求锁改为Bob,执行输出:
A启动了 38:36
B启动了 38:36
threadA say hello world! 38:38
threadB speak hello world! 38:38
可以看到互斥消失了。
继续更改:

    public void speak() {        synchronized (this) {            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " speak hello world! " + TimeUtil.getCurrentTime());        }    }

这里的this指的是需要调用当前函数的对象锁,执行输出:
A启动了 40:45
B启动了 40:45
threadA say hello world! 40:47
threadB speak hello world! 40:49
互斥又出现了,因为又在竞争Alice锁
(2)分析类锁
每一个类都有唯一且同一时间只能被唯一线程持有的类锁。
实例如下:

public enum Person {    Alice,    Bob;    public static synchronized void say() {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " say hello world! " + TimeUtil.getCurrentTime());    }    public static synchronized void speak() {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " speak hello world! " + TimeUtil.getCurrentTime());    }}public class ThreadA implements Runnable {    @Override    public void run() {        Person.say();    }}public class ThreadB implements Runnable {    @Override    public void run() {        Person.speak();    }}public class Main {    public static void main(String[] args) {        Thread threadA = new Thread(new ThreadA());        threadA.setName("threadA");        Thread threadB = new Thread(new ThreadB());        threadB.setName("threadB");        threadA.start();        System.out.println("A启动了 " + TimeUtil.getCurrentTime());        threadB.start();        System.out.println("B启动了 " + TimeUtil.getCurrentTime());    }}

执行输出:
A启动了 44:18
B启动了 44:18
threadA say hello world! 44:20
threadB speak hello world! 44:22
明显看到线程互斥。
改动代码:

    public static void speak() {        synchronized (Person.class) {            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " speak hello world! " + TimeUtil.getCurrentTime());        }    }

改动speak函数,将里面的的代码用 synchronized (Person.class) 修饰,执行输出:
A启动了 44:18
B启动了 44:18
threadA say hello world! 44:20
threadB speak hello world! 44:22
两个线程依旧互斥,因为还在竞争类锁。

总结:类锁类似对象锁,唯一且同一时间只能由唯一线程持有。

最后补充一点:类锁和对象锁是两套互斥机制,互不影响,具体看你的函数需求的是对象锁(哪个对象)还是类锁

原创粉丝点击