synchronized

2.synchronized详解

为了避免 临界区的竟态条件发生,有多种手段可以达到目的

  • 阻塞式解决方案: synchronized ,Lock
  • 非阻塞式解决方案:原子变量

互斥:synchronized

synchronized ,俗称【对象锁】,才采用互斥的方式 ( 多线程锁住同一个对象才有互斥的效果 ) 让同一时刻最多只有一个线程持有【对象锁】,其他线程再想获得这个【对象锁】时就会被阻塞,这样就能保证拥有锁的线程可以安全顺利的执行 临界区内的代码,不用担心上下文切换;

注意:

虽然java中互斥和同步都可以采用synchronized关键字来完成, 但是它们还有区别:

  • 互斥 是保证临界区的竟态条件发生,同一时刻只能有一个线程执行临界区代码;
  • 同步 是由于执行的先后,顺序不同,需要一个线程 等待其他线程运行到某个点

语法:

synchronized(对象){
  //临界区
}
------------------------------------
  //线程1获得了对象锁,这时候线程2来了再想获得对象锁,就必须要等待线程1释放对象锁,线程2就会陷入阻塞状态( blocked),
  //等线程1执行完了,会把对象锁释放,唤醒阻塞状态中的其他线程,其他线程才有机会获得这个对象锁
  //换句话说
synchronized(n){
 i++; //静态变量i 
}

对1 里面的案例 进行修正

@Slf4j
public class Interrupt {
static int n =0;
static final Object obj = new Object;

    @SneakyThrows
    public static void main(String[] args) {
        Thread t1 =new Thread(()->{
            for (int i =0; i<5000;i++){
          synchronized (obj){ 
                n++;
              } 
            }
        });

        Thread t2 =new Thread(()->{
            for (int i =0; i<5000;i++){
             synchronized (o){ 
                n--;
              } 
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(n);
    }
}

再讲解一下 这个synchronized 流程;:

t1 线程遇到了synchronized(对象){ 临界区 } ,拿到了对象锁,执行临界区的代码(就好比是,他进了一个房间并且把门锁了,然后在房间里工作),这时候再有其他线程 也运行到了这段代码,那么它们就只能等待,发生了上下文切换 Context Switch)

阻塞住了(blocked)(从运行状态 却换到了阻塞状态); 在这中间 即时 t1的时间片用完了,被踢出了房间但是这房间的门还是锁着的,(也不要错误的理解为锁住的对象就能一直运行),对象锁还是由t1持有,其他线程还是进不去,只有等t1再次被分配时间片才能开门进入;当t1执行完{ 临界区}内的代码,会释放对象锁,并且唤醒其他线程,分配到时间片的线程会持有对象锁,进去执行代码;:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DC8cnXff-1644027605400)(ddd.assets/image-20220114025539724.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s76H4JMi-1644027605401)(ddd.assets/image-20220114025559745.png)]

思考

synchronized实际上是用 对象锁,保证了 临界区内的代码的原子性,临界区的代码对外是不可分割的,是一个整体,不会被线程切换所打断

为了加深印象,请思考下面三个问题

