美文网首页
线程基础

线程基础

作者: kindol | 来源:发表于2018-07-10 00:28 被阅读0次

实现方式:

  1. 继承Thread类并且重写run()方法(必须)

    调用方式:new一个该已经继承Thread类的类后调用start()方法

    构造器的几种参数:

    • String name

      Thread的名称,默认为Thread-N(N是唯一的数字)

    • Runnable target

      runnable是thread要执行的指令列表。默认情况下,这是thread本身的run()这个method的消息。Thread class本身实现了Runnable接口。

    • Thread group

      默认情况下,thread会分配给予调用constructor的thread一样的thread group

    • Stack size

      每个thread都有一个method运行时保存临时变量的stack,stack大小由平台而定,不建议在可移植程序使用此变量

    Thread中,调用start后的执行流程为:start->start0->run->target.run

    观察其init函数,会发现其会获取当前运行线程为父线程,并使用父线程相关属性继承——daemon、priority、inheritableThreadLocal

  2. 实现Runnable接口并且重写run()方法(必须)

    调用方式:先new一个实现该接口的类a,再new一个Thread类并且将类a作为参数传入,调用start()方法执行产生线程执行

    注意:Runnable不是线程,而是线程运行的代码宿主

  1. 通过 Callable 和 Future 创建线程

    步骤:

    1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call()方法将作为线程执行体,并且有返回值。
    2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable对象(作为FutureTask的参数),FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
    3. 使用FutureTask对象作为 Thread 对象的 target 创建并启动新线程
    4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

线程的状态:

  • 创建(new)状态: 准备好了一个多线程的对象

    线程创建之后,不会立即进入就绪状态(new后的状态为创建状态)。因为线程的运行需要一些条件(比如内存资源,程序计数器、Java栈、本地方法栈等,这些都是线程私有的,需要为其分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。

  • 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度

  • 运行(running)状态: 执行run()方法

  • 阻塞(blocked)状态: 暂时停止执行

    多个原因导致,用户主动让线程等待,或者被同步块给阻塞等事件

  • 终止(dead)状态: 线程销毁,退出run方法


    All.jpg
阻塞分为三种:
  • 等待

    运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法或者中断才能被唤醒

  • 超时等待

    不同于等待,到达一定时间将返回,包含wait(...)、sleep(...)、join(...)。

  • 阻塞

    运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

sleep和wait的区别:

  • sleep是Thread类的静态方法,wait是Object类中定义的方法.
  • Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的, 那么Thread.sleep不会释放锁;调用wait方法会导致线程放弃对象的锁,进入对象的等待池(wait pool)
  • 调用sleep方法,当前线程会暂停执行一段时间,时间到了回到就绪状态。调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU(只有调用对象的notify/notifyAll才能唤醒等待池中的线程进入等锁池(lock pool))
  • wait方法必须在synchronized块中

上下文切换:

实际上就是存储和恢复CPU状态的过程,线程切换需要记录程序计数器、CPU寄存器状态等数据,它使得线程执行能够从中断点恢复执行。

部分方法:

T2.jpg T3.jpg

currentThread():

返回代码段正在被哪个线程调用的信息。

sleep(...):

两个重载版本:

sleep(long millis)     //参数为毫秒
sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

调用sleep方法,如果此线程持有一对象的锁,其他线程也无法访问这个对象;sleep方法是让当前正在运行的线程休眠,也就是currentThread方法返回的对象

yield():

让当前线程交出CPU权限,跟sleep类似,不会释放对象锁,但是yield不能控制交出CPU的时间。另外,yield方法只能让拥有>=优先级的线程有获取CPU执行时间的机会

调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间。

sleep()和yield()的区别

  1. sleep切换其他线程不会考虑优先级,yield只会给相同优先级或更高优先级的线程以运行的机会。
  2. sleep后转入阻塞状态,yield后转入就绪状态
  3. sleep()抛出InterruptedException异常,yield无

对象方法:

  • start()

    启动新的线程

  • run()

    不需要用户调用,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。继承Thread类必须重写run方法

  • getId()

    取得线程的唯一标识

    public static void main(String[] args) {
            Thread t= Thread.currentThread();
            System.out.println(t.getName()+" "+t.getId());
        }
    
  • isAlive()

    判断当前线程是否处于活动状态(run或者ready)

  • join()

    很多情况下,主线程创建并启动线程,若子线程需要进行耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

    public class Thread4 extends Thread{
        public Thread4(String name) {
            super(name);
        }
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(getName() + "  " + i);
            }
        }
        public static void main(String[] args) throws InterruptedException {
            // 启动子进程
            new Thread4("new thread").start();
            for (int i = 0; i < 10; i++) {
                if (i == 5) {
                    Thread4 th = new Thread4("joined thread");
                    th.start();
                    th.join();
                }
                System.out.println(Thread.currentThread().getName() + "  " + i);
            }
        }
    }
    

    结果:

    main  0
    main  1
    main  2
    main  3
    main  4
    new thread  0
    new thread  1
    new thread  2
    new thread  3
    new thread  4
    joined thread  0
    joined thread  1
    joined thread  2
    joined thread  3
    joined thread  4
    main  5
    main  6
    main  7
    main  8
    main  9
    

    可见,main主进程等待joined thread线程先执行完了才结束。

  • getName和setName

    得到或者设置线程名称。

  • getPriority和setPriority

    获取和设置线程优先级。Java中,线程的优先级分为1~10这10个等级。线程优先级是决定线程需要多或者少分配一些CPU资源的线程属性,针对频繁阻塞(休眠或者I/O操作)的线程应该有较高优先级,偏重计算的(需要较多CPU时间或运算)的线程则设置较低优先级

    然而,操作系统也可以不理会Java线程对优先级的设定。

    源码比较简单:

    public final void setPriority(int newPriority) {
            ThreadGroup g;
            checkAccess();
            if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
                throw new IllegalArgumentException();
            }
            if((g = getThreadGroup()) != null) {
                if (newPriority > g.getMaxPriority()) {
                    newPriority = g.getMaxPriority();
                }
                setPriority0(priority = newPriority);
            }
        }
    

    线程优先级特性:

    • 继承性

    A线程启动B线程,则B线程的优先级与A是一样的。

    • 规则性

    高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。

    • 随机性

    优先级较高的线程不一定每一次都先执行完。

    一般地,优先级高的会运行稍快。

  • setDaemon和isDaemon

    设置线程是否成为守护线程和判断线程是否是守护线程,需要在线程启动之前设置。

    守护进程与用户进程的区别:守护线程依赖于创建它的线程,而用户线程则不依赖。好比在main中创建了一个守护进程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。GC就是守护进程。

