Java多线程之synchronized(二)

为了解决“非线程安全”带来的问题,上一节中使用的办法是用关键字synchronized修饰多个线程可能同时访问到的方法,但是这样写是存在一定的弊端的,比如线程A调用一个用synchronized修饰的同步方法,这个方法要执行很长时间,那么其它的线程必须无条件的等线程A执行完释放掉对象锁,当然前提是其他的线程也要访问这个同步方法。这种情况就可以用synchronized代码块来解决。在解决之前我先附上一段没优化之前的方法,这样就可以直观的看到效果差异。

证明synchronized方法的弊端,代码如下:

public class Entity {
	public static long beginTime1;
	public static long endTime1;
	public static long beginTime2;
	public static long endTime2;
}
public class Task_Synchronized {

	private String getData1;
	private String getData2;
        //两个子线程要调用的公共方法
	public synchronized void doLongTimeTask() {

		try {
			System.out.println("begin task");
			Thread.sleep(3000);                        //getData1和getdata2实际过程中可以是两个非常耗时的操作,这样看起来效果更名
			getData1 = "长时间处理任务后从远程返回的值1 threadName="
					+ Thread.currentThread().getName();
			getData2 = "长时间处理任务后从远程返回的值2 threadName="
					+ Thread.currentThread().getName();
			System.out.println(getData1);
			System.out.println(getData2);
			System.out.println("end task");

		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
        public static void main(String[] args) throws InterruptedException {

		Task_Synchronized task = new Task_Synchronized();
		MyThread1 t1 = new MyThread1(task);
		t1.start();
		MyThread2 t2 = new MyThread2(task);
		t2.start();
		Thread.sleep(10000);
		// 因为线程1和线程2哪个先执行不一定,所以比较了一下时间,开始的时间取比较小的值,结束的时间取较大的值
		long beginTime = Entity.beginTime1;
		if (Entity.beginTime2 < Entity.beginTime1) {
			beginTime = Entity.beginTime2;
		}
		long endTime = Entity.endTime1;
		if (Entity.endTime2 > Entity.endTime1) {
			endTime = Entity.endTime2;
		}
		System.out.println("耗时" + (endTime - beginTime) / 1000 + "s");
	}
        //第一个线程
	public static class MyThread1 extends Thread {

		private Task_Synchronized task;

		public MyThread1(Task_Synchronized task) {
			super();
			this.task = task;
		}

		@Override
		public void run() {
			super.run();
			Entity.beginTime1 = System.currentTimeMillis();
			task.doLongTimeTask();
			Entity.endTime1 = System.currentTimeMillis();
		}
	}
         //第二个线程
	public static class MyThread2 extends Thread {

		private Task_Synchronized task;

		public MyThread2(Task_Synchronized task) {
			super();
			this.task = task;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			super.run();
			Entity.beginTime2 = System.currentTimeMillis();
			task.doLongTimeTask();
			Entity.endTime2 = System.currentTimeMillis();
		}
	} 

     运行结果如下:从Task_Synchronized 类可以看出,synchronized修饰的是doLongTimeTask()方法,从执行结果也可以看出syschronized修饰方法的执行顺序是这样的:Thread—0必须把同步的方法全部执行完,释放掉对象锁之后,Thread—1才可以执行,也就是说无论哪个线程先抢上CPU,就要执行到底之后,另一个线程才可以上CPU执行,这样执行下来用时间是6s。

那么接下来看看用synchronized代码块怎么解决这个弊端,我写了一个例子,如下:

    public class Task_Synchronized {

	private String getData1;
	private String getData2;
        //没有synchronized修饰
	public void doLongTimeTask() {

		try {
			System.out.println("begin task");
			Thread.sleep(3000);
			String privateGetData1 = "长时间处理的任务1 threadName="
					+ Thread.currentThread().getName();
			String privateGetData2 = "长时间处理的任务1 threadName="
					+ Thread.currentThread().getName();                        //synchronized代码块
			synchronized (this) {
				getData1 = privateGetData1;
				getData2 = privateGetData2;
			}
			System.out.println(getData1);
			System.out.println(getData2);
			System.out.println("end task");

		} catch (InterruptedException e) {

			e.printStackTrace();
		}
	}
    }

    执行结果如下:只需要修改一下doLongTimeTask()这个方法即可,用synchronized代码块来代替synchronized修饰方法,从运行结果可以看到时间只用3s,时间缩短了是由于线程Thread—0访问Task_Synchronized类的同步代码块时,线程Thread—1仍然可以访问Task_Synchronized类里的非同步方法。在这里为什么会想到只同步变量getData1和getData2呢,我以前说过出现“非线程安全”的原因,其中有一个原因就是有多个线程同时访问成员变量时可能会出现“脏读”现象。

但是还有两个问题需要验证,那就是syschronized代码块里的内容真的是同步执行的吗?这个this真的代表当前类的对象锁吗?下面我写了一个例子来验证一下,如下:

	public static void main(String[] args) {

		Task task = new Task();
		ThreadA a = new ThreadA(task);
		a.setName("A");
		ThreadB b = new ThreadB(task);
		b.setName("B");
		a.start();
		b.start();

	}

	public static class ThreadA extends Thread {

		private Task task;

		public ThreadA(Task task) {
			super();
			this.task = task;
		}

		@Override
		public void run() {

			super.run();
			task.doLongTimeTask();
		}
	}

	public static class ThreadB extends Thread {

		private Task task;

		public ThreadB(Task task) {
			super();
			this.task = task;
		}

		@Override
		public void run() {

			super.run();
			task.doLongTimeTask();
		}
	}
}

class Task {

	public void doLongTimeTask() {

		for (int i = 0; i < 100; i++) {
			System.out.println("noSynchronized threadName="
					+ Thread.currentThread().getName() + " i=" + (i + 1));
		}
		System.out.println("*********************************");
		synchronized (this) {
			for (int i = 0; i < 100; i++) {
				System.out.println("Synchronized threadName="
						+ Thread.currentThread().getName() + " i=" + (i + 1));
			}
		}
	}

       运行结果如下:由图(a)可以看出来,两个线程在执行第一for循环的时候,是不同步交叉执行的,由图b1、b2、c1、c2可以看出来线程A排队执行完,线程B才开始执行,所以synchronized代码块是同步的。

        

图(a)                                                              图(b1)                                                             图(b2)   

   

                   图(c1)                      图(c2)

那么怎么验证synchronized使用的“对象监视器”是一个呢,也就是this代表的是当前类的对象锁。需要证明只有一个线程释放掉当前对象锁,其它的线程才可以执行。我写了一个例子,如下:

	public static void main(String[] args) {

		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		ThreadB b = new ThreadB(service);
		b.setName("B");
		a.start();
		b.start();
	}

	public static class ThreadA extends Thread {

		private MyService service;

		public ThreadA(MyService service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {

			super.run();
			service.serviceMethodA();

		}
	}

	public static class ThreadB extends Thread {

		private MyService service;

		public ThreadB(MyService service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {

			super.run();
			service.serviceMethodB();

		}
	}
}

class MyService {

	public void serviceMethodA() {
		synchronized (this) {

			try {
				System.out.println("A begin time=" + System.currentTimeMillis());
				Thread.sleep(5000);
				System.out.println("A end time=" + System.currentTimeMillis());
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
		}
	}

	public void serviceMethodB() {
		synchronized (this) {

			try {
				System.out.println("B begin time=" + System.currentTimeMillis());
				Thread.sleep(2000);
				System.out.println("B end time=" + System.currentTimeMillis());
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
		}
	}
    运行结果如下:从代码上可以看出,两个synchronized代码块里都有休眠方法,但是并没有影响线程的执行顺序,如果两个this不是同一把对象锁,那么在休眠的这段时间,线程肯定会出现交替执行的,从结果也可以看出来,线程A执行完之后,线程B才开始执行的,说明当线程A访问MyService的同步代码块serviceMethodA的时候,线程B对同步代码块serviceMethodB的访问将被阻塞。所以“对象监视器”是同一个。   
时间: 2024-10-17 20:01:48

Java多线程之synchronized(二)的相关文章

JAVA多线程之Synchronized关键字--对象锁的特点

一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的特点. 二,分析 synchronized可以修饰实例方法,如下形式: 1 public class MyObject { 2 3 synchronized public void methodA() { 4 //do something.... 5 } 这里,synchronized 关键字锁住的是当前对象.这也是称为对象锁的原因. 为啥锁住当前对象?因为 methodA()是个实例方法,要想执行methodA(),

Java多线程之synchronized关键字

一.synchronized锁住的不是代码块,是对象. 1 /** 2 * synchronized 对某个对象加锁 3 */ 4 public class SynchronizedTest { 5 6 private int count = 10; 7 private Object o = new Object(); 8 9 private void method() { 10 synchronized (o) { //任何线程想执行下面这段代码都需要拿到o这把锁 11 count--; 12

Java多线程之synchronized和volatile的比较

概述 在做多线程并发处理时,经常需要对资源进行可见性访问和互斥同步操作.有时候,我们可能从前辈那里得知我们需要对资源进行 volatile 或是 synchronized 关键字修饰处理.可是,我们却不知道这两者之间的区别,我们无法分辨在什么时候应该使用哪一个关键字.本文就针对这个问题,展开讨论. 版权说明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 本文作者:Coding-Naga 发表日期: 2016年4月5日 本文链接:http://blog.csdn.net/

Java多线程之synchronized及其优化

Synchronized和同步阻塞synchronized是jvm提供的同步和锁机制,与之对应的是jdk层面的J.U.C提供的基于AbstractQueuedSynchronizer的并发组件.synchronized提供的是互斥同步,互斥同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只有一个线程访问. 在jvm中,被synchronized修饰的代码块经javac编译之后,会在代码块前后分别生成一条monitorenter和moniterexit字节码指令,这两个字节码都需要一个

java多线程之synchronized

首先来看下一个场景,某电影院某个时间4个窗口同时在卖票,本场电影总共票只有100张,卖完为止.看下实际代码. package cn.com.thread; public class TestThread { public static void main(String[] args) { SellTicketThread t=new SellTicketThread(); new Thread(t,"窗口1").start(); new Thread(t,"窗口2"

Java多线程之synchronized(一)

在上节中已经说过了“非线程安全”是如何出现的,链接如下:http://www.cnblogs.com/chentong/p/5650137.html,那么怎么解决“非线程安全”问题呢,只需要在两个线程都需要同时访问的方法前面加上synchronized关键字即可,我只贴出需要修改的这个方法的代码,具体修改如下: public static class GetNum { private int num = 0; //两个线程访问同一个对象中的同步方法时一定是线程安全的 synchronized p

Java多线程之synchronized(五)

上篇介绍了用synchronized修饰static方式来实现“Class 锁”,今天要介绍另一种实现方式,synchronized(class)代码块,写法不一样但是作用是一样的.下面我附上一段代码来看一下synchronized(class)代码块的基本用法,如下: public static void main(String[] args) { Service4 s1 = new Service4(); Service4 s2 = new Service4(); ThreadA a = n

Java多线程之synchronized线程锁

1 package org.study2.javabase.ThreadsDemo.sync; 2 3 /** 4 * @Auther:GongXingRui 5 * @Date:2018/9/18 6 * @Description:synchronized线程锁 7 **/ 8 public class TicketApp { 9 public static void main(String args[]) { 10 Ticket ticket = new Ticket(); 11 Threa

JAVA多线程之volatile 与 synchronized 的比较

一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作 ②对该变量操作完后,在某个时间再把变量刷新回主内存 关于JAVA内存模型,更详细的可参考: 深入理解Java内存模型(一)——基础 因此,就存在内存可见性问题,看一个示例程序:(摘自书上) 1 public c