Java多线程-synchronized关键字

一)synchronized关键字简介

synchronized:Java语言中的关键字,可用来给对象、方法或代码块加锁,当它锁定一个对象、一个方法或一个代码块的时,同一时刻最多只能有一个线程执行这段代码。

1)对象加锁:当对象加锁时,其它别的线程在该类所有对象上的任何操作都不能进行,需要等待该对象释放才能执行。

// 方式一: 当前对象加锁
public void currentObject() {
    synchronized(this) {
        // 代码
    }
}
    
// 方式二: 虚拟对象加锁
Object xlock = new Object(), ylock = new Object();
public void xyObject() {
    synchronized(xlock) {
        // 代码
    }
    synchronized(ylock) {
        // 代码
    }
}

 

2)方法加锁:当方法加锁时,线程获得的是成员锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入执行。

public synchronized void synMethod() {
    // 方法体
}

 

3)代码块加锁:当对某一代码块加锁时,synchronized后跟括号,括号里是变量,这样一次只有一个线程进入该代码块,此时线程获得的是成员锁。

public Object synMethod(Object obj) {
    synchronized(obj) {
        // 一次只能有一个线程进入执行
    }
}

 

二)synchronized关键字基本规则

规则一:当有多个线程同时对“某对象”的“synchronized方法”或“synchronized代码块”访问时,一次只会有一个线程得到“某对象”的“synchronized方法”或“synchronized代码块”访问权限,其它线程会阻塞,必须等待该线程执行完之后才能进行访问。

同一对象访问:

public class MyRunnable implements Runnable {

    public void run(){
        synchronized (this) { // 代码块
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ", i=" + i);
            }
        }
    }

    public static void main(String[] args) {
        MyRunnable my = new MyRunnable(); // 创建一个Runnable对象,声明多个线程对它进行访问

        Thread t1 = new Thread(my); // t1线程对Runnable对象访问
        Thread t2 = new Thread(my); // t2线程对Runnable对象访问
        t1.start(); // 启动t1线程,会对Runnable对象加锁
        t2.start(); // 启动t2线程,由于t1线程已加锁,所以需要等待t1线程释放锁之后才能访问
    }
}

运行结果:

 

不同对象访问:

public class MyThread extends Thread {

    private String name;
    public MyThread (String name) {
        this.name = name;
    }

    public void run(){
        synchronized (this) { // 当前对象,只要对象不相同,都可以访问
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
                    System.out.println(name + ", i=" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyThread my1 = new MyThread("A线程"); // 创建一个A线程
        MyThread my2 = new MyThread("B线程"); // 创建一个B线程

        my1.start(); // 启动A线程时, this表示A线程对象
        my2.start(); // 启动B线程时, this表示B线程对象
    }
}

运行结果:

 

规则二:当有多个线程同时对“某对象”的“synchronized方法”或“synchronized代码块”访问时,其它线程都可以访问“某对象”的非同步代码块。

public class Calc {

    // 同步方法
    public void synchroMethod() {
        synchronized (this) { // 当前对象
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
                    System.out.println(Thread.currentThread().getName() + ", i=" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 非同步方法
    public void noSynchroMethod() {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
                System.out.println(Thread.currentThread().getName() + ", i=" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final Calc calc = new Calc();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                calc.synchroMethod(); // 调用Calc对象中的同步方法
            }
        }, "t1线程");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                calc.noSynchroMethod(); // 调用Calc对象中的非同步方法
            }
        }, "t2线程");

        t1.start(); // 启动t1线程
        t2.start(); // 启动t2线程
    }
}

运行结果:

 

规则三:当有多个线程同时对“某对象”的“synchronized方法”或“synchronized代码块”访问时,其它线程对于“某对象”的“synchronized方法”或“synchronized代码块”访问都将被暂时阻塞。

public class Calc {

    // 同步方法
    public void synchroMethod() {
        synchronized (this) { // 当前对象
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
                    System.out.println(Thread.currentThread().getName() + ", i=" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 非同步方法
    public void noSynchroMethod() {
        synchronized (this) { // 当前对象
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
                    System.out.println(Thread.currentThread().getName() + ", i=" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final Calc calc = new Calc();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                calc.synchroMethod(); // 调用Calc对象中的同步方法
            }
        }, "t1线程");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                calc.noSynchroMethod(); // 调用Calc对象中的非同步方法
            }
        }, "t2线程");

        t1.start(); // 启动t1线程
        t2.start(); // 启动t2线程
    }
}

运行结果:

 

三)全局锁和实例锁

全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
               全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。

实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
               实例锁对应的就是synchronized关键字。

关于“全局锁”和“实例锁”的一个案例:

pulbic class Something {
    public synchronized void isSyncA(){}
    public synchronized void isSyncB(){}
    public static synchronized void cSyncA(){}
    public static synchronized void cSyncB(){}
}

假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
(01) x.isSyncA()与x.isSyncB() 
(02) x.isSyncA()与y.isSyncA()
(03) x.cSyncA()与y.cSyncB()
(04) x.isSyncA()与Something.cSyncA()

(01) 不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!

(02) 可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。

(03) 不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。

(04) 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

 

识别二维码关注个人微信公众号

本章完结,待续,欢迎转载!
 
本文说明:该文章属于原创,如需转载,请标明文章转载来源!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值