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

来源:互联网 发布:哈雷尔体测数据 编辑:程序博客网 时间:2024/05/22 22:57
(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();// 此句为 换行。
}
}


就可以同步了。


原创粉丝点击