android多线程

1.多线程的概念以及2种启动方式
     进程(Process):每个独立执行的程序(正在执行的程序)(不一定有界面)
     线程(Thread) :是一个程序内部的一条执行路径,java虚拟机允许应用程序并发地运行多个执行路径(相当于 进程中的一个子程序 )。线程是CPU调度和分派的基本单位
    线程与进程的区别:
        (1)每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大;
                同一进程内的线程共享代码和数据空间,线程切换的开销小。
        (2)操作系统是以进程为单位的,而进程是以线程为单位的。
        (3)多进程:在操作系统中能同时运行多个任务(程序);
                多线程:在同一应用程序中多条执行路径同时执行。(即多个子程序)是实现并发机制的一种有效手段
        (4)同样作为基本的执行单元,线程是划分得比进程更小的执行单位
        (5)每个进程都有一段专用的内存区域。于此相反,线程却共享内存单元(包括代码和数据),通过共享的内存单元来实现数据交换、实时通信与必要的同步步骤。
        **为什么需要线程???
                因为多个不同任务可同时执行(“多线程的并发”)。---其实是高频度的轮流操作。
                CPU的多线程的并发实际是每个线程轮流获得CPU的控制权(“CPU的时间片”)
                单线程是一个任务一个任务的来执行,第一个任务执行完了才执行第二个任务。
        **java线程是用户级别的(就是与CPU打交道),区别于CPU寄存器线程
        **java  main方法即是主线程,由它启动的若干线程即是子线程,默认地:主线程执行时间优于子线程

            1.耗时的操作使用线程,提高应用程序响应

            2.并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。

            3.多CPU系统中,使用线程提高CPU利用率

            4.改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。


    创建新线程的两种方式
        共有的特点:1.都要重写run()方法
                           2.都不能手动去调用run()方法,都是通过线程类的对象来调用start()方法。直接调用run()方法则仍然是单线程,没有启动多线程
        (1)将类声明为Thread的子类并重写run()方法
             class MyThread extends Thread{
                public void run(){...}
            }
            创建此线程的实例并启动:
           **  MyThread thread1 = new MyThread("线程1");  //  给每个线程设置自己的名字---给继承Thread类的子类写构造方法。如: public MyThread(String threadName) {
                                super(threadName);    //调用父类的构造方法
                            }
                thread1.start();  //启动线程--->自动运行其中的run方法(一般先执行主方法里的打印,再执行OtherClass类中的打印,因为main方法是主线程,OtherClass类是子线程)---主线程和子线程交替打印
            ** 或:使用内部类:new MyThread("线程1").start();

             给主线程取名: Thread.currentThread().setName("");
             获取当前线程的名字 Thread.currentThread().getName()

          (2)定义实现Runnable接口的类
            Runnable接口中只有一个方法public void run();用来定义线程运行体
             class MyRun implements Runnable{
                public void run(){...}
            }
            创建此线程的实例的时候,将这个类的实例作为参数传递到线程实例内部,然后再启动:
             MyRun myRun = new MuRun();
            Thread t1 = new Thread(myRun, "线程1");
            t1.start();
            
Thread t2 = new Thread(myRun, "线程2");
//这里共用的myRunnable对象,所以就会共用RunnThread类中的成员变量(但是, 这种没有同步的,就可能出现数重复,或者多数
            t2.start();
            或:
             Thread thread1 = new Thread(new MyRun());
            thread1.start();

    两种线程创建方式的比较:
        使用Runnable接口:还可以从其他类继承;保持程序风格的一致性;可以为相同程序代码的多个线程提供共享的数据(当不存在共享数据时,这两种方法都可以)
        直接继承Thread类:不能再从其他类继承;编写简单,可以直接操纵线程 (相当于把线程和任务结合在一起,不方便扩充。MyRun类相当于一个任务)
        
    线程小结:
        Java的线程是通过java.lang.Thread类来实现的。
        当程序启动运行时,JVM会产生一个线程(主线程),主方法(main方法)就是在这个线程上运行的。
        可以通过创建Thread的实例来创建新的线程。
             *每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。
             *通过调用Thread类的start()方法来启动一个线程。线程进入Runnable(可运行)状态,它将向线程调度器注册这个线程。
             *调用start()方法并不一定马上会执行这个线程,正如上面所说,它只是进入Runnable 而不是Running。
    注意,不要直接在程序中调用线程的run()方法。(直接调用不是启动线程,而是在本线程中执行方法)


