多线程基础(三)——Synchronized

目录

1.解决线程不安全

2.关键字Synchronized特性

1.互斥

2.刷新内存

3.可重入

3.synchronized的使用        

1.修饰实例方法

2.修饰代码块 

3.修饰静态方法

4.锁对象

5.总结:


 

根据上篇文章多线程基础(二)——线程安全我们了解到在实际的开发中,会出现线程不安全现象,但是通过加锁就可以避免线程不安全,主要应用关键字Synchronized来解决。

1.解决线程不安全

 class Counter02 {
        public int count = 0;
       synchronized void increase() {
            count++;
        }
    }
public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
         Counter02 counter = new Counter02();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

2.关键字Synchronized特性

1.互斥

synchronized会起到互斥的作用,某个线程执行到某个对象的synchronized中时,此时如果其他线程也执行到同一个对象synchronized就会阻塞等待

  • 进入synchronized修饰的代码块,相当于加锁
  • 退出synchronized修饰的代码块,相当于解锁

 synchronized用的锁是存在对象头里的

  • 可以粗略理解成, 每个对象在内存中存储的时候, 都存有一块内存表示当前的 "锁定" 状态
  • 如果当前是 "无人" 状态, 那么就可以使用, 使用时需要设为 "有人" 状态.
  • 如果当前是 "有人" 状态, 那么其他人无法使用, 只能排队

注意:

  • 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 这也就是操作系统线程调度的一部分工作.
  • 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则. 

2.刷新内存

synchronized实现内存可见机制是通过原子性完成的

synchronized 的工作过程:

  1. 获得互斥锁
  2. 从主内存拷贝变量的最新副本到工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁

在一系列工作完成之后,下一个线程读到的值是上一个线程写入的最新值。

3.可重入

已经获得锁对象的线程针对当前锁加了多次锁,不产生互斥现象,这就是可重入特性

static class Counter {
    public int count = 0;

    synchronized void increase() {
            count++;
        }

    synchronized void increase2() {
            increase();
        }
}
  • increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前对象加锁的.
  • 在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释放, 相当于连续加两次锁)

但是此时代码正确。

在可重入锁的内部, 包含了 "线程持有者" 和 "计数器" 两个信息:

  • 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增.
  • 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到) 

3.synchronized的使用        

1.修饰实例方法

作用于当前实例加锁:

public synchronized void method(){
   // todo
}

此时加锁为锁定了整个方法的内容。

synchronied关键字不能继承,虽然可以定义方法,但是并不属于方法定义的一部分,因此不能被继承。如果父类方法使用synchronied修饰,而在子类方法中重写这个方法,此时是不同步的,必须显示的在子类方法中添加 synchronied关键字。还可以在子类调用父类的方法,此时子类方法中不同步,但是子类调用了父类的同步方法,因此子类方法相当于同步。

子类方法加上synchronied:

class Parent {
   public synchronized void method() { }
}
class Child extends Parent {
   public synchronized void method() { }
}

子类方法调用父类方法:

class Parent {
   public synchronized void method() {   }
}
class Child extends Parent {
   public void method() { super.method();   }
} 

注意:

  • 在定义接口方法时不能使用synchronized关键字。
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

2.修饰代码块 

指定加锁对象,给给定对象加锁
锁当前对象:

public void method(){

   synchronized(this) {
      // todo
   }
}

3.修饰静态方法

作用于当前类对象加锁:

public synchronized static void method() {
    //TODO
}

4.锁对象

  • 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞,竞争同一把锁,线程安全

public class Demo_405 {
    private static int num = 50000;

    public static void main(String[] args) throws InterruptedException {

        Counter06 counter  = new Counter06();
        // 创建线程完成自增
        // 创建两个线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter1的自增方法
                counter.increase();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter2的自增方法
                counter.increase();
            }
        });
        // 启动线程
        t1.start();
        t2.start();
        // 等待两个线程执行完成
        t1.join();
        t2.join();

        // 获取自增后的count值
        System.out.println("count结果 = " + counter.count);

    }
}

class Counter06 {
    public int count = 0;

    /**
     * 执行自增操作
     */
    public void increase() {
        // 指定自定义的锁对象
        synchronized (this) {
            count++;
        }
    }
}
  • 没有明确的对象作为锁,可以创建一个特殊的对象作为锁,同一个对象使用同一把锁,线程安全

