synchronized作用于实例方法、静态方法、代码块的三种作用方式

synchronsized有三种应用方式:synchronized作用于实例方法,sychronized作用于静态方法,synchronized同步代码块
造成线程安全问题的原因,有共享数据,多个线程操作共享数据。
解决方式,保证同一时刻只有一个线程操作数据,其他线程必须在线程处理完成之后在进行,访问互斥
synchronized保证统一时刻只有一个线程可以执行某个方法或者某个代码块中的共享数据进行操作,synchronized保证一个线程导致共享数据的变化能够被其他线程看到,保证可见性,完全可以替代volatile功能
修饰实例方法,作用于当前实例加锁,进入同步代码前要首先获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步方法之前首先要获取当前类对象的锁,这种用类对象加锁的方式实际上为了解决多个实例对象加锁的情况下带来的线程不安全的情况
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁
synchronized作用于实例方法
实例对象锁作用于实例方法就是用synchronized修饰实例对象中的实例方法,注意修饰的是实例方法,而不是静态方法,什么是实例方法:类中没有静态关键字修饰的方法,想使用实例方法就要先创建一个实例对象,通过创建的实例对象调用这个实例方法,静态方法就是直接通过类名就可以调用类中声明的实例方法

package binfabianchen;
// 创建Tread实例,实现Runnable实例
public class AccountingSync implements Runnable{
	static int i=0;// 共享资源(临界资源)	
	public synchronized void increase() {// synchronized修饰的是实例方法increase(),当前的所对象就是实例对象,java中的线程同步锁可以是任意对象
		i++; // i++不具有原子操作,i++操作时先读取值,之后再原来的值的基础上面加上1,分两步完成
	}
	@Override
	public void run() { // 实现runnable接口,runnable接口中run方法就是要实现的方法
		for(int j=0;j<10000;j++) {
			increase();
			if (j<2) {
				System.out.println(Thread.currentThread().getName());
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		AccountingSync instance=new AccountingSync();
		Thread t1=new Thread(instance);//thread内部是实现runnable接口的实例
		Thread t2=new Thread(instance);//同一个实例创建了两个线程
		t1.start();
		t2.start();// java中的join()方法,将交替执行的线程合并为顺序执行的线程。
		t1.join();//在主线程中调用了join()方法,等于将主线程和t1线程执行的方式改为了串行
		t2.join();//必须当线程全部执行结束之后才会调用线程,join方法还可以调用过期时间,就是说在执行到预定时间后
				 // 执行方式将继续以并行的方式执行
		System.out.println(i);
	}
}

上面的代码中,创建了AccountingSync类并且实现了runnable接口,重写了run方法,在run方法中调用了synchronized修饰的实例方法,实例方法中有一个共享变量,开启了两个线程操作同一个共享资源,因为i++操作不具有原子性,该操作实际上是先读取值,之后写一个新值,分两步,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的值,那么第二个线程就会与第一个线程一起看到同样一个值并执行加1操作,实际上就咋成了线程不安全,因此对于incerea方法必须要使用synchronized方法修饰,以便保证线程是安全的。
synchronized修饰的是实例方法increase()方法,这时候锁的对象就是实例对象instance
如果没有synchronized关键字最终输出结果就很可能小于200000。
需要注意的是,一个线程正在访问一个对象的synchronized实例方法的时候,其他线程也不能访问该对象的其他的synchronizd方法,因为一个对象只有一把锁,当一个线程获取该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的,其他synchronized实例方法,但是其他线程可以访问该实例对象的其他非synchronized方法,当然一个线程A正在访问一个对象的synchronized方法,另一个线程可以访问其他对象的synchronized方法,这样是允许的,因为两个实例对象锁并不相同,如果此时两个线程操作的数据并非共享的,线程安全是有保障的,但是如果方法中有共享数据,那么线程安全就可能无法保证:如下创建了两个实例对象,这个时候存在两把锁,这个时候,创建两个线程,一个线程可以访问这个类中的synchronnized方法,另一个对象也可以访问这个类中的synchronized方法,因为创建了同一个类的两个不同实例。

package binfabianchen;

public class AccountingSyncBad  implements Runnable{
	static int i=0;
	public synchronized void increase() {
		i++;
	}
	@Override
	public void run() {
		for(int j=0;j<10000;j++) {
		increase();
		if (j<2) {
			System.out.println(Thread.currentThread().getName());	
		}
	}
 }
	public static void main(String[] args) throws InterruptedException {
		AccountingSyncBad instance1=new AccountingSyncBad();
		AccountingSyncBad instance2=new AccountingSyncBad();
		// 线程t0和t1使用的是不同的锁,因此两个线程都可以操作类中synchronized修饰的实例访问中的共享变量,因此就出现了多个线程同时访问共享资源的情况,就无法保证线程安全。如何解决这个多个实例锁的出现的线程安全问题,通过对类对象进行加锁而不是实例对象,这样无论创建多少个实例对象也不会影响线程安全synchronized修饰类中的静态方法的时候
		// 但是对于类对象的拥有只有一个,这种情况下对象锁就是唯一的。
		Thread t0=new Thread(instance1);
		Thread t1=new Thread(instance2);
		t0.start();
		t1.start();
		t0.join();
		t1.join();
		System.out.println(i);
	}
}

两个线程访问不同对象的synchronized方法是可以的,前提是sychronised修饰的方法中的变量是不是同一个共享变量,如果synnchronsized方法中的共享变量是同样的额就会造成线程不安全,解决这种多个锁的方法是,synchronized方法作用于静态的increase方法,这样的话当前类对象就是锁对象,无论创建多少个实例对象,但是类对象只有一个,所以这种情况下对象锁就是唯一的。
synchronized作用于静态方法
synchronized作用于静态方法的时候,它的锁就是当前类的class对象。由于静态成员不转属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。如果一个线程A调用一个实例对象的非static synchronized方法,而线程B调用这个实例对象所属类对象的静态synchronized方法,这种情况是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的class对象,而访问非静态synchronized方法占用的锁是当前实例对象锁,二者不会产生互斥现象,但是可能会造成线程安全问题
package binfabianchen;

public class AccountingSyncClass implements Runnable{

public static int i=0;
/*
 * 多线程执行,要实现runnable接口,并将要执行的方法使用run方法
 * synchronized方法作用于静态方法
 */
public static synchronized void increase() {
	i++;
}
/*
 * synchronized作用于实例方法
 * 非静态方法,和实例方法访问的时候锁不一样不会发生互斥
 */
public synchronized void increase2() {
	i++;
}
@Override
public void run() {
	for(int i=0;i<10000;i++) {
		increase();	
		increase2();
	}
}
public static void main(String[] args) throws InterruptedException {
	// 创建新实例
	Thread t1=new Thread(new AccountingSyncClass());
	Thread t2=new Thread(new AccountingSyncClass());
	// 启动线程
	t1.start();
	t2.start();
	t1.join();
	t2.join();
	System.out.println(i);
}

}
synchronized除了修饰实例方法,静态方法之外,还可以同步代码块,为什么要修饰同步代码块,因为修饰同步方法的时候,实际上是将同步方法中所有的内容进行加锁的,有些方法比较大同时比较耗时,而我们只需要同步其中的一小部分就行,因此使用synchronized同步代码块的方式可以实现只同步其中的一小部分。同步代码块中的实例就是锁对象,当线程进入同步代码块时候就要先持有当前线程的实例对象锁,如果当前有其他线程持有该对象锁那么该线程必须要进行等待。实例对象,this对象(当前实例对象)当前类对象都可以作为锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值