停止线程:

停止线程是在多线程开发时很重要的技术点。可以使用Thread.stop()方法,但最好不用它,因为该方法是不安全的,已被弃用。有3种方法停止线程:

  1. run方法完成后线程终止

  2. stop

    stop和suspend(停止)及resume(继续)一样,都是作废过期的方法,使用他们可能产生不可预料的结果。suspend和resume方法会导致对公共同步对象的独占和由于暂停导致对数据的不同步。

    该方法将会抛出java.lang.ThreadDeath异常,不需要显示捕捉;使用此方法将会导致线程的清理工作得不到完成,而且会对对象进行解锁,可能导致数据不一致问题

  3. interrupt方法中断线程,但这个不会终止一个正在运行的线程,只是在当前线程打了一个停止的标记。需要加入一个判断才可以完成线程的停止

  • 判断线程是否停止:

    Thread提供了两种方法:

    • this.interrupted():静态方法,测试当前线程是否已经中断,但是,调用此函数将会清除线程的中断状态
    • this.isInterrupted():测试线程是否已经中断,调用此函数不会清除中断状态
  • 真正的线程停止:

    比较常用的方法

    1. 加入判断this.interrupted获取标记,如果当前线程被标记需要停止,抛出异常,在后面捕获异常即可;有一些特殊的情况:

      • 在sleep之后interrupt(sleep会自动要求抛出InterruptedException),在catch代码块用isInterrupted进行判断的时候会发现停止状态值会被自动清除
      • 先interrupt,再sleep,也会进入异常
    2. 使用return停止线程

      不过使用return停止线程可能造成代码污染(多个return),建议还是使用异常,因为catch能对异常信息进行处理,方便控制程序

wait和notify概览

All2.jpg

参考:

http://www.importnew.com/21136.html

相关文章

网友评论

      本文标题:线程基础

      本文链接:https://www.haomeiwen.com/subject/topxpftx.html