黑马程序员_多线程

------- android培训java培训、期待与您交流! ----------

多线程

一、  多线程的概念。

每个在系统中运行的程序叫做进程,在一个进程中,有多个独立运行的程序片断就叫做多线程。线程是一组指令集合,换句话说就是一条独立运行代码的线路,从这条线路的入口开始执行编码好的指令,最后到达这条执行线路的末尾,结束线程。

多线程在软件开发中是必不可少,在某些情况,单线线无法解决我们的实际问题。如我们知道,进度条用于指示任务的执行状态。它需要两个动作,一个是执行任务,一个是计算进度。如果用单线程来处理问题的话,那么当线程去处理任务时,根本没有时间来处理进度务显示,在执行完了任务全部或部份之后,才有机会来处理进度条显示,这样将导致前端阻塞,无法实现进度务的效果。如果换成了多线程,那么问题就好办多了,当需要进度务时,开启一个线程,去读取数据,然后另一个线程取到这个数据,然后计算进度结果并控制进度条显示,这样就实现了进度条的效果了。

如图:

二、  多线程的原理。

一个程序中,不管有多少个线程,它们都使用CPU进行处理,而CPU在某个时刻,只能处理一个任务,所以从CPU的角度来说,不可能同一时刻同时处理多个任务(单处理器的情况)。但是CPU的处理速度是毫秒级别,CPU可以在多个执行任务的线程之间进行来回切换,因为速度及快,所以我们才能看多线程同步的假象。其实是有时间差的。直到了多处理器时代,同步才得以真正实现。两核的CPU能同时处理两个任务,多核CPU处理多个任务。CPU的核数和线程同步处理成正比。

我曾经有过这样的经历,有一天,我在公司复制视频教程到U盘时,因为视频较多,而且文件又较大,当我复制到最后三个时,因为快下班了,急得没办法,我同时复制最后面的三个文件,开启三个线程。本来在复制的那个文件只有一分种就搞定了,可我又开启了两个线程,这时,在复制的那个文件突然从1分中跳到了56分钟。最后每办法,只能一个个的复制。

为什么会这样呢,这就是线程多了的缘故。为什么呢,不是说使用多线程会提高效力吗,为什么会适得其反呢。这是因为线程数大于计算机CPU同步处理能力的范围之外,导致CPU在某个时刻进行线程的不断切换。本来只有一个任务的变成了两个任务,从而导致复制速度放慢。时间变长也不足为奇。

多线程的原理就是CPU在多个线程间进行调度,来回切换,独立执行指令代码。在多CPU系统里实现真正的同步处理,如果任务线程超出了CPU同步范围的话,就进行任务调度。

三、  多线程解决的问题领域。

1、       设计多线程是解决单线程的局限性,就是单线程无法解决的问题领域。

1)       解决单线程执行某个比较耗时的任务时,用多线程把任务放到后台去执行。而主线程继续执行其他任务。阻止因主线程执行比较耗时的任务而阻塞其他任务的执行。

2)       用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度

3)       当执行某个任务而无须等待其执行完毕时,解决了单线程因正在执行某个任务,但没有等待其结束的必要性。因为这样会使需要及时处理的任务得不到处理,而不及时的任务却得到了处理,影响到了软件实时性。使用了多线程后,可产生一个线程来执行某个任务,然后主线程继续向下执行。

2、       在多处理器的硬件下还可以或可能提高效力。因为多处器真正的实现了线程同步。换句话说就是多处理器可以同时工作,在同一时刻可以处理多个任务。

四、  线程在java中的应用。

java是一门面向对象的计算机编程语言。也为我们提供了对线程的封装。对线程对象的操作,就可以实现多线程。Thread就是线程对象,它封装了一些有用的方法,使用这些方法就可以实现我们的多线程。下面对Thread的进解。

Thread继承了Object并实现了Runnable接口。Runnale描述了多线程的方法run。一个类如果实现了Runnable接口,那么就实现了一个线程的入口,然后调用Thread的对应构造,把实现了Runnable接口的子类传到Thread,然后调用Thread的方法就可以操作线程。其实,实现了Runnable接口的类就是实现了一条任务的执行路径。另外一个类继承了Thread,覆写了Threadrun方法,那么这个类也实现了一务任务的执行路径。

代码如下:

在举例之前,我们先类解释下两个名词。

(1)         CPU的执行资格。

线程持有CPU的执行资格,在等待CPU的执行权。一旦有CPU的执行权,就会启动执行。

(2)         CPU的执行权。

CPU执行资格的线程抢到CPU的执行权,线程在运行中。

class ThreadDemo3 extends Thread

{

    public void run()

