zoukankan      html  css  js  c++  java
  • 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的访问将被阻塞。所以“对象监视器”是同一个。

      

  • 相关阅读:
    php友好格式化时间
    GraphicsMagick为图片添加水印
    Kali Linux下破解WIFI密码挂载usb无线网卡的方法
    用nginx做反向代理来访问防外链图片
    Nginx反向代理的目录访问问题
    Cookie存储中文报错:java.lang.IllegalArgumentException: Control character in cookie value or attribute.(转)
    4.0之后的hibernate获取sessionFactory
    Servlet的延迟加载和预加载
    hibernate注解(转)
    web项目路径问题
  • 原文地址:https://www.cnblogs.com/chentong/p/5654112.html
Copyright © 2011-2022 走看看