同步代码块:
/*通过分析,发现,打印出0,-1,-2等错票多线程的运行出现了安全问题.问题原因: 当多条语句在操作多个线程的共享数据时,其中一个线程对多条语句只 执行了一部分,还没有执行完,另一个线程参与进来执行.导致共享数据 的错误.解决办法: 对多条操作共享数据的语句,在同一时间间隔内只让其中一个线程都执行完,在执行过程中,其它 线程不可以参与执行.Java对于多线程的安全问题提供了专业的解决方式就是同步代码块:synchronized(对象)//该锁或叫监视器{ 同步代码块}对象如同锁,持有锁的线程可以在同步中执行没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有锁同步前提:1.必须要有两个或者两个以上的线程2.多个线程必须使用同一把锁(使用的不同的锁不叫同步) 必须保证同步中只能有一个线程在运行好处:解决了多线程的安全问题弊端:多个线程每个线程都需要判断锁,较为消耗资源.(开门上的锁)*/class Ticket implements Runnable{ private int ticket=3000; Object obj=new Object(); public void run()//不能抛出interruptedExcetption,因为Runnable中的run方法没有抛出任何异常 { while(true) { synchronized(obj) { if(ticket>0) { try{Thread.sleep(10);}catch(Exception e){ }//模拟出错,让线程休眠(暂停)10秒,让cpu执行其它线程,未加同步代码块前 System.out.println(Thread.currentThread().getName()+" "+ticket--); //运行结果可能是全是一个线程在执行 //这是因为其它线程还没有抢到cpu,当抢到时,ticket<=0 //可以通过把ticket赋值大一点,让其它线程有执行到的机会 } } } }}class TicketDemo2{ public static void main(String[] args) { Ticket t=new Ticket(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); }}/*分析:(出现0,-1,-2)while(true) if(ticket>0) { //可能出现的一种情况(安全隐患),当ticket=1时 //Thread-0 执行到该位置(0线程变成就绪状态)->cpu切换到Thread-1执行到该位置->Thread-2同理->Thread-3同理 //->cpu又切换到Thread-0(10ms时间已到),继续向下执行->打印出1 //->切换到Thread-1->0 //->切换到Thread-2->-1 //->切换到Thread-3->-2 try{Thread.sleep(10);}catch(Exception e){ }//模拟出错,让线程休眠(暂停)10ms,让cpu执行其它线程 System.out.println(Thread.currentThread().getName()+" "+ticket--); }*//* //在加上同步代码块后//以下为可能出现的一种情况:假设ticket已经为1 Thread-0 Thead-1 Thread-2 Thread-3 //③此时cpu切换到2线程,由于对象标志位为0,2线程无法执行 如果cpu切换到1,3线程,依然无法执行同步代码块 synchronized(obj) { //①当cpu切换到0线程执行,判断对象标志位,为1,可以进来执行 //②0线程进来以后,把对象标志位置为0 //④cpu切换到0线程,ticket>0 if(ticket>0) { //⑤执行到sleep方法,0线程暂停10ms,cpu去执行其他线程->其它线程依然无法执行同步代码块 try{Thread.sleep(10);}catch(Exception e){ } //⑥10ms时间到时,当cpu再次切换到0线程时,打印1,ticket->0 System.out.println(Thread.currentThread().getName()+" "+ticket--); } } //⑦0线程执行完同步代码块对象标志位置1 *//*经典同步问题: 火车上 上厕所*/
银行存钱小例子:
/*需求:银行有一个金库.有两个储户分别存300元,每次存100,存3次.思路:两个储户->两个线程一个金库->对同一个金库进行操作目的:该程序是否有安全问题?找问题:1.首先 明确哪些代码是多线程运行代码2.其次 明确共享数据3.再次 明确多线程运行代码中哪些语句是操作共享数据的*/class Bank{ private int sum=0; Object obj=new Object(); public void add(int n)//这个位置可以抛出异常,注意Bank { //没有继承任何父类(当然Object除外) synchronized(obj)//这里犯得一个错误,采用匿名对象(new Object())->每个线程用的不是一把锁 { sum += n; try{Thread.sleep(10);}catch(Exception e){}//依然模拟出错 System.out.println(Thread.currentThread().getName()+" sum="+sum); } }}class Depositor implements Runnable{ //储户里面有存钱动作,被多个线程执行 private Bank b=new Bank(); public void run()//此位置不可抛出异常,因为Runnable中的run方法没有抛出任何异常 {//也可以把同步代码块加在这里//但是出现一个问题:张三必须把所有钱存完,李四才能去存 for(int i=0;i<3;++i) { //我也可以把同步代码块加在这里,问题是如果我在add中调用 //一些其它方法,或定义一些其它变量,是没有必要同步的 b.add(100); } }}class BankDemo{ public static void main(String[] args) { Depositor d=new Depositor(); new Thread(d).start(); new Thread(d).start(); }}
以上运行结果有点巧合,也可能出现线程交替(我的是双核cpu)
稍微改进一下:
/*需求:银行有一个金库.有两个储户分别存300元,每次存100,存3次.*/class Bank{ private int sumMoney=0; public void add(int storeMoney){ synchronized(Bank.class){ sumMoney+=storeMoney; System.out.println(Thread.currentThread().getName()+"..."+sumMoney); } }}class StoreUser{ private int storeMoney; public StoreUser(int storeMoney){ //用户存入多少钱 this.storeMoney=storeMoney; } public void storeMoney(Bank bank){ //用户拿着钱,那么由用户发出存钱动作,存入哪个银行? bank.add(storeMoney); } public int getMoney(){ return storeMoney; } public void setMoney(int storeMoney){ this.storeMoney=storeMoney; }}class MainClass{ public static void main(String[] args){ final Bank bank=new Bank(); final StoreUser[] su={ new StoreUser(100),new StoreUser(100)}; for( int i=0;i
同步函数:
/*同步函数用的是哪一个锁呢? 函数需要被对象调用.那么函数都有一个所属对象引用.就是this所以同步函数使用的锁是this通过该程序验证. 使用两个线程来买票 一个线程在同步代码块中. 一个线程在同步函数中. 都在执行卖票动作.*/class Ticket implements Runnable{ private int tick=100; Object obj=new Object(); boolean flag=true; /* public void run() { while(tick>0)//卖完1000张"不卖了" synchronized(obj) { if (tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+" "+tick--); } } } */ //方法二:利用同步函数加锁 public void run() { if(flag) { while(tick>0) { synchronized(this) { if (tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+" --code()-- "+tick--); } } } } else while(tick>0) show(); } private synchronized void show() { if (tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+" --show()-- "+tick--); } }}class ThisLockDemo{ public static void main(String[] args) { Ticket t=new Ticket(); new Thread(t).start(); try{Thread.sleep(10);}catch(Exception e){} t.flag=false; new Thread(t).start(); //以上运行结果极有可能都为show()(当不使主线程暂停) //这是因为主线程代码开启0线程后->0线程处于就绪状态,cpu不一定切换到 //该线程执行run()->cpu接着执行主线程,此时flag已经为false //->cpu切换到0线程执行else内容 //->cpu切换到主线程开启1线程->依然执行else内容 //这时加上sleep让主线程短暂暂停,让其他线程有执行机会 }}/*以上打印结果中有0号票原因:两个线程用的不是一把锁(obj和this) 没有实现同步这时,把线程①的锁换成this此时消除0号票验证:同步函数的锁为this,并且this指向new Ticket()*/
同步函数被静态修饰:
/*如果同步函数被静态修饰后,使用的锁是什么?通过验证,不是this.因为静态方法中不可以定义this静态进内存时,没有本类对象,但是一定有该类对应的 字节码文件对象(类名.class)该对象的类型(所述的类)是Class(Class 类的实例表示正在运行的 Java 应用程序中的类和接口)静态的同步方法,使用的锁是该方法所在类的字节码文件对象.类名.class*/class Ticket implements Runnable{ private static int tick=100; boolean flag=true; Class c=Ticket.class; public void run() { if(flag) { while(tick>0) { synchronized(Ticket.class)//或者用c { if (tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"--code()-- "+tick--); } } } } else while(tick>0) show(); } private static synchronized void show() { if (tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+" --show()-- "+tick--); } }}class StaticLockDemo{ public static void main(String[] args) { Ticket t=new Ticket(); new Thread(t).start(); try{Thread.sleep(10);}catch(Exception e){} t.flag=false; new Thread(t).start(); }}
又见单例设计模式:
/*单例设计模式*///饿汉式/*class Single{ private Single(){} private static final Single s=new Single();//加上final更严谨,不能改变s的引用 public static Single getInstance() { return s; }}*///懒汉式class Single{ private Single(){} private static Single s=null; /* public static synchronized Single getInstance() { if(s==null) //A执行到该位置,CPU切换B,B执行到该位置 //cpu又切换到A执行,创建对到象 //切换到B,又创建对象,因此使用同步函数 s=new Single(); return s; } */ //使用同步函数,每个线程都需要判断对象标志位,效率很低 public static Single getInstance() { //⑥当C线程执行,s已不为null,返回 if(s==null) { //②CPU切换到B,s依然为null synchronized(Single.class)//该类所属的字节码文件 { //⑤CPU切换到B,s不为null,返回 if(s==null) //①A线程执行到该位置 s=new Single(); //③A创建完对象 } //④对象标志位置1 } return s; } //利用同步代码块,多加一个判断,减少线程判断锁的次数 //相对提高了效率 }
死锁:
/*死锁:通俗点说:两个人一个人一根筷子,两人相互索要,谁也不给谁->都别想吃 同步中嵌套同步*/class Ticket implements Runnable{ private static int tick=1000; boolean flag=true; Object obj=new Object(); public void run() { if(flag) { while(tick>0) { synchronized(obj) { //(可能出现)0线程执行到此持有obj锁,要想执行show需要this锁 show(); } } } else while(tick>0) show(); } private synchronized void show() { //1线程执行到此,持有this锁,需要obj锁继续执行 synchronized(obj) { if (tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+" --code()-- "+tick--); } } }}class DeadLockDemo{ public static void main(String[] args) { Ticket t=new Ticket(); new Thread(t).start(); try{Thread.sleep(10);}catch(Exception e){} t.flag=false; new Thread(t).start(); }}/*可能不出现死锁:当CPU把一个线程执行完,再去执行另一个线程(把ticket赋值大点使其发生死锁)*/
死锁小程序:
class Test implements Runnable{ private boolean flag; Test(boolean flag) { this.flag=flag; } public void run() { if(flag) { synchronized(MyLock.locka) { System.out.println("if locka"); synchronized(MyLock.lockb) { System.out.println("if lockb"); } } } else { synchronized(MyLock.lockb) { System.out.println("else lockb"); synchronized(MyLock.locka) { System.out.println("else locka"); } } } }}//把锁放在单独类里面class MyLock{ static MyLock locka=new MyLock(); static MyLock lockb=new MyLock();}class DeadLockDemo2{ public static void main(String[] args) { new Thread(new Test(true)).start(); new Thread(new Test(false)).start(); }}
鉴于以上我在想,能否略有改动解决死锁,可以让0线程暂停一会,让cpu执行1线程.在if(flag)下加上try{Thread.sleep(10);}catch(Exception e){},但是这只是减小死锁发生概率.