• Java多线程学习总结


     

    1.创建和启动线程的两种传统方式

    Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:

    ◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法; 
    ◆实现Runnalbe接口,重载Runnalbe接口中的run()方法。

    它们都有哪些区别?相比而言,哪一种方法更好呢?

    在Java中,类仅支持单继承,也就是说,当定义一个新的类的时候,它只能扩展一个外部类.这样,如果创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。

    (1)通过继承Thread类的方法实现多线程:
    package toheima;  
    /** 
     * 通过扩张Thread类实现的多线程程序 
     * @author Ch 
     */  
    public class ThreadDemo extends Thread {//通过集成Thread类的方式  
        public ThreadDemo(String name){  
            super(name);//调用父类带参数的构造方法  
        }  
        public void run(){//重写父类run方法  
            for(int i = 0;i<5;i++){     
                for(long k= 0; k <360000000;k++);//用来模拟一个非常耗时的操作的。  
                System.out.println(this.getName()+" :"+i);     
            }     
        }  
        public static void main(String[] args) {  
            Thread t1=new ThreadDemo("熊大");//多态  
            Thread t2=new ThreadDemo("熊二");  
            t1.start();  
            t2.start();  
        }  
    }  

     (2)通过实现Runnable接口来实现:

    package toheima;  
    /** 
     * 实现Runnable接口的类 
     * @author Ch 
     */  
    public class ThreadDemo implements Runnable {//通过集成Thread类的方式  
        private String name;     
            
        public ThreadDemo(String name) {     
            this.name = name;     
        }     
        public void run(){//重写父类run方法  
            for(int i = 0;i<5;i++){     
                for(long k= 0; k <360000000;k++);//用来模拟一个非常耗时的操作的。  
                System.out.println(name+" :"+i);     
            }     
        }  
        public static void main(String[] args) {  
            ThreadDemo td1=new ThreadDemo("熊大");  
            ThreadDemo td2=new ThreadDemo("熊二");  
            Thread t1=new Thread(td1);  
            Thread t2=new Thread(td2);  
            t1.start();  
            t2.start();  
        }  
    }  

     (3)Thread和Runnable的区别:

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

    package toheima;
    /**
     * 多线程共享数据需实现Runnbale接口。
     * @author Ch
     */
    public class Thread1 implements Runnable{
        private int count=15;  
        @Override  
        public void run() {  
              for (int i = 0; i < 5; i++) {  
                  System.out.println(Thread.currentThread().getName() + "运行  count= " + count--);  
                    try {  
                        Thread.sleep((int) Math.random() * 10);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
        }
        public static void main(String[] args) {
            Thread1 my = new Thread1();  
            new Thread(my, "C").start();//同一个mt,但是在Thread中就不可以,如果用同一个实例化对象mt,就会出现异常     
            new Thread(my, "D").start();  
            new Thread(my, "E").start();  
        }
    }

    输出结果:

    C运行 count= 15
    D运行 count= 14
    E运行 count= 13
    C运行 count= 11
    C运行 count= 9
    C运行 count= 8
    C运行 count= 7
    E运行 count= 10
    D运行 count= 12
    E运行 count= 6
    D运行 count= 5
    E运行 count= 4
    D运行 count= 3
    E运行 count= 2
    D运行 count= 1

    总结:

    实现Runnable接口比继承Thread类所具有的优势:

    1):适合多个相同的程序代码的线程去处理同一个资源

    2):可以避免java中的单继承的限制

    3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

    2.关于线程间状态的转换:

    其实线程状态的转换问题已经很显而易见了,这里我就不赘述了,上图:

    1、新建状态(New):新创建了一个线程对象。
    2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
     
    3.线程的调度:
    1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
     
    Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
    static int MAX_PRIORITY
              线程可以具有的最高优先级,取值为10。
    static int MIN_PRIORITY
              线程可以具有的最低优先级,取值为1。
    static int NORM_PRIORITY
              分配给线程的默认优先级,取值为5。
     
    Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
     
    每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
    线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
    JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
     
    2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
     
    3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
     
    4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
     
    5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
     
    6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
     注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

    4.常见线程名词注解:

    sleep(): 强迫一个线程睡眠N毫秒。 
      isAlive(): 判断一个线程是否存活。 
      join(): 等待线程终止。 
      activeCount(): 程序中活跃的线程数。 
      enumerate(): 枚举程序中的线程。 
        currentThread(): 得到当前线程。 
      isDaemon(): 一个线程是否为守护线程。 
      setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
      setName(): 为线程设置一个名称。 
      wait(): 强迫一个线程等待。 
      notify(): 通知一个线程继续运行。 
      setPriority(): 设置一个线程的优先级。

    5.线程同步:

    1、synchronized关键字的作用域有二种: 
    1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 
    2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 

    2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象; 

    3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法; 


    学习过程中,要注意的问题:

    在线程的Thread对象上调用start()方法,而不是run()或者别的方法。 
    在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。 
    在调用start()方法之后:发生了一系列复杂的事情 
    启动新的执行线程(具有新的调用栈); 
    该线程从新状态转移到可运行状态; 
    当该线程获得机会执行时,其目标run()方法将运行。 

    注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。

  • 相关阅读:
    冲刺第一天
    就用户界面和体验评价搜狗输入法
    学习进度条10
    典型用户及用户场景描述
    学习进度条09
    冲刺阶段第八天
    对石家庄铁道大学网站的UI分析
    学习进度条(第八周)
    冲刺阶段第七天
    冲刺阶段第六天
  • 原文地址:https://www.cnblogs.com/altman29/p/4894135.html
Copyright © 2020-2023  润新知