2.多线程的调度以及优先级
    1.线程的状态:(枚举类型的)
        新建(NEW)
       等待就绪(WAITING  |  TIMEWAITING)
       运行(RUNNABLE)
       阻塞(BLOCKED)
       终止(TERMINATED)
    2.促使线程在各种状态间转换的操作---线程的调度,比如Thread.sleep()是线程主动进入BLOCKED
        (已经进入休眠状态的,不能在调度,会出异常)
     3.学习应用几种"线程调度"方法
     (1)join() throws InterruptedException 方法 ---“强行加入线程队列”在当前线程中调用另一个线程的 join()方法,则当前线程转入WAITING状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态。谁把它启动起来,它就是阻塞的谁(阻塞它的宿主所在的线程
            阻塞的几种方法:
                   ** 通过改变线程的优先级来阻塞(效果不好,一般不用) Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
                             设定线程的优先级并不能改变:主线程默认就获得CPU的优先权,那么我们就用更“强硬的阻塞主线程的方式”---join
                   ** Thread对象.join();  ---谁调谁霸道,必须要运行完,别人才能运行,阻塞它的宿主所在的线程(即:在哪个线程中调用的join,就是阻塞的哪个线程)
                   ** Thread对象.join(long 毫秒数);  ---谁掉谁霸道,阻塞别人“毫秒",阻塞它的宿主所在的线程
                   ** Thread对象.join(long 毫秒数,int 纳秒数);--- 等待多少毫秒后,再阻塞多少毫秒

    (2)Thread.sleep(long millis) throws InterruptedException 方法,“线程的自我休眠”,在休眠的时候(BLOCKED状态)。

    (3) Thread.yield()方法---“线程礼让”,让出更多的CPU时间片控制权给其它更有价值的线程,减少本线程的占用时间

             注意:该方法也是静态方法,调用该方法之后,只是使该线程暂停一下,不会阻塞该线程,而是使该线程直接进入就绪状态。则与该线程优先级相同或者高的线程有可能获得执行机会。

           完全有一种可能,该线程暂停之后又立马获得运行机会。


    (4)线程停止:如果线程的run方法中执行的是一个重复执行的循环,可以提供一个标记来控制循环是否执行
                             如果线程因为执行sleep()或wait()而进入了阻塞状态,此时要想停止它,可以使用interrupt()--“线程自我中断”,线程会抛出InterrupException异常
                             如果程序因为输入/输出的等待而阻塞,基本上必须等待输入/输出的动作完成才能离开阻塞状态。
             无法用interrupt()方法来使得线程离开run()方法,要想离开,只能通过引发一个异常。
   
    线程的非静态方法是通过自身调用而改变其它线程(宿主线程)的状态,静态方法是改变当前线程的状态
    线程的优先级:优先级高的线程会获得较多的运行机会。 
            Java 线程的优先级用整数表示,取值范围是 1~10,Thread 类有以下三个静态常量:
                static int  MAX_PRIORITY     线程可以具有的最高优先级,取值为 10。
                    static int  MIN_PRIORITY      线程可以具有的最低优先级,取值为 1。
                    static int  NORM_PRIORITY     分配给线程的默认优先级,取值为 5。
            Thread类的setPriority()getPriority()方法分别用来设置和获取线程的优先级。


3.多线程的状态转换
         线程的状态:要想实现多线程,必须在主线程中创建新的线程对象。任何线程有具有五种状态:创建、就绪、运行、阻塞、终止
                                      
          新建状态(New): 新创建了一个线程对象。
        就绪状态(Runnable): 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。是一个过渡状态。
                                         获取了CPU,执行程序代码时也属于就绪状态。
        运行状态(Running): 执行run()。
        阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
               (分为主动阻塞、被动阻塞(别的线程要先运行,强制它阻塞,让其他线程运行完了才运行))
                阻塞的情况分三种:  同步阻塞:若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。运行的线程在获取对象的同步锁时 。
                                                     等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
                                                     其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
                                                                      当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
        结束状态(Dead) :线程执行完了或者因异常退出了run()方法,该线程结束生命周期。


4.多线程的同步问题--- synchronized
     编程技巧 在方法中尽量少操作成员变量,多使用局部变量
     关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该(加锁的)对象在任一时刻只能由一个线程访问。
     一个方法使用关键字synchronized修饰后,当一个线程A使用这个方法时,其他线程想使用这个方法就必须等待,直到线程A使用完该方法。
    操作的是同一个Runnable对象,所以为了保证共享资源的一致性,就要用同步

      *线程安全--同步(synchronized)---不能同时进行
       线程不考虑安全---异步(asynchronized) 即并发
     互斥锁
        1.明白“对象互斥”的功能---确保同一时刻,只能有一个线程才能访问多个线程锁共享的资源
             
synchronized的地方就是业务
            (1)
 synchronized(互斥锁对象---往往用this){    
                          //  需要同步的代码;(同步代码块---同步的粒度更细
                      }
                       当一个对象正在调用这个的时候,其它对象就在这个外面等待
            (2)synchronized(共享资源类.class){  //共享资源类的反射对象(反射--就是对象到类;实例化--就是类到对象)。只要是这个类的对象,都是同步的
                            //需要同步的代码块
                    }
                    同步类.class类有一种等价的写法:
                    synchronized放在静态方法前面synchronized放在方法的返回值前即可)--不常用

            (3) synchronized放在非静态方法上面( synchronized放在方法的返回值前即可)
                       同步的互斥锁就是该方法所在的对象,这里等价于this

            (4) 定义一把线程专用的"对象锁"--可以没有实际含义,它的作用只是为多线程需要同步的方法"加锁",减少内存的开销
                     //若是非静态---跟this的作用一样
                     private Object res1 = new Object();
                    //若是静态---跟同步类的作用一样
                         private static Object res1 = new Object();

     实现方式:用面向业务的逻辑去编写线程,将线程和业务分开编写---解耦(耦合--如齿轮---紧密结合在一起的)
    
     同步锁对象
         1.使用同步代码块时必须明确指明使用的同步锁。同步也可以使用字节码作为锁。int.class、String.class等均可以作为同步锁;
        2.同步方法也可以用到的同步锁,此时的锁对象是this。
        3.使用同步锁建议使用公共资源作为锁。换句话说就是,这段代码要修改哪个公共资源,就使用那个公共资源作为同步锁


