多线程之对象及变量的并发访问

synchronized同步方法

首先需要了解到:在方法内部的变量是线程安全的,不会存在非线程安全问题

实例变量非线程安全问题
例如:

class Test {
	private int num;
	
	public void change(String string) {
		try {
			if(string.equals("a")) {
				num = 100;
				System.out.println("a is ok");
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("b is ok");
			}
			System.out.println(string + "num = " + num);
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}

(注意,Test对象仅创建一个;如果创建多个Test对象,使线程与业务对象满足一对一则不会存在争抢)
当创建两个线程,分别调用该类的方法(先传入a,后传入b)num = 200 就会将之前的num = 100 覆盖造成非线程安全问题
解决: 在方法上使用该关键字–>同步方法 synchronized public void change(String string),只有一个执行完成后,另一个才可以进入执行

synchronized在字节码指令中的原理
同步方法中使用synchronized关键字实现同步的原因是使用了flag标记ACC_SYNCHRONIZED,当调用方法时,会检查ACC_SYNCHRONIZED访问标志是否设置,如果设置了,执行线程时先持有同步锁,然后才会执行方法,执行完成后释放方法

如果使用synchronized代码块,则使用monitorenter和monitorexit指令进行同步处理

将synchronized方法与对象作为锁
1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
2)A线程先持有object对象的Lock锁,B线程此时调用对象的synchronized类型的方法,则需要等待,也就是同步
3)在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象
4)java中只有“将对象作为锁”,并没有“锁方法”
5)java中,“锁”就是“对象”,“对象”可以映射成锁,哪个线程拿到这个锁就可以执行synchronized方法
6)如果在X对象中使用了synchronized关键字声明非静态方法,则X对象就被当成锁

脏读
发生原因:在读取实例变量时,此值已经被其他线程更改过了
例如:setValue(int a, int b)使用synchronized修饰,getValue()不修饰,那么getValue()可以任意调用,就可能存在脏读

synchronized锁重入
在使用synchronized时,当一个线程得到一个锁对象后,再次请求此对象锁是可以得到该对象锁的。

public class Service {

	synchronized public void service1() {
		System.out.println("service1");
		service2();
	}

	synchronized public void service2() {
		System.out.println("service2");
	}
}

当调用Service的实例对象的service1()时,输出:
service1
service2

注意:锁重入支持继承环境,当存在父子类继承关系时,子类可以通过锁重入调用父类的同步方法(子类若不添加synchronized即为非同步方法,可任意调用)

出现异常,自动释放锁
当一个线程执行出现异常时会自动将持有的对象锁释放,其他线程可以正常获取锁进入同步方法。(调用sleep()和suspend()不释放锁)

synchronized同步代码块
可以将任意对象作为锁(synchronized (this)),多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。(java对象的内存地址是否相同)

public void obj2() {
       synchronized (this) {
           int i = 5;
           while (i-- > 0) {
               System.out.println(Thread.currentThread().getName() + " : " + i);
               try {
                   Thread.sleep(500);
               } catch (InterruptedException ie) {
               }
           }
       }
   }

注意:
1)当多个线程同时执行synchronized (this){ }同步代码块时呈同步效果
2)当其他线程执行x对象中synchronized 同步方法时呈同步效果
3)当其他线程执行x对象里面的synchronized (this){ }代码块时呈同步效果

类Class的单例性
内存中只需存在一份Class对象

public class Student {
	private String name;
	private int age;
	//getter and setter
}

public class Test {
	public static void main(String[] args) {
		Student s1 = new Student();
		Student s2 = new Student();
		Student s3 = new Student();
		
		System.out.println(s1.getClass() == s1.getClass());
		System.out.println(s1.getClass() == s2.getClass());
		System.out.println(s1.getClass() == s3.getClass());
	}
}

结果为:
true
true
true

静态的同步方法和同步代码块
关键字synchronized应用在static静态方法上,就是对当前的 * .class 文件对应的Class类对象进行持锁,由上一个部分可知Class类对象是单例的,所以有:
1)静态同步方法可以对类的所有对象实例起作用
2)静态同步代码块可以对类的所有对象实例起作用

多线程死锁
不同的线程都在等待根本不可能被释放的锁,从而导致任务都无法完成
可以通过jdk自带工具检测:
1)进入jdk的bin目录下,打开命令窗口
2)执行jps,得到运行线程的id
3)执行jstack -l id,可以检测是否发生了死锁

volatile关键字
可见性:可以保证主内存和工作内存直接产生交互,进行读写操作,保证可见性
(synchronized通过写线程冲刷处理器缓存和读线程刷新处理器缓存保证可见性)
原子性:仅能保证变量写操作的原子性,不能保证读写操作的原子性,它提示线程每次从共享内存中去读取变量,而非从私有内存读取
(synchronized通过互斥来保障原子性,互斥是指一个锁一次只能被一个线程所持有,所以,临界区代码只能被一个线程执行,即保障了原子性)
有序性:禁止指令重排序(通过插入内存屏障),即volatile之前的代码不可以重排到volatile之后,volatile之后的代码不可以重排到volatile之前
(synchronized相似)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值