记得九几年小学课本里有一篇华罗庚的文章写的是统筹学,里面的例子大概是这么说的:我们想喝茶,第一种办法,洗刷茶壶,放上茶叶,烧水,等水开了泡茶;第二种办法,烧水,等待水开的时间里洗刷茶壶,放上茶叶,然后水开了泡茶。
生活中的统筹学也就是编程思想中的算法。在此之前我们写的所有程序都可以说是单线程的。因为都是按部就班的干了一样再干另一样。而多线程达到了事半功倍的效果,一个时刻可以进行多个工作。
多线程的另一个理解是多个员工在同时为了一个任务进行工作,而不是单线程中自始至终只有一个人在劳动。
一. 多线程的实现,继承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();
}
}
光标一直在闪。程序无法继续,线程被锁死。