synchronized的使用和线程八锁

1. 前言

临界资源:一次仅允许一个进程使用的资源就称为临界资源

临界区:访问临界资源的代码块

竞态条件:多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

为了避免临界区的竞态条件的发生,我们可以采用阻塞式的解决方案和非阻塞式的解决方案。阻塞式的方案就是使用synchronized。synchronized:对象锁,保证了临界区内代码的原子性,采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其它线程获取这个对象锁时会阻塞,保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换。

synchronized的锁对象理论上可以是任意的唯一对象,并且 synchronized是可重入的,不公平的重量级锁。

2. 使用

关键字所在位置

使用的三种格式:锁对象,同步静态方法,同步普通方法

synchronized(锁对象){
	// 访问共享资源的核心代码
}
//同步方法
修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}
//同步静态方法
修饰符 static synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

同步方法的底层也是类似于锁对象的:

//如果方法是实例方法:同步方法默认用 this 作为的锁对象
public synchronized void test() {} 
//等价于
public void test() {
    synchronized(this) {}
}


//如果方法是静态方法:同步方法默认用类名 .class 作为的锁对象
class Test{
	public synchronized static void test() {}
}
//等价于
class Test{
    public void test() {
        synchronized(Test.class) {}
	}
}

举例:

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for (int j = 0; j < 5000; j++) {
                room.increment();
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int j = 0; j < 5000; j++) {
                room.decrement();
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(room.get());
    }
}

class Room {
    int value = 0;
    public synchronized void increment() {
        value++;
    }
    public synchronized void decrement() {
        value--;
    }
    public synchronized int get() {
        return value;
    }
}

3. 锁分类

当在代码块中使用时,锁是代码块中传入的对象,比方说我们自己创建的对象或者是this。

当在普通方法上使用,锁是当前实例对象。

synchronized修饰函数我们简称同步函数,线程执行称同步函数前,需要先获取监视器锁,简称锁,获取锁成功才能执行同步函数同步函数执行完后,线程会释放锁并通知唤醒其他线程获取锁,获取锁失败「则阻塞并等待通知唤醒该线程重新获取锁」,同步函数会以this作为锁,即当前对象。

当再静态方法上使用,锁是当前类的对象。

Java的静态资源可以直接通过类名调用,静态资源不属于任何实例对象,它只属于Class对象,每个ClassJ V M中只有唯一的一个Class对象,所以同步静态函数会以Class对象作为锁,后续获取锁、释放锁流程都一致。

Java类可能会有很多个对象,但是Class对象只有一个。不过Class对象其实也是存放在堆中的实例对象,只不过比new出来的对象特殊一点,是jvm加载类时所创建的。所以不管这个对象new多少个新实例,class对象只有一个,作为锁的对象,线程安全。

 4. 线程八锁问题

可能看名字比较晦涩难懂,但是其实就是看synchronized锁住的是哪个对象,根据锁住的对象对代码的执行进行分析。

锁住类对象:所有类的实例方法都是安全的

锁住this对象:只有当前实例对象的线程内是安全的,如果存在多个实例就可能导致不安全

来看几个例子:

例一:线程不安全,线程1调用a方法,锁住类对象;而线程2调用b方法,锁住实例对象,即n2。两个线程锁住的不是同一个对象,相当于锁并没有起作用。

class Number{
    public static synchronized void a(){
		Thread.sleep(1000);
        System.out.println("1");
    }
    public synchronized void b() {
        System.out.println("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n2.b(); }).start();
}

例二:synchronized标注在静态方法上,则说明a和b两个方法都是对类对象进行加锁。当线程1调用a方法,线程2调用b方法时,两个线程对同一对象进行加锁,则是线程安全的。

class Number{
    public static synchronized void a(){
		Thread.sleep(1000);
        System.out.println("1");
    }
    public static synchronized void b() {
        System.out.println("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n2.b(); }).start();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值