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