记得九几年小学课本里有一篇华罗庚的文章写的是统筹学,里面的例子大概是这么说的:我们想喝茶,第一种办法,洗刷茶壶,放上茶叶,烧水,等水开了泡茶;第二种办法,烧水,等待水开的时间里洗刷茶壶,放上茶叶,然后水开了泡茶。
生活中的统筹学也就是编程思想中的算法。在此之前我们写的所有程序都可以说是单线程的。因为都是按部就班的干了一样再干另一样。而多线程达到了事半功倍的效果,一个时刻可以进行多个工作。
多线程的另一个理解是多个员工在同时为了一个任务进行工作,而不是单线程中自始至终只有一个人在劳动。
一. 多线程的实现,继承Thread:
Java中的Thread类是线程类,用它可以创建新的线程对象。
//继承自线程类class MyThread extends Thread{ //重写 public void run() { //自定义的新的线程执行的命令 for (int i=0; i<100; i++) { System.out.println("新线程:"+i); } }} class ThreadTest{ public static void main(String[] args) { MyThread mt=new MyThread(); //创建新的线程对象 mt.start(); //开启新线程,并调用run方法 //主线程执行的命令 for (int j=0; j<100; j++) { System.out.println("多线程测试:"+j); } }}
图中展示两个进程并不是一一交替的,这是因为线程在CPU中优先权的问题。线程自己争取CPU资源以优先处理。我的CPU是单核单线程的,系统是32位XP,因此虽然程序是多线程的,但我的电脑并不能实现多线程,而是模拟。当然这个我们可以不考虑,只要知道多线程可以提高效率即可。
再来看看我们喝茶的例子:
class BoilWater extends Thread{ public void run() { for(int i=0; i<60; i++) { if(i!=59) { System.out.println("烧水中..."); } else { System.out.println("水开了!"); } } }} class DrinkTea{ public static void main(String[] args) { BoilWater bw=new BoilWater(); bw.start(); for(int j=0; j<60; j++) { if(bw.isAlive()) //正在烧水 { System.out.println("刷好茶壶,备好茶叶"); } else { System.out.println("一切就绪,沏茶"); break; } } }}
二. 实现Runnable:
喝茶的例子中,两个线程可以互不干扰执行。现实中我们实现一个任务时,多个人可能使用共同的资源,比如售票。票数是一定的,多个售票员卖的都是这些票中的一部分,你卖得多,她就卖的少。
/*避免了单继承的局限性线程代码在Runnable子类的run方法中*/ //1.实现Runnable接口class Ticket implements Runnable{ private int tickets=100; //总票数 //2.重写run方法 public void run() { while (true) { if (tickets>0) { System.out.println(Thread.currentThread().getName()+"sale "+tickets--); }else { System.out.println(Thread.currentThread().getName()+"售票结束"); break; } } }} class RunnableTest{ public static void main(String[] args) { //3.通过Thread类建立线程对象 Ticket tic=new Ticket(); //4.将Runnabel接口的子类对象作为实参传给Thread类构造方法 Thread thr=new Thread(tic); //第1个售票员 //5.调用Thread类start方法开启线程并调用run方法运行代码 thr.start(); new Thread(tic).start(); //第2个售票员 new Thread(tic).start(); //第3个售票员 new Thread(tic).start(); //第4个售票员 }}
每个售票员卖完自己的份额,汇报结果。
三. 线程安全:
有一个情况:票卖的差不多了,买票的来了。买票者到一号这晃了晃,一号售票员看了一眼放票的地方,还有一张,然后准备卖出却还没拿到手的时候买票者到二号那里晃了晃;二号售票员也看了一眼,还有一张,也准备卖出而没有拿到手买票者到三号号那里晃了晃;如此也到四号售票员那里晃了晃。这么一来,四个人都卖出了票,结果票多卖出3张,成了负数。实际上100个号的票,不能有0号票。
当多个线程同时操作一个共享数据时,一个线程没有对数据操作完毕,另一个线程抢占了CPU资源对数据进行操作,导致共享数据的错误。
为了再次演示这种情况,我们让售票员看过余票之后打个哈欠。在Ticket类中修改:
class Ticket implements Runnable{ private int tickets=100; //票数 public void run() { while (true) { if (tickets>0) { try { Thread.sleep(10); //让售票员犯困 } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"sale "+tickets--); }else { System.out.println(Thread.currentThread().getName()+"售票结束"); break; } } }}
1. 同步代码块,
为了防止票被卖成负数的事情发生,我们限制只有每一个售票员的售票行为完成后,才允许另一个售票员卖票行为的开始。Java中通过了一个同步代码块的解决方法,synchronized(); 。synchronized意为同步的。自由是有条件的,同步是在Java同步机制下的同步。将共享数据操作语句用同步代码块包裹,售出负数票的情况就不会再发生。
修改Thicket类:
class Ticket implements Runnable{ private int tickets=100; //票数 Object obj=new Object(); //监工 public void run() { while (true) { //同步代码块,放在离操作共享数据的语句最近的地方 synchronized (obj) { if (tickets>0) { try { Thread.sleep(10); } catch(Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"sale "+tickets--); }else { System.out.println(Thread.currentThread().getName()+"售票结束"); break; } } } }}
非常好,尽管打瞌睡,没有卖出0号票,没有出错。
synchronized ( obj ) {
同步代码块;
}
大多数情况下,obj这个对象被翻译为“锁”,哪个线程进入执行代码块就锁上,别的线程就进不来,避免了对共享数据的错误操作。我将之理解为监工,监视4个售票员,允许一号售票员卖票的时候,监工就把其他3个窗口关上,让二、三、四号喝茶去。允许三号卖票的时候,把三号的售票窗口打开,其他的关上。
synchronized,同步。这是有条件的:必须要有超过一个的线程;必须多个线程使用同一个锁(监工);必须保证同步中只有一个线程。在售票的例子中就是:必须不止一个售票员;所有的售票员都被监工管理;监工只能让一个售票员在一段时间里售票。
加了一个监工的确安全了。但是每个售票员都想多卖票,要不就没提成,所以,一号卖票的时候,二三四号就会和监工吵,都想赢得窗口;二号窗口打开的时候,一三四就会和监工干架。在程序中也是如此,每个线程都想拿到锁,以获取CPU资源。跟监工吵架(拿到锁)这个过程中又消耗了资源,这个是同步的弊端。
2. 同步函数,
同步函数将同步声明定义在了函数上,它也有个默认的监工,就是所在的类 this。
class Ticket implements Runnable{ private int tickets=100; //票数 private boolean over=true; public void run() { while (true) { if (over) { sale(); } else { break; } } } //同步函数 public synchronized void sale () { if (tickets>0) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"sale "+tickets--); }else { System.out.println(Thread.currentThread().getName()+"售票结束"); over=false; } }}
3. 我们让同步代码块在默认窗口售票,让同步函数在新窗口售票,
1)一般情况:
class Ticket implements Runnable{ private int tickets=100; //票数 public boolean onlyWin=true; //仅仅使用默认售票窗口 private boolean fucSyn=true; //防止同步函数中同步线程死循环 public void run() { if (onlyWin) { while (true) { //同步语句块,使用监工this。 synchronized (this) { if (tickets>0) { System.out.println(Thread.currentThread().getName()+" 默认窗口售票 "+tickets--); }else { System.out.println(Thread.currentThread().getName()+"售票结束"); break; } } } }else { while (true) { if (fucSyn) { sale(); } else { break; } } } } //同步函数。监工默认为this,和默认售票窗口的监工是同一个。 public synchronized void sale () { if (tickets>0) { System.out.println(Thread.currentThread().getName()+" 新开窗口售票 "+tickets--); }else { System.out.println(Thread.currentThread().getName()+"售票结束"); fucSyn=false; } }} class RunnableTest{ public static void main(String[] args) { Tickettic=new Ticket(); new Thread(tic).start(); //第1个售票员 new Thread(tic).start(); //第2个售票员 //主线程有最高优先权,以上两条线程开启后处于临时状态,并非立刻执行。 try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();} tic.onlyWin=false; //开启新窗口 new Thread(tic).start(); //第3个售票员 new Thread(tic).start(); //第4个售票员 }}
为什么只有三个?只要票没卖错就成。
2)同步函数为静态的:
当同步函数为静态的,则监工不再是this,而是函数所属的类。所以相应的要把同步代码块的监工设为这个类.class。
四. 单例设计模式:
//懒汉式单例设计模式class Single implements Runnable{ private static Single s = null; public static Single getInstance() { if (s==null) { synchronized(Single.class) { if (s==null) { s = newSingle(); System.out.println("对象已有"); } } } return s; } public void run () { while (true) { if (s==null) { getInstance(); } else { break; } } }} class SingleTest{ public static void main(String[] args) { Single single=new Single(); //single.run(); new Thread(single).start(); new Thread(single).start(); new Thread(single).start(); new Thread(single).start(); }} /*//饿汉式 class Single{ private static final Single s = Single(); public static Single getInstance() { return s; }} */
五. 死锁:
嵌套同步的时候,要么和谐,要么死锁,
class Locks implements Runnable{ private boolean lock; Locks (boolean lock) { this.lock=lock; } public void run() { if (lock) { while (true) //一定可以遇到死锁的情况 { synchronized(MyLock.obj1) { System.out.println("ifMyLock.obj1"); synchronized(MyLock.obj2) { System.out.println("ifMyLock.obj2"); } } } }else { while (true) //一定可以遇到死锁的情况 { synchronized(MyLock.obj2) { System.out.println("elseMyLock.obj2"); synchronized(MyLock.obj1) { System.out.println("elseMyLock.obj1"); } } } } }} class MyLock{ static Object obj1=new Object(); static Object obj2=new Object();} class DeadLockTest{ public static void main(String[] args) { Thread th1=new Thread(newLocks(true)); Thread th2=new Thread(newLocks(false)); th1.start(); th2.start(); }}
光标一直在闪。程序无法继续,线程被锁死。
烦恼忧愁不用追,吃点好的别嫌贵,