java并发 浅显易懂的synchronized讲解

为什么要使用关键字synchronized?

程序的并行化是为了提高效率,但是不能以牺牲正确性为代价。
在java中volatile关键字不能真正的保证线程的安全性,volatile不能代替锁,它无法保证一些符合操作的原子性。它只能确保一个线程修改了数据之后,其他的线程能够看到这个改动,但是当两个线程同时修改某一个数据时,却会产生冲突。
下面为演示代码:

public class AccountingVol implements Runnable{
	static AccountingVol instance=new AccountingVol();
	static volatile int i=0;
	public static void increase() {
		i++;
	}
	public void run() {
		for(int j=0;j<10000000;j++) {
			increase();
		}
	}
	public static void main(String[] aegs) throws InterruptedException{
		Thread t1=new Thread(instance);
		Thread t2=new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}
}

运行后结果很多时候小于20000000,出现了类似的情况,线程就是不安全的。
在这里插入图片描述

关键字synchronized的作用就是实现线程的同步,他的工作是对同步的代码加锁,使的每一次,只有一个线程进入同步块,从而保证安全性。

synchronized 的基本用法

1.指定枷锁对象;对给定的对象枷锁,进入同步代码前要获得给定的对象的锁。

下面的代码,将synchronized作用于一个给定的对象instance,因此,每次线程进入synchronized包裹的代码段,就会要求请求instance的实例锁。如果有其他的线程正在访问这把锁,那么新到的线程就要等待。这样就保证了每次只有一个线程执行i++操作

package pconcurrent;

public class AccountingSync implements Runnable{
	static AccountingSync instance=new AccountingSync();
	static int i=0;
	public void run() {
		for(int j=0;j<10000000;j++) {
			synchronized(instance){
				i++;
			}
		}
	}
	public static void main(String[] args) throws InterruptedException{
		Thread t1=new Thread(instance);
		Thread t2=new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}

}

操作结果如下:
在这里插入图片描述
这样结果就是正确的了。

2.直接作用于实例方法:相当于对实例方法加锁,进入同步代码前要获得当前实例方法的锁。

代码如下:

public class AccountingSync2 implements Runnable{
	static AccountingSync2 instance=new AccountingSync2();
	static int i=0;
	public synchronized void increase() {
		i++;
	}
	public void run() {
		for(int j=0;j<10000000;j++) {
			increase();
		}
	}
	public static void main(String[] aegs) throws InterruptedException{
		Thread t1=new Thread(instance);
		Thread t2=new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}
}

结果也是正确的为20000000.
这里需要注意的是13,14行,这里使用Runnable接口创建两个线程,并且两个人线程都指向同一个Runnable接口实例(instance对象),所以才保证两个线程在工作是能够关注到同一个对象锁上去,保证了线程的安全。

下面为一种错误的方式:

public class AccountingSyncBad implements Runnable {
	static int i=0;
	public synchronized void increase() {
		i++;
	}

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for(int j=0;j<10000000;j++) {
			increase();
		}
	}
	public static void main(String[] args)throws InterruptedException{
		Thread t1=new Thread(new AccountingSyncBad());
		Thread t2=new Thread(new AccountingSyncBad());
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}
}

结果如下没有到达20000000.
在这里插入图片描述
错误的原因在于,虽然第三行申明了同步方法,但是创建线程时指向了两个不同的Runnable实例(new出来的对象,都会是新的对象),两个线程就不会关注到同一个对象锁上去。

3.直接作用于静态方法,相当于对当前类加锁,进入同步代码前要获得当前类的锁。

上述的错误代码有一种改正方法就是将关键字synchronized直接作用于静态方法,代码如下:

public class AccountingSync3 implements Runnable {
	static int i=0;
	public static synchronized void increase() {
		i++;
	}
	
	public void run() {
		for(int j=0;j<10000000;j++) {
			increase();
		}
	}
	public static void main(String[] args)throws InterruptedException{
		Thread t1=new Thread(new AccountingSync3());
		Thread t2=new Thread(new AccountingSync3());
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}

}

结果为:
在这里插入图片描述
这样做结果就是正确的了,及时两个线程指向不同的Runnable对象,但由于方法块需要的时当前类的锁,而非当前实例。因此线程还是可以同步的。

补充

出了用于线程的同步,确保线程外,synchronized还可以保证线程之间的可见性和有序性。可见性方面synchronized可以完全代替volatile的功能。有序性方面synchronized限制每次只有一个线程可以访问同步块,因此只要保证同步块内串行语意一致,那么执行的结果就是一样的。

最近在看书(java高并发程序设计)自学并发,为了加强记忆,博客记录之。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值