(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();// 此句为 换行。
}
}
就可以同步了。