java多线程 synchronized

原创 2015年11月17日 19:27:36

在java多线程并发操作中,如果不加任何的同步控制,有可能会出现一些错误的情况。

package com.lql.thread;


public class MyTask10 implements Runnable {

	private int n = 10;


	public MyTask10() {
	}
	
	public void method(){
		while (n > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-->"
					+ n--);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		method();
	}

	public static void main(String[] args) {
		MyTask10 task10 = new MyTask10();
		for (int i = 0; i < 5; i++) {
			Thread thread = new Thread(task10);
			thread.start();
		}
	}
}
一次运行结果:

Thread-1-->10
Thread-2-->9
Thread-0-->8
Thread-3-->9
Thread-4-->7
Thread-0-->6
Thread-2-->4
Thread-1-->5
Thread-3-->6
Thread-4-->3
Thread-3-->1
Thread-2-->-1
Thread-0-->2
Thread-1-->0
Thread-4-->-2

可以看到一些数字打印了两遍,一些打印了一遍,更为要命的是还打印出来一些负数。如果学习过操作系统并发的知识的话,这个问题其实不难理解。JVM为每个线程分配时间片,并选择了一个线程将处理机分配给他。但是这个线程执行执行到sleep()时进入了休眠状态(也就是阻塞态),这时JVM就会再选择一个线程分配处理机资源。设想如果当n=1时,有三个线程都进入了都执行到Thread.sleep(10),当这三个线程被唤醒使就会去执行打印语句部分。因为他们都已经经历了n>0条件,所以都会执行打印语句,而且执行n--,就可能出现上述出现-1,-2的情况。打印两次也是这样,当一个线程执行完打印n,还没来得及对n进行减一,就被剥夺了处理机。另一个线程此时又打印了一次n。这样的情况在并发编程中是不允许的,就比如买火车票,上述情况就如同有的票被卖给两个人,没有票了还在售卖。

java中提供了同步关键字:synchrnoized(同步)来解决多线程并发操作中的由于共享资源导致的资源冲突。

package com.lql.thread;


public class MyTask10 implements Runnable {

	private int n = 10;


	public MyTask10() {
	}
	
	public synchronized void method(){
		while (n > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-->"
					+ n--);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		method();
	}

	public static void main(String[] args) {
		MyTask10 task10 = new MyTask10();
		for (int i = 0; i < 5; i++) {
			Thread thread = new Thread(task10);
			thread.start();
		}
	}
}
运行结果:

Thread-0-->10
Thread-0-->9
Thread-0-->8
Thread-0-->7
Thread-0-->6
Thread-0-->5
Thread-0-->4
Thread-0-->3
Thread-0-->2
Thread-0-->1
在方法前加一个synchronized关键字,该方法就变成了一个同步方法,就不会出现上一个那样错误的情况了。但是又有一个奇怪的情况:在主线程中我开启了五个线程去执行task10这个任务,为什么只有打印出来的线程名只有一个Thread-0呢。这里就得说明一下synchronized同步的机制了。在java中每个对象都有一个对象锁,一个对象的对象锁同一时间只能由一个线程获取。一个线程从获取该对象锁的时刻起到该线程释放该对象锁止,其他线程是无法取得该对象锁的。如果线程无法获取该对象的对象锁,那么这些线程是无法去执行该对象的同步方法的。例子的运行结果就是因为:Thread-0取得了task10这个对象的对象锁,他就一直占用着task10对象的对象锁。另外四个线程只能干巴巴的看着,进不了被同步的方法里去。

我在同步方法之前和之后又加了两个普通的输出语句,来说明对象锁对于同步代码块的锁定。

package com.lql.thread;


public class MyTask10 implements Runnable {

	private int n = 10;


	public MyTask10() {
	}
	
	public synchronized void method(){
		while (n > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-->"
					+ n--);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName() + "hello");
		
		method();
		
		System.out.println(Thread.currentThread().getName()+"hello");
	}

	public static void main(String[] args) {
		MyTask10 task10 = new MyTask10();
		for (int i = 0; i < 5; i++) {
			Thread thread = new Thread(task10);
			thread.start();
		}
	}
}
运行结果:

Thread-1hello
Thread-3hello
Thread-0hello
Thread-4hello
Thread-2hello
Thread-1-->10
Thread-1-->9
Thread-1-->8
Thread-1-->7
Thread-1-->6
Thread-1-->5
Thread-1-->4
Thread-1-->3
Thread-1-->2
Thread-1-->1
Thread-1hello
Thread-2hello
Thread-4hello
Thread-0hello
Thread-3hello

