浅析多线程访问同一资源的问题
锁的概念:
多个线程在对同一个资源进行访问时要上锁
synchronized加在静态方法上和在代码中sychronized这个类是等价的:
加在非静态方法上和在代码中sychronized(this)是等价的
synchronized static void m() == sychronized (T.class) // 类锁
synchronized void m() == sychronized(this) // 对象锁
重点:
1、程序之中如果出现异常,默认情况下锁会被释放
底层源码有两个monitorexit,一个是锁正常情况下的退出,一个是异常情况下的退出
2、synchronized锁作用的是对象不是代码
synchronized锁升级的概念:
当只有一个线程访问时,在synchronized的对象(object)的markword上记录这个线程的ID,此时单个线程调用并没有加锁;如果有其他线程争用时会升级为轻量级锁,也叫自选锁,线程在那转圈访问锁是否被释放了,如果默认旋转一定时间(10次)还没有释放的话,锁就会升级为重量级锁,重量级锁时这个线程就会有一个队列,就会将这些线程放到等待队列中,等锁释放后根据队列中的顺序一次获得资源执行。
注:
加锁代码执行时间短,线程数少,用轻量级锁
执行时间长,线程数多,用重量级锁
synchronized(object),object不要用基础类型的,如String,Integer,Long等:
原因是java的自动封箱和拆箱在作怪,Integer,Long:当执行i++,实际上就是i=new Integer(i+1),所以执行完i++后i已经不是以前的那个对象了,同步块自然也就失效了
String: String定义的变量会放在常量池中,如果多个线程定义的String变量的值相等, 指向的地址是一致的,这样两个线程指向的实际就是同一个对象,这时锁就无效了。所以严格意义上来讲是不要用String常量来作为锁对象,但为了规避风险,最好直接就不要用
String作为锁对象
注:所以锁对象不要用基础的数据类型!!!
例子:如果有两以上的线程同时访问同一个共享资源,可能造成线程冲突,线程冲突会造成数据丢失、重复等严重问题。
以下通过两个线程同时访问同一个类,来表现线程冲突,如果产生冲突便会打印输出。
public class TestDemo {String name;public static void main(String[] args) {TestDemo td =new TestDemo();Thread t = new Thread() { @Override public void run() { try { while(true) { Thread.sleep(500); td.aa("ls"); } }catch (InterruptedException e) {e.printStackTrace();} } }; t.start(); Thread f = new Thread() { public void run() { try { while(true) { Thread.sleep(500); td.aa("aas"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; f.start();}public void aa(String name) {this.name=name;eq(name);}private void eq(String name) {if(!(name==this.name)) {System.out.println(this.name+" "+name);}}}
日志打印:
ls aasaas lsaas lsaas lsls aasaas lsaas ls
解决方法:可以使用synchronized关键字让线程同步。
线程的冲突:
在多个线程访问同一个对象的同一个属性时,才会出现线程冲突,线程冲突会造成数据丢失、重复等严重问题。为了避免这个问题可以加synchronized关键字让线程同步。
例子:
public class Test { String name; public static void main(String[] args) {final Test ts=new Test();final Test ts2=new Test();final Thread t=new Thread(){ @Override public void run(){ while (true){ try { ts.setName("张三"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }};t.start();final Thread t2=new Thread(){ @Override public void run(){ while (true){ try { ts2.setName("李四"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }};t2.start(); } /* 1,放在函数中锁住整个函数,只有前一个线程执行完下一个函数才能访问 * 2,放在代码块中,锁住需要共享的资源,推荐使用 */ public /*synchronized*/ void setName(String name){// 放在函数中锁住整个函数,只有前一个线程执行完下一个函数才能访问 // synchronized(this) {//2,放在代码块中,锁住需要共享的资源,推荐使用 this.name = name; eqName(name); // } } public void eqName(String name){if(name!=this.name) System.out.println(name+" "+this.name); }}