    {

             System.out.println(getStr());

    }

    public String getStr()

    {

             return Thread.currentThread().getName()+"student";

    }

}

class ThreadDemo1TestDemo

{

    public static void main(String[] args)

    {

             ThreadDemo3 t1=new ThreadDemo3();

             t1.start();

             System.out.println(Thread.currentThread().getName());

    }

}

上面创建了两个线程。第一个线程为主线程main,别一个线程为Thread-0。主线程启动了线程Thread-0后,Thread-0就有了CPU的执行资格,没有CPU的执行权,在等待CPU的执行权的到来,持有CPU的执行权的main线程继承向下执行,在某个时候,CPU把执行权切到了Thread-0之后,Thread-0开始执行。

上面的例子是接直接继承致 Thread对,虽然可以实现了多线程,但是这样做和一个局限性,我们知道java是单继承的语言。当我们有一个类继承至其他的类,那么我们就不可能再使用继承了,除非使用多重继承,这是一个办法,但是这样做会增加类间的复杂性,可读性,可维护性都会降低,而且还曾加了类间的藕合度。这时我们得使用接口来解决我们的问题。

代码如下:

class ThreadDemo3 implements Runnable

{

    public void run()

    {

             System.out.println(getStr());

    }

    public String getStr()

    {

             return Thread.currentThread().getName()+"student";

    }

}

class ThreadDemo1TestDemo

{

    public static void main(String[] args)

    {

             ThreadDemo3 td=new ThreadDemo3();

             Thread t1=new Thread(td);

             t1.start();

             System.out.println(Thread.currentThread().getName());

    }

}

运行结果:

运行结果和实例一相同。第二种是我们使用移线程经常用到的方式。

五、  线程安全。

我们先来看看一个代码:

class ThreadDemo3 implements Runnable

{

        int ticket=100;

        public void run()

        {

                  while(ticket>0)

                  {

                           try{Thread.sleep(100);}catch(InterruptedException e){}

                           System.out.println(Thread.currentThread().getName()+"卖第"+ticket--+"");

                  }

        }

        public String getStr()

        {

                  return Thread.currentThread().getName()+"student";

        }

}

class ThreadDemo1TestDemo

{

        public static void main(String[] args)

        {

                  ThreadDemo3 td=new ThreadDemo3();

                  Thread t1=new Thread(td);

                  Thread t2=new Thread(td);

                  t1.start();

                  t2.start();

        }

}

从上面的例子可以看出使用多线程是存在线程安全隐患的。

1)       同步代码块,使用同步代码块可以解决线程安全隐患。代码如下:

class ThreadDemo3 implements Runnable

{

    int ticket=100;

    Object obj=new Object();//

    public void run()

    {

              synchronized(obj)

              {

                       while(ticket>0)

                       {

                                try{Thread.sleep(100);}catch(InterruptedException e){}

                                System.out.println(

Thread.currentThread().getName()+"卖第"+ticket--+"");

                       }

              }

    }

    public String getStr()

    {

              return Thread.currentThread().getName()+"student";

    }

}

class ThreadDemo1TestDemo

{

    public static void main(String[] args)

    {

              ThreadDemo3 td=new ThreadDemo3();

              Thread t1=new Thread(td);

              Thread t2=new Thread(td);

              t1.start();

              t2.start();

    }

}

用了同步代码块之后,运行结果正确。

2)       同步函数,使用同步函数块可以解决线程安全隐患。代码如下:

class ThreadDemo3 implements Runnable

{

    int ticket=100;

    public void run()

    {

              getStr();

    }

    public synchronized void getStr()

    {

              while(ticket>0)

              {

                       try{Thread.sleep(10);}catch(InterruptedException e){}

                       System.out.println(

                                Thread.currentThread().getName()+"卖第"+ticket--+"");

              }

    }

}

class ThreadDemo1TestDemo

{

    public static void main(String[] args)

    {

              ThreadDemo3 td=new ThreadDemo3();

              Thread t1=new Thread(td);

              Thread t2=new Thread(td);

              t1.start();

              t2.start();

    }

}

用了同步函块之后,运行结果正确。

3)       线程等待唤醒

为什么要使用线程的等待和唤醒机制呢。因为有这样的需求,如我是出版社,当我发行了一本杂志之后,我要通知我的订阅者,让他们来取杂志,又如某个客户来订杂志,但是他来到出版社,发现杂志已订完了,他只好等下一期杂志了。代码如下:

class Magazine

{

    String  magazineNmae="";

    int Index=0;

    boolean flag=false;

    public synchronized String getMagazine()

    {

              if(!flag)

                       try{this.wait();}catch(InterruptedException e){}

              flag=false;

              this.notify();

              return magazineNmae;

    }

