本文主要是介绍synchronized 同步操作应该是细粒度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
synchronized
Java良好的支持多线程。使用java,我们可以很轻松的编程一个多线程程序。但是使用多线程可能会引起并发访问的问题。synchronized和ThreadLocal都是用来解决多线程并发访问的问题。大家可能对synchronized较为熟悉,而对ThreadLocal就要陌生得多了。
并发问题。当一个对象被两个线程同时访问时,可能有一个线程会得到不可预期的结果。
一个简单的java类Studnet
- publicclassStudent{
- privateintage=0;
- publicintgetAge(){
- returnthis.age;
- }
- publicvoidsetAge(intage){
- this.age=age;
- }
- }
public class Student {private int age=0;public int getAge() {return this.age;}public void setAge(int age) {this.age = age;}
}
一个多线程类ThreadDemo.
这个类有一个Student的私有变量,在run方法中,它随机产生一个整数。然后设置到student变量中,从student中读取设置后的值。然后睡眠5秒钟,最后再次读student的age值。
- publicclassThreadDemoimplementsRunnable{
- Studentstudent=newStudent();
- publicstaticvoidmain(String[]agrs){
- ThreadDemotd=newThreadDemo();
- Threadt1=newThread(td,"a");
- Threadt2=newThread(td,"b");
- t1.start();
- t2.start();
- }
- /*(non-Javadoc)
- *@seejava.lang.Runnable#run()
- */
- publicvoidrun(){
- accessStudent();
- }
- publicvoidaccessStudent(){
- StringcurrentThreadName=Thread.currentThread().getName();
- System.out.println(currentThreadName+"isrunning!");
- //System.out.println("firstreadageis:"+this.student.getAge());
- Randomrandom=newRandom();
- intage=random.nextInt(100);
- System.out.println("thread"+currentThreadName+"setageto:"+age);
- this.student.setAge(age);
- System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
- try{
- Thread.sleep(5000);
- }
- catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
- }
- }
public class ThreadDemo implements Runnable{Student student = new Student();public static void main(String[] agrs) {ThreadDemo td = new ThreadDemo();Thread t1 = new Thread(td,"a");Thread t2 = new Thread(td,"b");t1.start();t2.start();}
/* (non-Javadoc)* @see java.lang.Runnable#run()*/public void run() {accessStudent();}public void accessStudent() {String currentThreadName = Thread.currentThread().getName();System.out.println(currentThreadName+" is running!");// System.out.println("first read age is:"+this.student.getAge());Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());}}
运行这个程序,屏幕输出如下:
a is running!
b is running!
thread b set age to:33
thread b first read age is:33
thread a set age to:81
thread a first read age is:81
thread b second read age is:81
thread a second read age is:81
需要注意的是,线程a在同一个方法中,第一次读取student的age值与第二次读取值不一致。这就是出现了并发问题。
synchronized
上面的例子,我们模似了一个并发问题。Java提供了同步机制来解决并发问题。synchonzied关键字可以用来同步变量,方法,甚至同步一个代码块。
使用了同步后,一个线程正在访问同步对象时,另外一个线程必须等待。
Synchronized同步方法
现在我们可以对accessStudent方法实施同步。
public synchronized void accessStudent()
再次运行程序,屏幕输出如下:
a is running!
thread a set age to:49
thread a first read age is:49
thread a second read age is:49
b is running!
thread b set age to:17
thread b first read age is:17
thread b second read age is:17
加上了同步后,线程b必须等待线程a执行完毕后,线程b才开始执行。
对方法进行同步的代价是非常昂贵的。特别是当被同步的方法执行一个冗长的操作。这个方法执行会花费很长的时间,对这样的方法进行同步可能会使系统性能成数量级的下降。
Synchronized同步块
在accessStudent方法中,我们真实需要保护的是student变量,所以我们可以进行一个更细粒度的加锁。我们仅仅对student相关的代码块进行同步。
- synchronized(this){
- Randomrandom=newRandom();
- intage=random.nextInt(100);
- System.out.println("thread"+currentThreadName+"setageto:"+age);
- this.student.setAge(age);
- System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
- try{
- Thread.sleep(5000);
- }
- catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- }
synchronized(this) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}
运行方法后,屏幕输出:
a is running!
thread a set age to:18
thread a first read age is:18
b is running!
thread a second read age is:18
thread b set age to:62
thread b first read age is:62
thread b second read age is:62
需要特别注意这个输出结果。
这个执行过程比上面的方法同步要快得多了。
只有对student进行访问的代码是同步的,而其它与部份代码却是异步的了。而student的值并没有被错误的修改。如果是在一个真实的系统中,accessStudent方法的操作又比较耗时的情况下。使用同步的速度几乎与没有同步一样快。
使用同步锁
稍微把上面的例子改一下,在ThreadDemo中有一个私有变量count,。
private int count=0;
在accessStudent()中, 线程每访问一次,count都自加一次, 用来记数线程访问的次数。
- try{
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}
为了模拟线程,所以让它每次自加后都睡眠5秒。
accessStuden()方法的完整代码如下:
- StringcurrentThreadName=Thread.currentThread().getName();
- System.out.println(currentThreadName+"isrunning!");
- try{
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- System.out.println("thread"+currentThreadName+"readcount:"+this.count);
- synchronized(this){
- Randomrandom=newRandom();
- intage=random.nextInt(100);
- System.out.println("thread"+currentThreadName+"setageto:"+age);
- this.student.setAge(age);
- System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
- try{
- Thread.sleep(5000);
- }
- catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- }
- System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
String currentThreadName = Thread.currentThread().getName();System.out.println(currentThreadName+" is running!");try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}System.out.println("thread "+currentThreadName+" read count:"+this.count);synchronized(this) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:2
thread a set age to:49
thread a first read age is:49
thread b read count:2
thread a second read age is:49
thread b set age to:7
thread b first read age is:7
thread b second read age is:7
我们仍然对student对象以synchronized(this)操作进行同步。
我们需要在两个线程中共享count失败。
所以仍然需要对count的访问进行同步操作。
- synchronized(this){
- try{
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- }
- System.out.println("thread"+currentThreadName+"readcount:"+this.count);
- synchronized(this){
- Randomrandom=newRandom();
- intage=random.nextInt(100);
- System.out.println("thread"+currentThreadName+"setageto:"+age);
- this.student.setAge(age);
- System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
- try{
- Thread.sleep(5000);
- }
- catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- }
- System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
- longendTime=System.currentTimeMillis();
- longspendTime=endTime-startTime;
- System.out.println("花费时间:"+spendTime+"毫秒");
synchronized(this) {try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName+" read count:"+this.count);synchronized(this) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());long endTime = System.currentTimeMillis();long spendTime = endTime - startTime;System.out.println("花费时间:"+spendTime +"毫秒");
程序运行后,屏幕输出
a is running!
b is running!
thread a read count:1
thread a set age to:97
thread a first read age is:97
thread a second read age is:97
花费时间:10015毫秒
thread b read count:2
thread b set age to:47
thread b first read age is:47
thread b second read age is:47
花费时间:20124毫秒
我们在同一个方法中,多次使用synchronized(this)进行加锁。有可能会导致太多额外的等待。
应该使用不同的对象锁进行同步。
设置两个锁对象,分别用于student和count的访问加锁。
- privateObjectstudentLock=newObject();
- privateObjectcountLock=newObject();
- accessStudent()方法如下:
- longstartTime=System.currentTimeMillis();
- StringcurrentThreadName=Thread.currentThread().getName();
- System.out.println(currentThreadName+"isrunning!");
- //System.out.println("firstreadageis:"+this.student.getAge());
- synchronized(countLock){
- try{
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- }
- System.out.println("thread"+currentThreadName+"readcount:"+this.count);
- synchronized(studentLock){
- Randomrandom=newRandom();
- intage=random.nextInt(100);
- System.out.println("thread"+currentThreadName+"setageto:"+age);
- this.student.setAge(age);
- System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
- try{
- Thread.sleep(5000);
- }
- catch(InterruptedExceptionex){
- ex.printStackTrace();
- }
- }
- System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
- longendTime=System.currentTimeMillis();
- longspendTime=endTime-startTime;
- System.out.println("花费时间:"+spendTime+"毫秒");
private Object studentLock = new Object();
private Object countLock = new Object();accessStudent()方法如下:long startTime = System.currentTimeMillis();String currentThreadName = Thread.currentThread().getName();System.out.println(currentThreadName+" is running!");// System.out.println("first read age is:"+this.student.getAge());synchronized(countLock) {try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName+" read count:"+this.count);synchronized(studentLock) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());long endTime = System.currentTimeMillis();long spendTime = endTime - startTime;System.out.println("花费时间:"+spendTime +"毫秒");
这样对count和student加上了两把不同的锁。
运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:1
thread a set age to:48
thread a first read age is:48
thread a second read age is:48
花费时间:10016毫秒
thread b read count:2
thread b set age to:68
thread b first read age is:68
thread b second read age is:68
花费时间:20046毫秒
与两次使用synchronized(this)相比,使用不同的对象锁,在性能上可以得到更大的提升。
由此可见synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。
可见,同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,所以同步操作应该是细粒度的。如果同步使用得当,带来的性能开销是微不足道的。使用同步真正的风险是复杂性和可能破坏资源安全,而不是性能。
这篇关于synchronized 同步操作应该是细粒度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!