如果把sychronized(obj) 放在for循环外面,如何理解?---->考察的是临界区内的代码原子性;

 @SneakyThrows
    public static void main(String[] args) {
        Thread t1 =new Thread(()->{
            for (int i =0; i<5000;i++){
          synchronized (obj){ 
              //只有 n++;这4条指令对进来的线程来说 是一个不可分割的整体;
                n++;
              } 
            }
        });
        ------------------------------------------------------------------------------
@SneakyThrows
    public static void main(String[] args) {
        Thread t1 =new Thread(()->{
            //放在for循环外面 ,这2W条指令对进来的线程来说 是一个不可分割的整体;
       synchronized (obj){ 
            for (int i =0; i<5000;i++){
              
                n++;
              } 
            }
        });

如果t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎么运行? 这里考察的是锁对象;

这个的话 t1 t2持有的是不同的对象锁,不用多说了;

如果t1 synchronized(obj) 而 t2没有不使用synchronized关键字 会怎么运行?

 @SneakyThrows
    public static void main(String[] args) {
        Thread t1 =new Thread(()->{
            for (int i =0; i<5000;i++){
          //t1要对n++操作;t1就会持有锁对象
          synchronized (obj){ 
                n++;
              } 
            }
        });

        Thread t2 =new Thread(()->{
            for (int i =0; i<5000;i++){
            //t2要对n++操作 但是不会去尝试获取锁,不去获取对象锁,那也就不会被阻塞 ,因此代码继续运行t2还能能对共享数                 据操作;
                n--;
            }
        });

用面向对象思想 对上面的案例进行改进:

将互斥的逻辑封装 对外只用简单的调用

对共享资源的保护由内部来实现

package chapact01;

import com.sun.org.apache.bcel.internal.generic.NEW;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
@Slf4j
public class Interrupt {


    @SneakyThrows
    public static void main(String[] args) {
        Room room = Room.getInstance();
        Thread t1 =new Thread(()->{
            for (int i =0; i<5000;i++){

                room.increment();
            }
        });

        Thread t2 =new Thread(()->{
            for (int i =0; i<5000;i++){
               room.decrement();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(room.getN());
    }
}
class Room{
   private static int n =0;
   private static volatile   Room INSTANCE;
  private Room(){

  }
    public void increment(){
        synchronized (this){
            n++;
        }
    }
    public void decrement(){
        synchronized (this){
            n--;
        }
    }
    public int getN() {
        synchronized (this) {
            return n;
        }
    }
    //这里呢 Room 之创建一个对象是没问题的,但是如果创建两个对象 就是两个对象锁了;那么安全性就无法保障,可以使用单例模式改进
    //双重检查 的单例模式
    public static Room getInstance(){
      //双重检查不用每次调用
         if (INSTANCE ==null){
             synchronized (Room.class){
                 if (INSTANCE ==null){
                     INSTANCE= new Room();
                 }
             }
         }
         return  INSTANCE;
    }
//这样你无论获取多少个实例  ,都是同一个对象;
}
---------------------------------------
   //上面的案例 还可以不用  单例模式  
    //把(this)对象锁  换成(Room.class)  这个类只会被加载这一次,  那么new 多个对象也是同一个对象锁,就也能确保共享资源的安全问题

方法上的synchronized

1.成员方法上加synchronized

public synchronized void increment(){
    n++;
}
 //等价于                       //synchronized 加在成员方法上,它锁的是this对象;它只能锁对象
public void increment(){
        synchronized (this){
            n++;
        }
    }


2.静态方法上加synchronized

public synchronized  static void increment(){
    n++;
}
//等价于               //synchronized 加在静态方法上,它锁的是类对象(就是大的Class实例),只存在这一份
public static void increment(){
    synchronized(Room.class){
        n++;
    }
}

所谓的 “线程八锁”

第一锁:结果: 1 2 或 2 1

@Slf4j
class Number{
 public synchronized void a() {
 log.debug("1");
 }
 public synchronized void b() {
 log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}

第二锁 结果 一秒后 1 2 或者 2 一秒后 1

@Slf4j
class Number{
 public synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public synchronized void b() {
 log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}

第三锁 结果: 2 3一秒后 1 或者 3 一秒后 1 2 或者 3 2 一秒后 1

@Slf4j
class Number{
 public synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public synchronized void b() {
 log.debug("2");
 }
 public void c() {
 log.debug("3");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
 new Thread(()->{ n1.c(); }).start();
}

第四锁 结果: 2 一秒后 1

@Slf4j
class Number{
 public synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public synchronized void b() {
 log.debug("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();
}

第五锁 结果:2 一秒后 1

@Slf4j
class Number{
 public static synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public synchronized void b() {
 log.debug("2");
 }
    }
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}

第六锁:result: 一秒后 12 或者 2 一秒后 1

@Slf4j
class Number{
 public static synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public static synchronized void b() {
 log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}

第七锁 Result:2一秒后1

@Slf4j
class Number{
 public static synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public synchronized void b() {
 log.debug("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();
}

第八锁 Result: 一秒后12 或者 2 一秒后 1

@Slf4j
class Number{
 public static synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public static synchronized void b() {
 log.debug("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();
}

w Thread(()->{ n2.b(); }).start();
}


第八锁 Result: 一秒后12 或者 2 一秒后 1

```java
@Slf4j
class Number{
 public static synchronized void a() {
 sleep(1);
 log.debug("1");
 }
 public static synchronized void b() {
 log.debug("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();
}
上一篇:pandas统计dataframe中所有负值(negative values)的个数(count total number of negative values in dataframe)


下一篇:.NET中大型项目开发必备(12)--使用MQ消息队列