Synchronized关键字

Posted 爱跑步的星仔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Synchronized关键字相关的知识,希望对你有一定的参考价值。

  synchronized,形容词,意思是同步的。在多线程中经常用到,我们经常遇到多个线程访问同一个 共享资源 ,这时候必须考虑如何维护数据一致性,在java中synchronized关键字被常用于维护数据一致性。synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的。

1、锁的概念

  因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法,线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁(无论是通过正常语句退出还是执行过程中抛出了异常)。正如前面所说,对共享资源的访问必须是顺序的,也就是说当多个线程对共享资源访问的时候,只能有一个线程可以获得该共享资源的锁。当线程A尝试获取线程B的锁时,线程A必须等待或者阻塞,直到线程B释放该锁为止,否则线程A将一直等待下去,因此java内置锁也称作互斥锁,也就是说锁实际上是一种互斥机制。

  根据使用方式的不同一般我们会将锁分为对象锁和类锁,两个锁是有很大差别的,对象锁是作用在实例方法或者一个对象实例上面的,而类锁是作用在静态方法或者Class对象上面的。一个类可以有多个实例对象,因此一个类的对象锁可能会有多个,但是每个类只有一个Class对象,所以类锁只有一个。 类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定的是实例方法还是静态方法区别的 。

2、synchronized修饰的对象

2.1. 修饰代码块

被修饰的代码块称为同步语句块,作用的对象是调用这个代码块的对象,相当于对象锁。一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。

ps:请注意标红的字体,正确理解。

package sync;

public class SyncThread implements Runnable {
   private int count;
   public SyncThread() {
      count = 1;
   }

   public  void run() {
      synchronized(this) {
         for (int i = 0; i < 5; i++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }

   public int getCount() {
      return count;
   }
}

测试调用:

package sync;

public class Test {

    public static void main(String[] args) {
        SyncThread th  = new SyncThread();
        
        new Thread(th,"Thread1").start();
        new Thread(th,"Thread2").start();
        
        //new Thread(new SyncThread(),"Thread3").start();
        //new Thread(new SyncThread(),"Thread4").start();
    }

}

此时线程1、2调用的是同个对象,线程2在执行,线程1一直在等待,直到线程2运行结束,结果:

 

对于Thread3和Thread4,调用的是两个对象,互不干扰,并行执行,结果:

2.2 修饰方法

2.2.1 修饰一个普通方法

被修饰的方法称为同步方法,作用的对象是调用这个方法的对象。Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//方法体}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。这里不再举例子,只需要将上面的synchronized放到run方法上。

2.2.2 修饰一个静态的方法

其作用的范围是整个静态方法,作用的对象是这个类的所有对象,因为静态方法是属于类的而不属于对象的,相当于在类上加锁。

package sync;

public class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 1;
    }

    public void run() {
        method();
    }

    public synchronized static void method() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

调用测试:

package sync;

public class Test {

    public static void main(String[] args) {
        SyncThread th  = new SyncThread();
        
        //new Thread(th,"Thread1").start();
        //new Thread(th,"Thread2").start();
        
        new Thread(new SyncThread(),"Thread3").start();
        new Thread(new SyncThread(),"Thread4").start();
    }
}

结果:

2.3修饰一个类

其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象,是类锁,类的所有对象用的是同一把锁。

package sync;

 class SyncThread implements Runnable {
    private int count;
    public SyncThread() {
        count = 1;
    }

    public void run() {
        method();
    }

    public void method() {
        synchronized(SyncThread.class){
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

测试调用:

package sync;

public class Test {

    public static void main(String[] args) {
        SyncThread th  = new SyncThread();
        
        //new Thread(th,"Thread1").start();
        //new Thread(th,"Thread2").start();
        
        new Thread(new SyncThread(),"Thread3").start();
        new Thread(new SyncThread(),"Thread4").start();
    }
}

结果:

2.4修饰一个对象

其作用的范围是synchronized后面括号括起来的部分,作用的对象是synchronized加锁的那个对象,相当于对象锁。

账户类:

package sync;

public class Account {
   String name;
   float amount;

   public Account(String name, float amount) {
      this.name = name;
      this.amount = amount;
   }
   //存钱
   public  void add(float amt) {
      amount += amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   //取钱
   public  void minus(float amt) {
      amount -= amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   public float getBalance() {
      return amount;
   }
}

操作类:

package sync;

public class AccountOperator implements Runnable {
    
    private Account account; 
    
    public AccountOperator(Account account) {
        this.account = account;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":start");
        synchronized(account){
            account.add(5000);
            account.minus(5000);
            System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
        }
    }

}

测试类:

package sync;

public class Test {

    public static void main(String[] args) {
        Account account = new Account("zhang san", 10000.0f);
        AccountOperator accountOperator = new AccountOperator(account);

        int THREAD_NUM = 5;
        Thread threads[] = new Thread[THREAD_NUM];
        for (int i = 0; i < THREAD_NUM; i ++) {
           threads[i] = new Thread(accountOperator, "Thread" + i);
           threads[i].start();
        }
    }
}

结果:

可以看出来线程执行synchronized(account){}代码块时都是互斥的。还可以看出此时用的是栈存储的堵塞进程。

当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

class Test implements Runnable
{
   Object object= new Object();  // 特殊的对象
   {
      synchronized(object) {
         // todo 同步代码块
      }
   }

   public void run() {

   }
}

3、总结

1,synchronized关键字加在方法、对象或者是代码块上,如果它作用的对象是非静态的,则它取得的锁是针对对象。

2,synchronized作用的对象是一个静态方法或者是类,则它取得的锁是针对类,该类所有的对象同一把锁。

 

参考资料:https://blog.csdn.net/luoweifu/article/details/46613015

以上是关于Synchronized关键字的主要内容,如果未能解决你的问题,请参考以下文章

多线程 Thread 线程同步 synchronized

static synchronized与synchronized

synchronized关键字

java之结合代码理解synchronized关键字

java关键字之synchronized

synchronized同步关键字