1 多线程安全问题
1.1 起因
在一个进程中,多个线程共享相同的资源(数据或者引用地址),出现的多个线程先后使用、更新或者删除数据造成的重复读,幻读或者脏读问题。
1.2 非安全线程—使用继承Thread类的方式
1.2.1 example
public class SellTicketThread extends Thread{ int tickets=100; public void run(){ while(tickets>0){ System.out.println(getName()+"--"+tickets); tickets--; } } } public static void main(String[] args){ Thread t1=newSellTicketThread(); Thread t2=newSellTicketThread(); Thread t3=newSellTicketThread(); Thread t4=newSellTicketThread(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t4.setName("窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); }1.2.2解决方案
简单的多线程共享基本数据类型的数据时,使用线程子类内静态化属性的方法可以解决一部分重复读的问题,但是不能解决线程首次并发时的重复读问题。
public class SellTicketThread extends Thread{ public static int tickets=100; public void run(){ while(tickets>0){ System.out.println(getName()+"--"+tickets); try { Thread.sleep(10); } catch (InterruptedExceptione) { e.printStackTrace(); } tickets--; } } }1.3 使用实现Runnable接口的方式
1.3.1 example
简单的多线程共享基本数据类型的数据时,使用实现Runnable接口的方式时,由于线程只是实现了run方法,采用的是Runnable实现类的全局变量,而不是像实现Thread类的方式继承了Thread的属性,能够保持共享属性的完全隔离性(除首次开启线程时)。那么,采用实现接口的方式,在使用这个全局变量时,可能存在重复读,脏读,幻读等安全问题。
public class SellTicketTask implements Runnable{ private static int ticket=100; public void run() { while(ticket>0){ try { Thread.sleep(10); } catch (InterruptedExceptione1) { // TODO Auto-generatedcatch block e1.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"--"+ticket); try { Thread.sleep(10); } catch (InterruptedExceptione) { // TODO Auto-generatedcatch block e.printStackTrace(); } ticket--; } } }1.3.2 解决方案
(1) 使用同步代码块
Synchronized(Object){
需要同步的代码
}
public class SellTicketTask implements Runnable{ private int ticket=100; Object o=new Object(); public void run() { while(ticket>0){ synchronized(o){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"--"+ticket); try { Thread.sleep(10); } catch (InterruptedExceptione) { e.printStackTrace(); } ticket--; } } } } }对于synchronized的锁对象的测试
public class SellTicketTask implements Runnable{ private int ticket=100; Object o=new Object(); Object o2=new Object(); int a=1; public void run() { while(ticket>0){ if(a==1){ synchronized(o){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-1-"+ticket); try { Thread.sleep(10); } catch (InterruptedExceptione) { e.printStackTrace(); } ticket--; a=2; } } } if(a==2){ synchronized(o){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-2-"+ticket); try { Thread.sleep(10); } catch (InterruptedExceptione) { e.printStackTrace(); } ticket--; a=3; } } } if(a==3){ synchronized(o){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-3-"+ticket); try { Thread.sleep(10); } catch (InterruptedExceptione) { e.printStackTrace(); } ticket--; a=1; } } } } } }(2) 使用synchronized修饰非静态方法
如果一个方法内代码全部被synchronized代码块包裹,那么这个方法就可以定义为一个同步方法。格式将sychronized加到这个方法的修饰中。由于同步方法的锁就是当前的对象。所有,如果synchronized代码块和同步方法混合使用,需要将synchronized的对象锁设置为当前对象,也就是this。
public class SellTicketTask implements Runnable{ private int ticket=100; Object o=new Object(); Object o2=new Object(); int a=1; public synchronized void synmethod(inta){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-"+a+"-"+ticket); try { Thread.sleep(10); } catch(InterruptedExceptione) { e.printStackTrace(); } ticket--; if(a==2){ this.a=3; } if(a==3){ this.a=1; } } } public void run() { while(ticket>0){ if(a==1){ synchronized(this){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-1-"+ticket); try { Thread.sleep(10); } catch (InterruptedExceptione) { e.printStackTrace(); } ticket--; a=2; } } } if(a==2){ synmethod(2); } if(a==3){ synmethod(3); } } } }(3) 使用synchronized修饰的静态方法
同步静态方法的格式同同步非静态方法,但是注意引用全局变量的修饰符和类属性使用类调用的问题。另外,静态方法的对象锁不是类的实例对象,因为静态方法的加载先于实例对象。那么,一般,类加载到内存中,最开始是将class文件字节码内容加载到内存,并将这些静态数据转换成方法区中的运行时数据结构,然后在堆中生成一个表示这个类的Class对象。一般,有三种方法得到这个加载类的Class对象。1,Object类的getClass方法;2,类名.class;3,Class类的forName方法。
public class SellTicketTask implements Runnable{ private static int ticket=100; Object o=new Object(); Object o2=new Object(); static int a=1; public static synchronized void stasynmethod(inta){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-"+a+"-"+ticket); try { Thread.sleep(10); } catch(InterruptedExceptione) { e.printStackTrace(); } ticket--; if(a==2){ SellTicketTask.a=3; } if(a==3){ SellTicketTask.a=1; } } } public void run() { while(ticket>0){ if(a==1){ //synchronized(SellTicketTask.class){ try { synchronized(Class.forName("com.edu.thread.SellTicketTask")){ //synchronized(newSellTicketTask().getClass()){ //使用3种方法得到运行类的class对象 if(ticket>0){ System.out.println(Thread.currentThread().getName()+"-1-"+ticket); try { Thread.sleep(10); } catch(InterruptedExceptione) { e.printStackTrace(); } ticket--; a=2; } } } catch (ClassNotFoundExceptione) { e.printStackTrace(); } } if(a==2){ stasynmethod(2); } if(a==3){ stasynmethod(3); } } } }2 利用锁解决懒汉式单例的多线程安全问题
2.1 单例模式原型
public class SingleHungry { private SingleHungrysingleH=new SingleHungry(); private SingleHungry(){} public static SingleHungrygetSingleH(){ return singleH; } } public class SingleLazy { private static SingleLazy singleL; private SingleLazy(){} public static SingleLazy getSingleL(){ if(singleL==null){ singleL=new SingleLazy(); } returnsingleL; } }2.2 人为制造懒汉式的线程安全问题
public class SingleLazy { private static SingleLazy singleL; private SingleLazy(){} public static SingleLazy getSingleL(){ if(singleL==null){ try { Thread.sleep(100); } catch(InterruptedExceptione) { e.printStackTrace(); } singleL=new SingleLazy(); } return singleL; } } public class TestSingle implements Runnable{ public void run() { System.out.println(Thread.currentThread().getName()+"--"+SingleLazy.getSingleL()); } } public static void main(String[] args) throws InterruptedException{ TestSinglets=new TestSingle(); Threadt1=new Thread(ts,"lizi1"); Threadt2=new Thread(ts,"lizi2"); Threadt3=new Thread(ts,"lizi3"); Threadt4=new Thread(ts,"lizi4"); Threadt5=new Thread(ts,"lizi5"); Threadt6=new Thread(ts,"lizi6"); Threadt7=new Thread(ts,"lizi7"); Threadt8=new Thread(ts,"lizi8"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); t7.start(); t8.start(); }2.3 利用同步锁解决线程安全问题
public class SingleLazy { private static SingleLazy singleL; private SingleLazy(){} public static SingleLazy getSingleL(){ //优先判断null,提高效率。 if(singleL==null){ synchronized(SingleLazy.class){ if(singleL==null){ try { Thread.sleep(100); } catch(InterruptedExceptione) { e.printStackTrace(); } singleL=new SingleLazy(); } } } return singleL; } }测试方法同上。
3 deadlock
3.1 什么是死锁
两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无其他作用,它们都将无法推进下去。
产生死锁的四个条件:1,互斥条件;2,请求与保持条件;3,不剥夺条件;4,循环等待条件。
3.2 example
public class DeadLock implements Runnable{ private Objecto1=new Object(); private Objecto2=new Object(); public void run() { while(true){ synchronized(o1){ System.out.println(Thread.currentThread().getName()+"--"+"拿刀"); synchronized(o2){ System.out.println(Thread.currentThread().getName()+"--"+"拿叉"); } } synchronized(o2){ System.out.println(Thread.currentThread().getName()+"--"+"拿叉"); synchronized(o1){ System.out.println(Thread.currentThread().getName()+"--"+"拿刀"); } } } } }