可以看到确实开启了五个线程,而且同步方法里的代码只有一个线程在执行(而且这次是Thread-1占用了对象锁),执行完同步方法里的代码后,其他线程这时又登场了。


我们再来看一种情况。

package com.lql.thread;


public class MyTask10 implements Runnable {

	private int n = 10;


	public MyTask10() {
	}
	
	public synchronized void method(){
		while (n > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-->"
					+ n--);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		method();
		
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			Thread thread = new Thread(new MyTask10());
			thread.start();
		}
	}
}


与上边的情况的不同之处在于我为每个线程都分配了一个任务,每个线程锁执行的是不同的MyTask10类的对象。
Thread-0-->10
Thread-3-->10
Thread-2-->10
Thread-1-->10
Thread-4-->10
Thread-1-->9
Thread-2-->9
Thread-3-->9
Thread-0-->9
Thread-4-->9
Thread-3-->8
Thread-2-->8
Thread-1-->8
Thread-0-->8
Thread-4-->8
Thread-1-->7
Thread-0-->7
Thread-3-->7
Thread-2-->7
Thread-4-->7
Thread-1-->6
Thread-2-->6
Thread-0-->6
Thread-3-->6
Thread-4-->6
Thread-1-->5
Thread-3-->5
Thread-0-->5
Thread-2-->5
Thread-4-->5
Thread-1-->4
Thread-0-->4
Thread-2-->4
Thread-3-->4
Thread-4-->4
Thread-1-->3
Thread-2-->3
Thread-0-->3
Thread-3-->3
Thread-4-->3
Thread-1-->2
Thread-0-->2
Thread-2-->2
Thread-3-->2
Thread-4-->2
Thread-1-->1
Thread-0-->1
Thread-2-->1
Thread-3-->1
Thread-4-->1
这种情况其实更好理解,因为五个线程所执行的是不同的任务,所以就不存在共享资源的问题。因为这五个线程每次所执行的是五个不同的MyTask10对象的method方法,每线程各自完成各自的任务,互不影响,如果从对象锁的角度来说是因为五个对象都有各自的对象锁,一个线程占用一个对象的对象锁也不会影响另外一个线程去使用另一个对象的对象锁。即便没有synchrnoized来实现同步也不会出现一些错误的结果(就是打印出0,-1,-2的情况),没有资源共享时也没有必要同步。


同步静态方法:

这里我将method方法改为了static方法,static方法又称类方法,static修饰的方法或者变量在内存中只有一个副本,不管创建多少个类的实例,都只有一个副本。

package com.lql.thread;


public class MyTask10 implements Runnable {

	private static int n = 10;


	public MyTask10() {
	}
	
	public static synchronized void method(){
		while (n > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-->"
					+ n--);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		method();
		
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			Thread thread = new Thread(new MyTask10());
			thread.start();
		}
	}
}

运行结果:

Thread-0-->10
Thread-0-->9
Thread-0-->8
Thread-0-->7
Thread-0-->6
Thread-0-->5
Thread-0-->4
Thread-0-->3
Thread-0-->2
Thread-0-->1

同步静态方法又出现了不一样的情况,我在主线程中明明是用开了五个线程,而且五个线程分别取执行五个不同的任务。为什么又是只有Thread-0线程执行呢?   原因是同步的方法是静态方法(类方法),如果同步的是类方法,线程获取的对象锁就会是累的对象,MyTask10.class这个对象,我们知道一个类只有一个类对象。Thread-0占用了类对象的对象锁,其他线程也就无法进入同步方法区了。总结起来就是同步静态方法,线程的拥有的对象锁是类对象的对象锁,无论定义多少个该类的对象,都不能进入同步区。


上边说的都是同步方法,java还提供了另外一种细粒度的实现线程同步的机制——同步代码块。

package com.lql.thread;


public class MyTask11 implements Runnable {

	private int n = 10;
	
	private String name = "";
	
	public MyTask11(){
		
	}
	