public class Demo_405 {
    private static int num = 50000;

    public static void main(String[] args) throws InterruptedException {

        Counter06 counter  = new Counter06();
        // 创建线程完成自增
        // 创建两个线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter1的自增方法
                counter.increase();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter2的自增方法
                counter.increase();
            }
        });
        // 启动线程
        t1.start();
        t2.start();
        // 等待两个线程执行完成
        t1.join();
        t2.join();

        // 获取自增后的count值
        System.out.println("count结果 = " + counter.count);

    }
}

class Counter06 {
    public int count = 0;

    //  自定义一个锁对象
    Object locker = new Object();
    /**
     * 执行自增操作
     */
    public void increase() {
        // 指定自定义的锁对象
        synchronized (locker) {
            count++;
        }
    }
}
  • 静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象,线程安全
public class Demo_407 {
    private static int num = 50000;

    public static void main(String[] args) throws InterruptedException {

        Counter07 counter  = new Counter07();
        Counter07 counter1  = new Counter07();

        // 创建线程完成自增
        // 创建两个线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter1的自增方法
                counter.increase();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter2的自增方法
                counter.increase();
            }
        });
        // 启动线程
        t1.start();
        t2.start();
        // 等待两个线程执行完成
        t1.join();
        t2.join();

        // 获取自增后的count值
        System.out.println("count结果 = " + counter.count);

    }
}

class Counter07 {
    public static int count = 0;

    //  自定义一个锁对象
//    public Object locker = new Object();
    /**
     * 执行自增操作
     */
    public synchronized void increase() {
        // 指定自定义的锁对象
            count++;
    }
}
  • 自定义一个静态锁对象,也是全局唯一,线程安全

public class Demo_408 {
    private static int num = 50000;

    public static void main(String[] args) throws InterruptedException {

        Counter08 counter  = new Counter08();
        Counter08 counter1  = new Counter08();
        counter.count = 1;
//        System.out.println(counter.count);
//        System.out.println(counter1.count);
//        System.out.println(counter);
//        System.out.println(counter1);
//        System.out.println(counter.locker);
//        System.out.println(counter1.locker);

        // 创建线程完成自增
        // 创建两个线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter1的自增方法
                counter.increase();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter2的自增方法
                counter1.increase();
            }
        });
        // 启动线程
        t1.start();
        t2.start();
        // 等待两个线程执行完成
        t1.join();
        t2.join();

        // 获取自增后的count值
        System.out.println("count结果 = " + counter.count);

    }
}

class Counter08 {
    public static int count = 0;

    //  自定义一个锁对象
    public static Object locker = new Object();
    /**
     * 执行自增操作
     */
    public void increase() {
        // 指定自定义的锁对象
        synchronized (locker) {
            count++;
        }
    }
}
  • 使用类对象持有的所信息,全局唯一,存在锁竞争,线程安全
public class Demo_410 {
    private static int num = 50000;

    public static void main(String[] args) throws InterruptedException {

        Counter10 counter  = new Counter10();
        Counter10 counter1  = new Counter10();

        // 创建线程完成自增
        // 创建两个线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter1的自增方法
                counter.increase();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                // 这里调用了counter2的自增方法
                counter1.increase();
            }
        });
        // 启动线程
        t1.start();
        t2.start();
        // 等待两个线程执行完成
        t1.join();
        t2.join();

        // 获取自增后的count值
        System.out.println("count结果 = " + counter.count);

    }
}

class Counter10 {
    public static int count = 0;

    public void increase() {
        // 使用这个类对象持有锁信息
        synchronized (Counter10.class) {
            count++;
        }
    }

}

5.总结:

  1. 只有一个线程要获取锁,直接可以拿到,没有锁竞争

  2. 两个线程同时竞争一把锁,谁先拿到就执行自己的逻辑,另一个线程就是阻塞等待,等释放锁之后,再次竞争锁

  3. 两个线程竞争的不是同一把锁,两者没有竞争关系

  4. 要保证线程之间存在锁竞争,必须要使用相同的锁

  5. synchronized可以修饰方法,可以修饰代码块:

  • 修饰静态方法,用到的锁对象是类对象.class

  • 修饰普通方法,用到的锁对象是当前的实例对象,new

  • 修饰代码块,用到的锁对象是当前对象this new 

  • 在 synchronized(locker){},此处locker可以为任意对象 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值