(3)传统线程互斥技术 synchronized 经典解析。【线程同步】 外加【内部类与外部类 】

(1)首先回顾:内部类与外部类

点击打开链接


(2)线程同步(这一段文字源于借鉴)
    当多个线程访问同一个数据时,非常容易出现线程安全问题。
    
    这时候就需要用线程同步    Case:银行取钱问题,有以下步骤:    
    A、用户输入账户、密码,系统判断是否登录成功    
    B、用户输入取款金额    
    C、系统判断取款金额是否大于现有金额    
    D、如果金额大于取款金额,就成功,否则提示小于余额     
    
    现在模拟2个人同时对一个账户取款,多线程操作就会出现问题。这时候需要同步才行;    
    同步代码块:    synchronized (object) {        //同步代码    }    Java多线程支持方法同步,方法同步只需用用synchronized来修饰方法即可,那么这个方法就是同步方法了。    
    对于同步方法而言,无需显示指定同步监视器,同步方法监视器就是本身this   
    同步方法:    public synchronized void editByThread() {        //doSomething    }    需要用同步方法的类具有以下特征:
    A、该类的对象可以被多个线程访问   
    B、每个线程调用对象的任意都可以正常的结束,返回正常结果   
    C、每个线程调用对象的任意方法后,该对象状态保持合理状态


    不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全。  
    例如Account就是一个可变类,它的money就是可变的,当2个线程同时修改money时,程序就会出现异常或错误。   
    所以要对Account设置为线程安全的,那么就需要用到同步synchronized关键字。 
    
    下面的方法用synchronized同步关键字修饰,那么这个方法就是一个同步的方法。
    这样就只能有一个线程可以访问这个方法,    在当前线程调用这个方法时,此方法是被锁状态,同步监视器是this。
    只有当此方法修改完毕后其他线程才能调用此方法。    
    这样就可以保证线程的安全,处理多线程并发取钱的的安全问题。   
    public synchronized void drawMoney(double money) {        //取钱操作    }    
    注意:synchronized可以修饰方法、代码块,但不能修饰属性、构造方法        可变类的线程安全是以降低程序的运行效率为代价,
    为了减少线程安全所带来的负面影响,
    可以采用以下策略:    
    A、不要对线程安全类的所有方法都采用同步模式,只对那些会改变竞争资源(共享资源)的方法进行同步。  
    B、如果可变类有2中运行环境:单线程环境和多线程环境,则应该为该可变提供2种版本;线程安全的和非线程安全的版本。  
    
    在单线程下采用非线程安全的提高运行效率保证性能,在多线程环境下采用线程安全的控制安全性问题。        
    释放同步监视器的锁定    任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,
    那么何时会释放对同步监视器锁定?    程序无法显示的释放对同步监视器的锁定,线程可以通过以下方式释放锁定:   
    A、当线程的同步方法、同步代码库执行结束,就可以释放同步监视器   
    B、当线程在同步代码库、方法中遇到break、return终止代码的运行,也可释放    
    C、当线程在同步代码库、同步方法中遇到未处理的Error、Exception,导致该代码结束也可释放同步监视器   
    D、当线程在同步代码库、同步方法中,程序执行了同步监视器对象的wait方法,导致方法暂停,释放同步监视器    
    
    下面情况不会释放同步监视器:    
    A、当线程在执行同步代码库、同步方法时,程序调用了Thread.sleep()/Thread.yield()方法来暂停当前程序,当前程序不会释放同步监视器   
    B、当线程在执行同步代码库、同步方法时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。
    
    注意尽量避免使用suspend、resume


以下是向 传智播客 张孝祥老师 的学习总结
1,代码出错分析:

package com.itm.thread;

/*******************
 * 
 * 在静态方法中 不能 new 内部类的实例对象:内部类可以访问 外部类的成员变量,
 * 
 * 就是说:我能访问你的成员变量意味着你一定有了实例对象。
 * 
 * 而在 静态方法执行的时候  可以不用创建那个对象,就矛盾了。
 * 
 * main方法运行的时候,没有任何外部类的实力对象,而这个内部类创建了,
 * 实际上又可以访问外部类的成员变量,而没有这个成员变量。
 * 
 * 要想创建 内部类的实例对象,必须要创建外部类的实例对象,外部类的实力对象 必须要整出来。
 * 
 * (1)内部类不能访问局部变量 要加final的。
 * 
 * 	我能访问你的成员变量 意味着你一定有了实例对象。而在 静态方法中 执行的时候 不用创建 那个实力对象。
 * 
 * 
 * 
 * @author wang.dm
 * 
 */
public class TraditionalThreadSynchronized {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
  				final Outputer outputer = new Outputer();
  
 				new Thread(new Runnable(){
		 				@Override 
		 				public void run() { 
		 				
		 					while(true){ 
		 						try { 
		 								Thread.sleep(1000);
		 							}catch (InterruptedException e) { 
		 								e.printStackTrace(); 
		 						}
		 						outputer.output("zhangxiaoxiang"); 
 					
 					} 
 				} 
	}).start();
		 			
		 
	}
	

	class Outputer {
		String xxx="";
		public void output(String name) {
			int len = name.length();
			
			synchronized(name){
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
			}
		}
	}

}

上面代码报错,解决方案:
No enclosing instance of type TraditionalThreadSynchronized  is accessible.


Must qualify the allocation with an enclosing instance of type TraditionalThreadSynchronized (e.g. x.new A() where x 
 is an instance of TraditionalThreadSynchronized).

 方法被调用的时候  一定是 某对象身上的方法,因为他不是静态方法,一定是创建了外部类的对象,
这个方法运行的时候,一定有一个对象,
new Outputer();//这个家伙一定要找一个外部类,也就是 谁调用了 init()方法。


public class TraditionalThreadSynchronized {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		 new TraditionalThreadSynchronized().init();
	}
	//方法被调用的时候  一定是 某对象身上的方法,因为他不是静态方法,一定是创建了外部类的对象,
	// 这个方法运行的时候,一定有一个对象,
	private void init(){
		final Outputer outputer = new Outputer();//这个家伙一定要找一个外部类,也就是 谁调用了 init()方法。
		  
			new Thread(new Runnable(){
 				@Override 
 				 public void run() { 
 				
 					while(true){ 
 						try { 
 								Thread.sleep(1000);
 							}catch (InterruptedException e) { 
 								e.printStackTrace(); 
 						}
 						outputer.output("zhangxiaoxiang"); 
				
 					} 
 				} 
			}).start();
			
			new Thread(new Runnable(){
 				@Override 
 				 public void run() { 
 				
 					while(true){ 
 						try { 
 								Thread.sleep(1000);
 							}catch (InterruptedException e) { 
 								e.printStackTrace(); 
 						}
 						outputer.output("lihuoming"); 
				
 					} 
 				} 
			}).start();
	}

	class Outputer {
		String xxx="";
		public void output(String name) {
			int len = name.length();
			
			synchronized(name){
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
			}
		}
	}

}

上面代码,出现:中间代码被打乱,也就是多线程喜欢出的问题。一个事情没有办完,另一个事件就会发生,为了防止这个问题,我们用同步技术。
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();// 此句为 换行。


这段代码要实现原子性。就是说,当有一个县城来执行我的时候,别的县城不能够来执行我;就像厕所里的坑一样,嘎嘎
synchronized(name){// 起不到效果,互斥一定要做到同一个对象。
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();// 此句为 换行。
}
分析:两个线程同时用到了outputer对象:

class Outputer {
		String xxx="";
		public void output(String name) {
			int len = name.length();
			synchronized(xxx){
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
			}
		}
	}

如果改成:new Outputer().output("lihuoming");就有问题了。注意:一定要使用同一个对象。不过:xxx可以用this关键字来代替,不然就多此一举了。




2,倘若我要保护output整个方法的代码,怎么办:那就在这个方法里面用:synchronized这个关键字。


下面代码也是灭有问题的,保护的地区不一样,不过也都是同一个对象:

package com.itm.thread;

/*******************
 * 
 * 在静态方法中 不能 new 内部类的实例对象:内部类可以访问 外部类的成员变量,
 * 
 * 就是说:我能访问你的成员变量意味着你一定有了实例对象。
 * 
 * 而在 静态方法执行的时候  可以不用创建那个对象,就矛盾了。
 * 
 * main方法运行的时候,没有任何外部类的实力对象,而这个内部类创建了,
 * 实际上又可以访问外部类的成员变量,而没有这个成员变量。
 * 
 * 要想创建 内部类的实例对象,必须要创建外部类的实例对象,外部类的实力对象 必须要整出来。
 * 
 * (1)内部类不能访问局部变量 要加final的。
 * 
 * 	我能访问你的成员变量 意味着你一定有了实例对象。而在 静态方法中 执行的时候 不用创建 那个实力对象。
 * 
 * 
 * 
 * @author wang.dm
 * 
 */
public class TraditionalThreadSynchronized {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		 new TraditionalThreadSynchronized().init();
	}
	//方法被调用的时候  一定是 某对象身上的方法,因为他不是静态方法,一定是创建了外部类的对象,
	// 这个方法运行的时候,一定有一个对象,
	private void init(){
		final Outputer outputer = new Outputer();//这个家伙一定要找一个外部类,也就是 谁调用了 init()方法。
		  
			new Thread(new Runnable(){
 				@Override 
 				 public void run() { 
 				
 					while(true){ 
 						try { 
 								Thread.sleep(1000);
 							}catch (InterruptedException e) { 
 								e.printStackTrace(); 
 						}
 						outputer.output("zhangxiaoxiang"); 
				
 					} 
 				} 
			}).start();
			
			new Thread(new Runnable(){
 				@Override 
 				 public void run() { 
 				
 					while(true){ 
 						try { 
 								Thread.sleep(1000);
 							}catch (InterruptedException e) { 
 								e.printStackTrace(); 
 						}
 						outputer.output2("lihuoming");
 						
				
 					} 
 				} 
			}).start();
	}

	class Outputer {
		public void output(String name) {
			int len = name.length();
			synchronized(this){// 起不到效果,互斥一定要做到同一个对象。
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
			}
		}
		
		public synchronized void output2(String name) {
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
		}
	}
	
	

}

3, 下面代码 问:方法1,2能否分别和方法3 同步???

static class Outputer {
		public void output(String name) {
			int len = name.length();
			synchronized(this){// 起不到效果,互斥一定要做到同一个对象。
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
			}
		}
		
		public synchronized void output2(String name) {
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
		}
		
		public static synchronized void output3(String name) { // 静态的一定要在静态类中,所以:写这行代码时,就要再类上 加上 static了。
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();// 此句为 换行。
		}

结果:不可以同步!!!


方法1和方法3没有同步,类的字节码在内存中也算是一个对像,静态方法执行的时候不用创建实例对象,只有 字节码对象这个说法了,要想让他们同步:方法1也必须用字节码,this改为:
public void output(String name) {
int len = name.length();
synchronized(Outputer.class){// 起不到效果,互斥一定要做到同一个对象。
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();// 此句为 换行。
}
}


就可以同步了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值