IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Java多线程】synchronized同步方法 -> 正文阅读

[Java知识库]【Java多线程】synchronized同步方法

目录

1. 方法内的变量为线程安全

2. 实例变量非线程安全

3. 多个对象多个锁

4.?synchronized方法与锁对象

5. 脏读

6.?synchronized锁重入

7. 出现异常,锁自动释放

8. 同步不具有继承性


“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取得的数据其实是被更改过的。

1. 方法内的变量为线程安全

“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。

2. 实例变量非线程安全

如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。只需要在方法前加关键字synchronized即可。

两个线程访问同一个对象中的同步方法时一定是线程安全的。

3. 多个对象多个锁

先来看一个示例:

(1) HasSelfPrivateNum.java

public class HasSelfPrivateNum {
	private int num = 0;

	synchronized public void addI(String username) {
		try {
			if (username.equals("a")) {
				num = 100;
				System.out.println("a set over!");
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("b set over!");
			}
			System.out.println(username + " num=" + num);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

(2)?ThreadA.java

public class ThreadA extends Thread {

	private HasSelfPrivateNum numRef;

	public ThreadA(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}

	@Override
	public void run() {
		super.run();
		numRef.addI("a");
	}
}

(3)?ThreadB.java

public class ThreadB extends Thread {

	private HasSelfPrivateNum numRef;

	public ThreadB(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}

	@Override
	public void run() {
		super.run();
		numRef.addI("b");
	}
}

(4) 主类 Run.java

public class Run {
	public static void main(String[] args) {

		HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
		HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

		ThreadA athread = new ThreadA(numRef1);
		athread.start();

		ThreadB bthread = new ThreadB(numRef2);
		bthread.start();
	}
}

运行结果如下:

a set over!
b set over!
b num=200
a num=100

上面的示例是两个线程分别访问同一个类的不同实例的相同名称的同步方法,效果确实以异步的方式运行的。

从上面程序的运行结果来看,虽然在HasSelfPrivateNum.java中使用了synchronized关键字,但打印的顺序却不是同步的,是交叉的。为什么是这样的结果呢?

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。

如果多个线程访问多个对象,则JVM会创建多个锁。

同步的单词为synchronized,异步的单词为asynchronized。

4.?synchronized方法与锁对象

上面3中的示例证明了线程锁的是对象。调用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本没有同步的必要。

那其他方法在调用时会是什么效果呢?如何查看到Lock锁对象的效果呢?下面我们用一个示例来说明:

(1)MyObject.java

public class MyObject {

	synchronized public void methodA() {
		try {
			System.out.println("begin methodA threadName="
					+ Thread.currentThread().getName());
			Thread.sleep(5000);
			System.out.println("end endTime=" + System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void methodB() {
		try {
			System.out.println("begin methodB threadName="
					+ Thread.currentThread().getName() + " begin time="
					+ System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("end");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

(2)?ThreadA.java

public class ThreadA extends Thread {

	private MyObject object;

	public ThreadA(MyObject object) {
		super();
		this.object = object;
	}

	@Override
	public void run() {
		super.run();
		object.methodA();
	}
}

(3)?ThreadB.java

public class ThreadB extends Thread {

	private MyObject object;

	public ThreadB(MyObject object) {
		super();
		this.object = object;
	}

	@Override
	public void run() {
		super.run();
		object.methodB();
	}
}

(4)?Run.java

public class Run {

	public static void main(String[] args) {
		MyObject object = new MyObject();

		ThreadA a = new ThreadA(object);
		a.setName("A");
		ThreadB b = new ThreadB(object);
		b.setName("B");

		a.start();
		b.start();
	}

}

运行结果:

begin methodA threadName=A
begin methodB threadName=B begin time=1633888880077
end endTime=1633888885092
end

通过上面的实验可以得知,虽然线程A先持有了object对象的锁,但线程B完全可以异步调用非synchronized类型的方法。

继续实验,将MyObject.java文件中的methodB()方法前面加上synchronized关键字,本示例是两个线程访问同一个对象的两个同步的方法。运行结果为:

begin methodA threadName=A
end endTime=1633889057071
begin methodB threadName=B begin time=1633889057075
end

本小节的实验结论是:

(1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。

(2)A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。

【注】:可以理解为,synchronized锁对象,会同时锁住该对象中所有synchronized类型的方法。

5. 脏读

有些程序虽然在赋值时进行了同步,但在取值时有可能出现一些意想不到额意外,这种情况就是“脏读”(dirtyRead)。

根据上面4中的结论可见,避免脏读的办法可以在setValue()和getValue()方法前同时加上synchronized关键字。

当A线程调用MyObject对象中加了synchronized关键字的X方法时,A线程就获得了A方法锁,确切地讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。

当A线程调用MyObject对象加入synchronized关键字时,A对象就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,而就是释放对象锁之后才可以调用。这是A线程已经执行了一个完整的任务。

脏读一定出现在操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。

6.?synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是说在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。

【注】:意思就是,当一个线程得到一个对象锁之后,可以访问这个对象所有加synchronized的方法,当然也可以访问非synchronized方法。

可重入锁的概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象还没有释放,当其再次想获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

这也证明了在一个synchronized方法/块的内部调用本类的其他方法/块时,是永远可以得到锁的。

下面通过一个示例来说明:

(1)Service.java

public class Service {

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

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

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

}

(2)?MyThread.java

public class MyThread extends Thread {
	
	@Override
	public void run() {
		Service service = new Service();
		service.service1();
	}

}

(3) Run.java

public class Run {

	public static void main(String[] args) {
		MyThread t = new MyThread();
		t.start();
	}

}

运行结果:

service1
service2
service3

可重入锁也支持在父子继承的环境中。

下面的示例证明说了这一点:

(1)Main.java

public class Main {

	public int i = 10;

	synchronized public void operateIMainMethod() {
		try {
			i--;
			System.out.println("main print i=" + i);
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

(2) Sub.java

public class Sub extends Main {

	synchronized public void operateISubMethod() {
		try {
			while (i > 0) {
				i--;
				System.out.println("  -- sub print i=" + i);
				Thread.sleep(100);
				this.operateIMainMethod();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

(3) MyThread.java

public class MyThread extends Thread {

	@Override
	public void run() {
		Sub sub = new Sub();
		sub.operateISubMethod();
	}

}

(4) Run.java

public class Run {

	public static void main(String[] args) {
		MyThread t = new MyThread();
		t.start();
	}

}

运行结果:

  -- sub print i=9
main print i=8
  -- sub print i=7
main print i=6
  -- sub print i=5
main print i=4
  -- sub print i=3
main print i=2
  -- sub print i=1
main print i=0

此实验说明,当存在父子继承关系时,子类完全可以通过“可重入锁”调用父类的同步方法。

7. 出现异常,锁自动释放

当一个线程执行的代码出现异常时,其持有的锁会自动释放。

8. 同步不具有继承性

同步不可以继承。

下面通过一个示例来验证。

(1) Main.java

public class Main {

    synchronized public void serviceMethod() {
        try {
            System.out.println("main "  + Thread.currentThread().getName()  +  " sleep begin threadName="
                    + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("main "  + Thread.currentThread().getName()  +  " sleep end threadName="
                    + " time="
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

(2) Sub.java

public class Sub extends Main {

    @Override
    public void serviceMethod() {
        try {
            System.out.println("  -- sub "  + Thread.currentThread().getName()  +  " sleep begin threadName="
                    + " time = "
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("  -- sub "  + Thread.currentThread().getName()  +  " sleep end threadName="
                    + " time="
                    + System.currentTimeMillis());
            super.serviceMethod();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

(3) MyThreadA.java

public class MyThreadA extends Thread {

    private Sub sub;

    public MyThreadA(Sub sub) {
        super();
        this.sub = sub;
    }

    @Override
    public void run() {
        sub.serviceMethod();
    }

}

(4) MyThreadB.java

public class MyThreadB extends Thread {

    private Sub sub;

    public MyThreadB(Sub sub) {
        super();
        this.sub = sub;
    }

    @Override
    public void run() {
        sub.serviceMethod();
    }
}

(5) Run.java

public class Run {

    public static void main(String[] args) {
        Sub subRef = new Sub();

        MyThreadA a = new MyThreadA(subRef);
        a.setName("A");
        a.start();

        MyThreadB b = new MyThreadB(subRef);
        b.setName("B");
        b.start();
    }

}

运行结果:

  -- sub B sleep begin threadName= time = 1633892050675
  -- sub A sleep begin threadName= time = 1633892050675
  -- sub A sleep end threadName= time=1633892055699
  -- sub B sleep end threadName= time=1633892055699
main A sleep begin threadName= time=1633892055699
main A sleep end threadName= time=1633892060700
main B sleep begin threadName= time=1633892060700
main B sleep end threadName= time=1633892065701

由此示例可以看到,同步不能继承,所以还得在子类的方法中添加synchronized关键字。添加后的运行效果如下:

  -- sub A sleep begin threadName= time = 1633892420973
  -- sub A sleep end threadName= time=1633892425991
main A sleep begin threadName= time=1633892425991
main A sleep end threadName= time=1633892430991
  -- sub B sleep begin threadName= time = 1633892430991
  -- sub B sleep end threadName= time=1633892435991
main B sleep begin threadName= time=1633892435991
main B sleep end threadName= time=1633892440992
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-12 23:18:12  更:2021-10-12 23:19:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:12:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码