Java上路09-多线程


       记得九几年小学课本里有一篇华罗庚的文章写的是统筹学,里面的例子大概是这么说的:我们想喝茶,第一种办法,洗刷茶壶,放上茶叶,烧水,等水开了泡茶;第二种办法,烧水,等待水开的时间里洗刷茶壶,放上茶叶,然后水开了泡茶。

       生活中的统筹学也就是编程思想中的算法。在此之前我们写的所有程序都可以说是单线程的。因为都是按部就班的干了一样再干另一样。而多线程达到了事半功倍的效果,一个时刻可以进行多个工作。

       多线程的另一个理解是多个员工在同时为了一个任务进行工作,而不是单线程中自始至终只有一个人在劳动。

 

一. 多线程的实现,继承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();
       }
}   
光标一直在闪。程序无法继续,线程被锁死。

 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值