    public synchronized void setMagazine(String name)

    {

              if(flag)

                       try{this.wait();}catch(InterruptedException e){}

              this.magazineNmae=name+Index++;

              flag=true;

              System.out.println(Thread.currentThread().getName()+" 出版了一本"+magazineNmae);

              this.notify();

    }

 

}

class Publishing implements Runnable//出版社

{

    Magazine m=null;

    public Publishing(Magazine m)

    {

              this.m=m;

    }

    public void run()

    {

              while(true)

              {

                       m.setMagazine("生活杂志");

              }

    }

}

class Subscription implements Runnable//订阅

{

    Magazine m=null;

    public Subscription(Magazine m)

    {

              this.m=m;

    }

    public void run()

    {

              while(true)

              {

                       System.out.println(Thread.currentThread().getName()+"客户订阅了一本"+m.getMagazine());

              }

    }

}

class ThreadDemo1TestDemo

{

    public static void main(String[] args)

    {

              Magazine m=new Magazine();

              Publishing p=new Publishing(m);

              Subscription s=new Subscription(m);

              Thread t1=new Thread(p);

              Thread t2=new Thread(s);

              t1.start();

              t2.start();

    }

}

运支结果:

        这只有两务线程,当有多条线程时,比如有三个订阅者时

class ThreadDemo1TestDemo

{

        public static void main(String[] args)

        {

                  Magazine m=new Magazine();

                  Publishing p=new Publishing(m);

                  Subscription s1=new Subscription(m);

                  Subscription s2=new Subscription(m);

                  Subscription s3=new Subscription(m);

                  Thread t1=new Thread(p);

                  Thread t2=new Thread(s1);

                  Thread t3=new Thread(s2);

                  Thread t4=new Thread(s3);

                  t1.start();

                  t2.start();

                  t3.start();

                  t4.start();

        }

}

运行如下:

运行结果可以看出只发行了一2143的生活杂志,但却订阅了三本,这双出现了线程安全问题了。这是怎么会事呢,上述总共有四个线程,t1为出版社,t2-t4为订阅者。当启动这四个线程时,它们都有CPU的执行资格,假如t1抢到CPU的执行权,那T1首先执行。产生编号为零之后,再继续执行,这次t1因为flag为真,所以t1wait()。此时t2抢到执行权,执行取出一本杂志后,又notify(),又是t3抢到了执行权,此时t3不会再判断flag,而直接取值,造成了一本杂志同时取了两次。

     分析上面的线程安全后,我改进为:

                class Magazine

{

        String  magazineNmae="";

        int Index=0;

        boolean flag=false;

        public synchronized String getMagazine()

        {

                  while(!flag)

                           try{this.wait();}catch(InterruptedException e){}

                  flag=false;

                  this.notifyAll();

                  return magazineNmae;

        }

        public synchronized void setMagazine(String name)

        {

                  while(flag)

                           try{this.wait();}catch(InterruptedException e){}

                  this.magazineNmae=name+Index++;

                  flag=true;

                  System.out.println(Thread.currentThread().getName()+" 出版了一本"+magazineNmae);

                  this.notifyAll();

        }

 

}

class Publishing implements Runnable//出版社

{

        Magazine m=null;

        public Publishing(Magazine m)

        {

                  this.m=m;

        }

        public void run()

        {

                  while(true)

                  {

                           m.setMagazine("生活杂志");

                  }

        }

}

class Subscription implements Runnable//订阅

{

        Magazine m=null;

        public Subscription(Magazine m)

        {

                  this.m=m;

        }

        public void run()

        {

                  while(true)

                  {

                           System.out.println(Thread.currentThread().getName()+"客户订阅了一本"+m.getMagazine());

                  }

        }

}

class ThreadDemo1TestDemo

{

        public static void main(String[] args)

        {

                  Magazine m=new Magazine();

                  Publishing p=new Publishing(m);

                  Subscription s1=new Subscription(m);

                  Subscription s2=new Subscription(m);

                  Subscription s3=new Subscription(m);

                  Thread t1=new Thread(p);

                  Thread t2=new Thread(s1);

                  Thread t3=new Thread(s2);

                  Thread t4=new Thread(s3);

                  t1.start();

                  t2.start();

                  t3.start();

                  t4.start();

        }

}

 

运行结果准确。

总结:

(1)         wait()能使当前执行线程处于等待装态,释放执行资格并释放执行权。sleep()也能使当前执行的线程处于休眼状态,释放执行资格,不释放执行权。

(2)         notify()使处于等待状态的线程醒过来,并没说明是哪个要醒过来,而是不确定的,只要处于等待状态的线程都有机会醒过来,且只醒一个。

