通过这两个关键字,我们可以很容易的实现同步多个任务的行为,可以实现同一时刻,只能有一条线程去访问共享资源
public class HasSelfPrivateNum{
public void add(String name){
try{
int num=0; //num在方法内部,永远是线程安全的!!!
if(name.equals("a"){
num =100;
}else{
num =200;
}
System.out.println("num=="+num);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class HasSelfPrivateNum{
private int num=0; //num是本类的实例变量,可能出现非线程安全问题!!!
public void add(String name){
try{
if(name.equals("a"){
num =100;
}else{
num =200;
}
System.out.println("num=="+num);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
只有共享的资源才可能出现非线程安全性问题,需要同步化...就像方法内部的变量,根本不可能共享,所以说没必要同步化
这一问题引入了Synchronized关键字,让出现非线程安全问题的方法,保持前后的同步,让线程拿到对象锁(this对象锁),当某一个线程进去后,其他线程只能等它释放对象锁之后,获取到对象锁在进入同步方法(Synchronized可以保证它修饰的方法实现原子性的操作)
拓展:A线程持有object对象的锁,B线程可以异步的调用object对象中的非Synchronized类型的方法
public class HasSelfPrivateNum{
private int num=0; //num是本类的实例变量,可能出现非线程安全问题!!!
Synchronized public void add(String name){
try{
if(name.equals("a"){
num =100;
}else{
num =200;
}
System.out.println("num=="+num);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
比如说,分别给两个线程分别去访问同一个类的两个不同不对象的同名同步方法,结果不是顺序执行,而是异步执行的...
关键字Synchronized获取到的锁全部是对象锁,而不是把一段代码或者一个方法当作锁!真出现了多个线程访问多个对象,那么JVM就会创建出,多把对象锁...
java中每一个对象都可被当作同步的锁,这些锁就叫做内置锁
一个线程进来之后,另一个线程不能进来
Synchronized的弊端:
声明方法在某些情况下是有弊端的,比如说A线程抢到了cpu的执行权,在调用同步方法执行某一个比较长的任务的,A还没来得及释放这个对象锁,紧接着B线程抢到了cpu的执行权,他也想去访问A线程访问的方法,然而B线程拿不到这个对象锁,所以被拒绝访问,因此B线程必须等待较长的时间
同步代码块语法:
synchronized(Object){
//同步执行的任务
}
sychronized代码块里面的方法是同步的,另一个线程只有等待当前线程执行完这个代码块之后,才能执行此代码块
使用同步代码块解决同步方法的弊端,提升效率 一半同步一半异步!-->
public task{
//类的实例属性 --> 共享资源
private int a;
private int b;
try{
.... // 非共享资源
.... // 非共享资源
synchronized(this){
a++;
b++;
}
}catch(InterruptedExceotion e){
e.printStackTrace();
}
}
可以看到,我们只是把可能出现非线程的共享资源放在同步代码块中...
当一个线程访问object的一个Synchronized(this){}同步代码块里面的内容时,另一个线程可以访问此object对象中的非synchronized代码块!!!
也就是一半同步,一半异步,不在同步代码块中的代码,就是异步执行,在同步代码块中的代码,就是同步执行!
当一个线程访问object对象中的Synchronized代码块的时候,其他的任何线程对object的其他任何Synchronized同步代码块的访问也是阻塞的,这也说明Synchronized同步代码块和Synchronized修饰的方法一样,使用的对象监视器是一个
Synchronized(非this对象){} 同步代码块可以是任意对象,这个非this对象大多数是,方法的参数,类的实例变量
如果一个类中有很多的Synchronized同步方法,虽然可以实现同步,但是,会发生阻塞而降低效率,因为所有的Synchronized同步方法,他们拥有的都是this锁,而Synchronized(非this对象){}同步代码块可以使用的是非this锁,拥有不同的锁,因此他们两者之间是异步执行的,对他们自己来说又是同步执行,提升了效率
同步代码块放在非同步Synchronized方法中进行声明,不能保证调用方法的线程执行同步,因为线程调用方法的顺序是无序的,虽然在同步代码块中执行的顺序依然有序,但如果有分支逻辑判断(逻辑判断没有在同步代码块中),就可能会出现脏读
//创建一个只能存储一个元素的集合...
List list = new ArrayList();
public void unsafe(){
try{
if(list.size()<0){
Thread.sleep(2000);
synchronized(){
list.add("a");
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
//此段代码就会出现脏读...
//解决方法: 同步化
List list = new ArrayList();
synchronized public void unsafe(){
try{
if(list.size()<0){
Thread.sleep(2000);
synchronized(){
list.add("a");
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
Synchronized方法同样可以修饰静态方法,运行的结果证明它同样可以实现线程安全,顺序执行,但是Synchronized修饰普通方法 和 Synchronized修饰静态方法是有本质上的区别的,Synchronized修饰静态方法实际上是给Class类上锁 而前者是给this对象上锁.
换言之,两个自身顺序执行,同时出现则异步执行,(锁不同)
Synchronized(类名.class){...}
作用和其修饰静态方法一样
public static void mainString[] avgs(){
String a ="a";
String b = "a";
System.out.println(a==b);
}
这也就是意味着,假如在两个线程给一个Synchronized同步代码块中传递进同样的字符串,也就意味着,他们要去竞争同一把锁,难免会出现阻塞的情况.所以绝大多数情况下,我们是不使用String字符串,来当作锁对象的
同步方法synchronized同步方法,容易造成死循环,如下代码,method永远不可能得到执行
public class Service{
synchronized public void methodA(){
System.out.println("methodA Begin");
boolean tag = true;
while(tag){}
System.out.println("methodA end");
}
synchronized public void methodB(){
System.out.println("methodB Begin");
System.out.println("methodB end");
}
}
使用同步代码块解决无限循环问题
public class Service{
public void methodA(){
Object o1 = new Object();
synchronized(o1){
System.out.println("methodA Begin");
boolean tag = true;
while(tag){}
System.out.println("methodA end");
}
}
synchronized public void methodB(){
Object o2 = new Object();
synchronized(o2){
System.out.println("methodB Begin");
System.out.println("methodB end");
}
}
}
如下代码死锁现象
public class demoSiSuo implements Runnable{
public class demoSiSuo implements Runnable{
private String username;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void set(String username) {
this.username = username;
}
@Override
public void run() {
if (username.equals("a")){
synchronized (lock1){
try {
System.out.println("a");
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
//do something
System.out.println("接着lock1 lock2的代码执行了");
}
}
}
if (username.equals("b")){
synchronized (lock2){
System.out.println("b");
//do something
synchronized (lock1){
//do something
System.out.println("接着lock2 lock1的代码执行了");
}
}
}
}
public static void main(String[] args) {
demoSiSuo demoSiSuo = new demoSiSuo();
new Thread(()->{
demoSiSuo.set("a");
demoSiSuo.run();
}).start();
new Thread(()->{
demoSiSuo.set("b");
demoSiSuo.run();
}).start();
}
}
使用JDK自带的检查死锁的工具
在cmd窗口切换到bin目录,输入指令jps 找到正在运行的实例id, 输入 jstack -l [id] 可查看到
Found 1 deadlock.
public static class myService{
private String lock="123";
public void method() throws InterruptedException {
synchronized (lock){
lock="456";
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"end");
}
}
}
public static void main(String[] args) {
myService j = new myService();
new Thread(()->{
try {
j.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
j.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
结果
Thread-0
Thread-1
Thread-0end
Thread-1end
当注释Thread.sleep(1000);结果如下
Thread-0 同步执行
Thread-0end
Thread-1
Thread-1end
对象锁改变,依然是遵循高并发下 拥有同一把锁的同步方法或代码块会阻塞,拥有不同锁,是不会阻塞的
public int add(){
synchronized(this){
this.value++;
return value;
}
}
public int add();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: dup
6: getfield #2 // Field value:I
9: iconst_1
10: iadd
11: putfield #2 // Field value:I
14: aload_0
15: getfield #2 // Field value:I
18: aload_1
19: monitorexit
20: ireturn
21: astore_2
22: aload_1
23: monitorexit
24: aload_2
25: athrow
Exception table:
from to target type
4 20 21 any
21 24 21 any
LineNumberTable:
line 7: 0
line 8: 4
line 9: 14
line 10: 21
LocalVariableTable:
Start Length Slot Name Signature
0 26 0 this Lcom/atGongDa/MultiThreading/Synchronized/jiShuQi;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 21
locals = [ class com/atGongDa/MultiThreading/Synchronized/jiShuQi, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
synchronized代码块被monitorenter和monitorexit包围,他俩之间做出了大量的判断,字节码并不是一行一行按顺序执行的,可能碰到ifle会跳转,碰到异常,查询异常表后跳转...最后锁一定会被释放
3: monitorenter
` ....
23: monitorexit
volatile通过加入内存屏障和禁止重排序优化来实现
对volatile变量进行写操作的时候,会在写操作前加入一条store屏障指令,将本地内存中的共享变量的值,刷新会主内存
对volatile变量进行读操作的时候,会在读操作前加入一条load屏障指令,从主内存中读
再次重申:volatile解决的是变量在多个线程之间的可见性, synchronized解决的是多个线程之间访问资源的同步性
我们完全可以使用Synchronized替代volatile 但是 后者不一定能替代前者,比如在获取变量的时候进行++操作,非原子性,导致非线程安全!
把JVM的运行环境该变成 -server, 当JVM运行在Server环境下,为了提高线程的效率,线程获取到的 类的属性值时,始终在自己的私有堆栈中获取,但是当它去更改类的属性值的时候,改变的确是公共堆栈中的属性!,也就是说,它获取到的属性值,就是一开始从公共堆栈中复制过去的值,不曾,也不能修改,
这也是volatile关键字出现的必要,保证了多个线程之间 属性的可见性 --> 强制从公共堆栈中获取变量的值,而不是从线程的私有数据栈中获取变量的值
直接运行下面一段代码:
public class demo01 {
boolean tag= true;
public void methodA(){
while(tag==true){
}
System.out.println("methodA end...");
}
public void setTag(){
this.tag=false;
}
public static void main(String[] args) {
demo01 demo01 = new demo01();
new Thread(()->{
demo01.methodA();
}).start();
new Thread(()->{
demo01.setTag();
}).start();
System.out.println("main endl...");
}
}
直接运行结果:
main endl...
methodA end...
更改JVM运行参数 -server再次运行
结果:
main endl;
更改JVM运行参数 -server再次运行,并将tag用volalite修饰
结果:
main endl...
methodA end...
验证了volatile实现了多个线程之间数据的可见性
将mathodA()进行如下修改
public void methodA(){
while(tag==true){
String string = new String();
synchronized (string){
}
}
System.out.println("methodA end...");
}
使用volatile做标记变量,如下代码,假设线程2执行前必须要等线程1做好初始化工作
volatile boolean tag = false;
//线程1:
Context = loadContext();
tag=true;
//线程2:
while(!tag){
sleep();
}
doSomethingWithContext();
硬盘-->内存-->CPU的缓存
volatile关键字起作用,依赖的是Lock指令
上图,从左到右,运算速度越来越高,每个线程都有属于自己的缓存,
参考书籍<
原文:https://www.cnblogs.com/ZhuChangwu/p/11150299.html