	public void method(){
		
		System.out.println(Thread.currentThread().getName() +"hello");
		
		synchronized (this) {
			while(n > 0){
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() +"-->" + n--);
			}
		}
	}
	
	@Override
	public void run() {
	// TODO Auto-generated method stub
		method();
	}
	
	

	public static void main(String[] args) {
		MyTask11 task11 = new MyTask11();
		Thread thread = new Thread(task11);
		Thread thread2 = new Thread(task11);
		thread.start();
		thread2.start();
	}
}
Thread-0hello
Thread-1hello
Thread-0-->10
Thread-0-->9
Thread-0-->8
Thread-0-->7
Thread-0-->6
Thread-0-->5
Thread-0-->4
Thread-0-->3
Thread-0-->2
Thread-0-->1

同步方法所能实现的同步,同步代码块也都能实现。同步方法有时同步的范围太大,有时只是一小部分的代码是临界区,如果将整个方法都整成同步方法并不好,这与并发的理念是相悖的。所以就有了同步代码块。

synchrnized(this)括号中的对象指定当前线程获取哪个对象的对象锁,因为对象的锁是唯一的,所以一个线程获取了对象锁,在该线程释放锁之前,其他线程也就无法再取得该对象锁,在一个线程执行完同步块代码后,才释放该对象锁。synchrnized()括号里可以使任何一个对象,因为一个确定的对象他的对象锁是唯一的。只要一个线程取得了对象锁,其他线程都无法进入同步块。


总结以上所有内容:

1.当并发编程共享资源时,要使用同步来确保同一时刻只有一个线程进入临界区。

2.java中每个对象都有一个对象锁,synchronized就是基于对象锁来实现同步,一个线程占用了对象锁其他线程就必须等待上一个线程释放了对象锁才能进入被同步的临界区。

3.同步静态方法时,线程获取的对象锁是类对象(MyTask.class)的对象锁。无论定义多少个对象,被同步的静态方法都无动于衷。哪个线程有了类对象的对象,就让哪个线程进到同步区。

4.同步块是一种细粒度实现同步的方法,同步块指定执行到同步块的线程获取哪个对象的对象锁。由于一个确定对象的对象锁唯一,所以能够实现只有一个线程进入同步区代码执行。


以上是个人的理解,如有错误或不当欢迎批评指正。



java 多线程synchronized互斥锁demo

  • 2016年12月16日 14:34
  • 665B
  • 下载

java多线程中 synchronized 和 Lock的区别

今天复习了一下多线程的知识,顺便总结了一下synchronized和lock的区别,这里重点只讲他们的区别。首先lock是java.util.concurrent类库中的类的对象(lock有读写锁,可...

浅谈java中的多线程及synchronized锁重入的含义

多线程编程有两种方法: 1,继承Thread类 2,实现Runnable接口 注意点: 1,Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待线程对象的run...

跟着实例学习java多线程3-synchronized的多种写法有何区别?

同步代码块是一种有效实现操作原子性的方法,上一章我们讲了一些同步的原子操作的基础。 现在我们回忆一下上一章的两个问题。 1:不同的synchronized的写法有什么区别,又该怎么写创建线程的代码呢?...
  • andy_gx
  • andy_gx
  • 2015年01月20日 23:29
  • 761

Java多线程(3)——同步与synchronized关键字

线程同步在单线程程序中,每次只做一件事情,后面的事情等待前面的事情完成才进行,但如果使用多线程程序,就会出现多个线程抢占资源的问题,线程的优先级部分地解决了这个问题,但还不够,Java提供线程同步机制...
  • picway
  • picway
  • 2016年10月11日 17:19
  • 749

简单的Java1.4版synchronized多线程的死锁演示

尽量避开死锁是开发的宗旨,因为死锁不是程序错误,是设计错误,一旦出现线程死锁,很难分析出来常见的线程死锁就是同步模块的嵌套,面试常用以下是线程死锁示例:class Test implements Ru...

java多线程(四)synchronized关键字修饰方法

在之前的博客中我们介绍了条件对象和锁对象,两者结合使用才能起到比较好的互斥与同步效果,大家可能觉得有些麻烦,有没有将两者结合起来的工具呢,有!java提供了synchronized关键字来实现线程的互...

java关键字volatile和synchronized在多线程中的应用

以前没有弄懂validate和synchronized,借此新开博之际记录下。 volatile(不稳定):java关键字用来修饰变量,不可用来修饰方法。 在java语法中对于变量值读写的操作...

【深入分析Java多线程】(5)synchronized和volatile分析

一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间-...

java多线程总结四:volatile、synchronized示例

1、synchronized保证同步 先看一个生成偶数的类 package demo.thread; /** *这是一个int生成器的抽象类 * */ public abstract c...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java多线程 synchronized
举报原因:
原因补充:

(最多只允许输入30个字)