(3)         notifyAll是所有处于等待状态的线程都会醒过来。

(4)         waitnotifynotifyAll都是在一个锁里有效。

4)       jdk1.5后的锁对象。

使用同步代码块或同步函数,只能有一组监视器,即waitnotifynotifyAll,特别是notifyAll唤醒全部线程,影响了程序的运行性能。jdk1.5后,提供了lock对象。此对象包含在java.util.concurrent.locks包里面。用法如下:

Lock l=new Lock();

l.lock()

try

{

  ………….

}

finally

{

  l.unlock();

}

别外,在一个Lock锁中,还可以有多组监视器。newCondition()方法获取一组新的监视器。

  例:

import java.util.concurrent.locks.*;

class Magazine

{

        String  magazineNmae="";

        int Index=0;

        boolean flag=false;

        Lock l=new ReentrantLock();

        Condition get_Con=l.newCondition();

        Condition set_Con=l.newCondition();

        public String getMagazine()

        {

                  l.lock();

                  try

                  {

                           while(!flag)

                                    try{get_Con.await();}catch(InterruptedException e){}

                           flag=false;

                           set_Con.signal();

                           return magazineNmae;

                  }

                  finally

                  {

                           l.unlock();

                  }

        }

        public void setMagazine(String name)

        {

                  l.lock();

                  try{

                           while(flag)

                                    try{set_Con.await();}catch(InterruptedException e){}

                           this.magazineNmae=name+Index++;

                           flag=true;

                           System.out.println(Thread.currentThread().getName()+" 出版了一本"+magazineNmae);

                           get_Con.signal();

                  }

                  finally

                  {

                           l.unlock();

                  }

        }

 

}

class Publishing implements Runnable//出版社

{

        Magazine m=null;

        public Publishing(Magazine m)

        {

                  this.m=m;

        }

        public void run()

        {

                  while(true)

                  {

                           m.setMagazine("生活杂志");

                  }

        }

}

class Subscription implements Runnable//订阅

{

        Magazine m=null;

        public Subscription(Magazine m)

        {

                  this.m=m;

        }

        public void run()

        {

                 while(true)

                  {

                           System.out.println(Thread.currentThread().getName()+"客户订阅了一本"+m.getMagazine());

                  }

        }

}

class ThreadDemo1TestDemo

{

        public static void main(String[] args)

        {

                  Magazine m=new Magazine();

                  Publishing p=new Publishing(m);

                  Subscription s1=new Subscription(m);

                  Subscription s2=new Subscription(m);

                  Subscription s3=new Subscription(m);

                  Thread t1=new Thread(p);

                  Thread t2=new Thread(s1);

                  Thread t3=new Thread(s2);

                  Thread t4=new Thread(s3);

                  t1.start();

                  t2.start();

                  t3.start();

                  t4.start();

        }

}

运行结果:

synchronizedlock最大的区别在于,lock可以有多组监视器,而synchronized只能有一组监视器。

六、  线程死锁。

当一个线程持有别一个线程的锁,而另一个线程又持有这线程的销时,就发生死销(DeadLock)。

如下:

class DeadLockDemoClient1

{

  

  public static void main(String[] args)

  {

           DeadLockDemo1 d1=new DeadLockDemo1();

           Thread t1=new Thread(d1);

           Thread t2=new Thread(d1);

           t1.start();

           t2.start();

  }

}

class DeadLockDemo1 implements Runnable

{

  Object lock1=new Object();

  Object lock2=new Object();

  int i=0;

  public void run()

  {

           while(true)

           {

                    if((i%2)==0)

                    {

                              synchronized(lock1)

                              {

                                       System.out.println("if......lock1");

                                       synchronized(lock2)

                                       {

                                                System.out.println("if......lock2");

                                       }

                              }

                    }

                    else

                    {

                              synchronized(lock2)

                              {

                                       System.out.println("else......lock2");

                                       synchronized(lock1)

                                       {

                                                System.out.println("else......lock1");

                                       }

                              }

                    }

                    i++;

           }

  }

}

运行上面代码就发生死锁。

另外一种发生死锁就是wait,当所有线程都处于wait,而又没有notify时,就会发生死锁。

七、  线程的其他使用。

(1)         join()等待线程的程中止。比如:

Thread t1=new Thread();

t1.start();

t1.join();

那么主线程main必需等待t1的中止,才继续运行。

(2)          setDaemon()设置守护线程。如果在运行中的都是守护线程时,虚拟机自动结束。

(3)         Interrupt()用于中断线程,清除 sleepwaitjoin,使线程灰复执行。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值