5.多线程间的通信---生产者消费者模型
     生产者生产满了就不能生产了,消费者消费完了就不能消费了,但是生产者在生产的过程中,消费者也可以消费。

    需求:

    1 同一时间内只能有一个生产者生产     

    2 同一时间内只能有一个消费者消费     

    3 生产者生产的同时消费者不能消费     

    4 消费者消费的同时生产者不能生产  

      消费者和生产者只能有一个在工作。

      ----同步方法或者同步代码快 

    5 共享空间空时消费者不能继续消费     

    6 共享空间满时生产者不能继续生产     

    这样就造成了死锁

    wait-notify机制可以有效的解决“线程死锁(资源)”---这两个方法是所有对象(Object)的方法

    注意:

     1.WaitnotifynotifyAll方法的调用必须放在同步方法或同步代码块里面

    2.是调用“同步锁”对象的waitnotifynotifyAll方法而不是线程对象的方法。

                

     线程等待:Object 类中的wait() throws InterruptedException 方法,导致当前的线程等待,直到其 他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法
     线程唤醒:Object 类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。
                     Object类中的notifyAll()方法,唤醒在此对象监视器上等待的所有线程。
    wait()、notify()、notifyAll()这三个方法只能在被同步化(synchronized)的方法或代码块中调用。

                    
                    
    程序例子:
        生产者:
         synchronized (resource) {
            //1.循环检查资源是否已满
            while(resource.isFull()){  //当资源满了
                System.out.print(this.getName()+"说:");  //等价于Thread.currentThread.getName()
                System.out.println(resource.getName()+"已经生产满了,停止生产,等待你来消费");
                resource.wait();   //实际是让生产者等待,也就是Producer的对象等待
                /**
                 * 不能用sleep---因为它会一直等待;
                 * 不能用join,因为它需要另一个线程,而这里没有另一个线程
                 * 用wait因为,直到其他线程调用此对象的 notify() 方法或 notifyAll()就会被唤醒
                 */
            }
            //2.接消费者发来的通知
            resource.setNum(resource.getNum() + 1);
            System.out.println(this.getName()+"正在生产第"+resource.getNum()+"个"+resource.getName());
            //发出通知,让所有消费者可以来消费
            resource.notifyAll();  //让所有的等待resource.wait()的线程唤醒该线程
        }

        消费者:
         synchronized (resource) {
            //1.循环检查资源是否为空
            while(resource.isEmpty()){   //如果资源空了,就要停止消费,通知生产者生产
                System.out.print(this.getName()+"说:");  //当前线程
                System.out.println(resource.getName()+"已经消费完了,停止消费,通知生产者生产");
                resource.wait();
            }
            //2.进行消费,接生产者发来的通知
            resource.setNum(resource.getNum()-1);
            System.out.println(this.getName()+"正在消费,"+resource.getName()+"还剩"+resource.getNum()+"个");
            //发出通知,让左所有生产者中的随机一个可以生产(后面的生产者就会去抢,没抢到就继续等)
            resource.notifyAll();
        }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值