• Java Thread Basic


    一、Java的多线程有三种实现方式。

      1、继承创建.

        a、定义子类,重写run方法

        b、创建Thread子类的实例(即现成对象)

        c、调用start() 方法启动现成

        特征:不可以共享变量。

     1 public class FirstThreadByExtends extends Thread {
     2     private int i;
     3 
     4     public FirstThreadByExtends(){
     5         super();
     6     }
     7     public FirstThreadByExtends(String name){
     8         super(name);
     9     }
    10     public void run() {
    11         for (; i < 100; i++) {
    12             System.out.println(getName() + " " + i);
    13         }
    14     }
    15 
    16     public static void main(String[] args) {
    17         for (int i = 0; i < 100; i++) {
    18             System.out.println("当前线程的名字: " + Thread.currentThread().getName());
    19             if (i == 20) {
    20                 new FirstThreadByExtends("线程" + i).start();
    21             }
    22         }
    23     }
    24 
    25 }
    View Code

        Note: 每次都是new出来的对象,没有共享变量。

      2、实现Runable接口.

        a、定义Runnable接口实现类,重写run()方法

        b、创建Runnable实现类实例,作为Thread实例的Target

        c、调用start() 方法启动现成

        特征:可以共享变量,因为可以用同一个Target创建多个线程。

     1 public class SecondThreadByImplementsRunnable implements Runnable {
     2     private int i;
     3 
     4     public void run() {
     5         for (; i < 10; i++) {
     6             System.out.println(Thread.currentThread().getName() + " " + i);
     7         }
     8     }
     9     public static void main(String[] args) {
    10         for (int i = 0; i < 10; i++) {
    11             System.out.println("当前线程的名字a: " + Thread.currentThread().getName());
    12             if (i == 5) {
    13                 SecondThreadByImplementsRunnable st = new SecondThreadByImplementsRunnable();
    14                 new Thread(st,"新线程1").start();
    15                 new Thread(st,"新线程2").start();
    16             }
    17         }
    18     }
    19 }
    SecondThreadByImplementsRunnable

      3、使用Callable和future创建线程

        a、创建Callable实现类,实现call()方法

        b、创建Callable实现类的实例,用FutureTask实现类来包装

        c、使用FutureTask作为Thread的Target来创建对象

        d、通过调用FT的get()方法来获取线程的返回值

        特征:可以有返回值,可以抛出异常

     1 public class ThirdThreadByImplementsCallable implements Callable<Long> {
     2     public Long call() throws Exception {
     3         int i = 0;
     4         for (; i < 100; i++) {
     5             System.out.println(Thread.currentThread().getName() + " " + i);
     6         }
     7         return System.currentTimeMillis();
     8     }
     9     public static void main(String[] args) throws InterruptedException, ExecutionException {
    10         ThirdThreadByImplementsCallable tt = new ThirdThreadByImplementsCallable();
    11         FutureTask<Long> task = new FutureTask<Long>(tt);
    12         for (int i = 0; i < 100; i++) {
    13             System.out.println("当前线程的名字: " + Thread.currentThread().getName() +"  "+ i);
    14             if (i == 5) {
    15                 new Thread(task,"有返回值的线程1").start();
    16             }
    17             if (i == 6) {
    18                 System.out.println("----------------------test---------------------------------");
    19                 new Thread(task,"有返回值的线程2").start();
    20             }
    21         }
    22         System.out.println("子线程的返回值:" + task.get());
    23     }
    24 }
    View Code

        Note: 第二个线程实例没有跑起来,情况未知,待解决!

    三种方式的对比

      采用实现Ruannable或Callable接口的方式创建:

        a、线程类只是实现了Runnable或Callable接口,还可以继承其他类

        b、多个线程可以共享一个Target对象,适合多个相同线程处理统一份资源,从而将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的思想

        c、劣势是 编程较复杂,如需访问当前现成还要调用 Thread.CurrentThread()方法.

      继承Thread类的方式创建多线程:

        a、优势,编写简单,可直接调用this获得当前线程

        b、劣势,因继承了Thread,无法再继承其它类

    二、线程生命周期

      线程的生命周期包括5个状态:新建(new),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)。

      a、新建和就绪

        新建:当使用了new关键字创建了一个线程,该线程就处于新建状态。

        就绪:当线程调用了start()方法,该线程就处于就绪状态,JVM会为其创建方法调用栈和程序计数器。该状态表示可以运行,准备被JVM线程调度器调度。

        注意:启动线程的方法是调用线程实例的start(),如果直接调用run()方法,系统会把线程对象,当成一个普通对象,run()方法也会被当成普通方法,而不是线程执行体。

     1 package lifecycle;
     2 
     3 public class InvokeRun extends Thread {
     4     private int i;
     5 
     6     public void run() {
     7         for (; i < 100; i++) {
     8             System.out.println(Thread.currentThread().getName() + "    " + i);
     9         }
    10     }
    11 
    12     public static void main(String[] args) {
    13         for (int i = 0; i < 100; i++) {
    14             System.out.println(Thread.currentThread().getName() + "    " + i);
    15             if (i == 20) {
    16                 //直接调用线程对象的Run方法
    17                 //系统会把线程对象当作普通对象,把run方法当成普通方法,
    18                 //所以,一下两行代码不会启动两个线程,而是依次执行两个Run方法
    19                 new InvokeRun().run();
    20                 new InvokeRun().run();
    21             }
    22         }
    23     }
    24 }
    InvokeRun

      b、运行和阻塞

        运行:就绪状态时,获得了CPU,该线程就处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行;但当线程数大于CPU数,也会出现轮换。具体线程调度细节取决于底层平台所采用的策略。

        运行==>阻塞:

              a、线程调用sleep()方法主动放弃所占用的资源

              b、线程调用了一个阻塞式IO,在该方法返回之前,该线程被阻塞

              c、线程试图获得一个同步监视器,但该同步监视器被其它线程所持有。关于同步监视器的知识、后面将有更深入的介绍

              d、线程在等待某个通知(notify)

              e、程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

        阻塞==>运行:

              a、sleep()方法超过了指定的时间

              b、线程调用的阻塞式IO已经返回

              c、线程成功获得了试图获得的同步监视器

              d、线程正在等待某个通知时,其它线程发出了一个通知

              e、处于挂起状态的的线程被调用了resume()方法

      c、线程死亡

        a、run()或call方法执行完成,线程正常结束

        b、线程抛出一个未捕获的Exception或Error

        c、直接调用线程的Stop()方法结束线程,容易死锁,不推荐使用

        注意:不要试图对已经死亡的线程调用start()方法,会抛出不合法线程状态异常

     1 public class StartDead implements Callable<Long> {
     2     private int i;
     3 
     4     public static void main(String[] args) throws InterruptedException, ExecutionException {
     5         StartDead sd = new StartDead();
     6         FutureTask<Long> ft = new FutureTask<Long>(sd);
     7         Thread t1 = new Thread(ft, "马上会死的线程,哇哈哈哈哈");
     8         //t1.setPriority(Thread.MAX_PRIORITY);
     9         t1.start();
    10         //get方法会阻塞main方法
    11         System.out.println(ft.get());
    12         for (int i = 0; i < 300; i++) {
    13             if (i == 20) {
    14                 System.out.println(t1.isAlive() + "Love is forever!");
    15             }
    16         }
    17         if (!t1.isAlive()) {
    18             t1.start();
    19         }
    20     }
    21 
    22     public Long call() throws Exception {
    23         for (; i < 100; i++) {
    24             System.out.println(Thread.currentThread().getName() + "    " + i);
    25         }
    26         return 100L;
    27     }
    28 
    29 }
    StartDead

    三、线程控制

      

      1、join()线程

        Thread提供了一个线程等待另一个线程完成的方法 join(),当在某个程序执行流中调用了其它线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的线程执行完成为止。

        join()方法由使用线程的程序调用,将大问题划分成许多小问题,每一个小问题分配一个线程。当所有小问题都得到处理以后,再调用主线程进行进一步操作。

        join()方法有三种重载形式:

          join(),等待被join的线程执行完成。

          join(long millis),等待被join的线程的时间最长为millis毫秒。如果在millis毫秒之内被join的线程还没有执行结束,则不再等待。

          join(long millis,int nanos),比上面多了个微秒。

     1 public class JoinThread extends Thread {
     2     public JoinThread(String name) {
     3         super(name);
     4     }
     5 
     6     public void run() {
     7         for (int i = 0; i < 100; i++) {
     8             System.out.println(this.getName() + "     " + i);
     9         }
    10     }
    11 
    12     public static void main(String[] args) throws InterruptedException {
    13         new JoinThread("新线程").start();
    14         for (int i = 0; i < 100; i++) {
    15             if (i == 20) {
    16                 JoinThread jt = new JoinThread("被Join的线程");
    17                 jt.start();
    18                 jt.join(0);
    19             }
    20             System.out.println(Thread.currentThread().getName() + "    " + i);
    21         }
    22     }
    23 
    24 }
    JoinThread

      2、后台线程

        所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了,同时会杀死所有后台线程。反过来说,只要有任何非后台线程(用户线程)还在运行,程序就不会终止。后台线程在不执行finally子句的情况下就会终止其run方法。后台线程创建的子线程也是后台线程。

        下面是一个后台线程的示例:

     1 public class Daemon extends Thread {
     2     public Daemon(){
     3         
     4     }
     5     public Daemon(String name){
     6         super(name);
     7     }
     8     
     9     public void run(){
    10         for (int i = 0; i < 1000; i++) {
    11             System.out.println(this.getName() + "     " + i);
    12             try {
    13                 sleep(100);
    14             } catch (InterruptedException e) {
    15                 e.printStackTrace();
    16             }
    17         }
    18     }
    19     
    20     public static void main(String[] args) throws InterruptedException {
    21         Thread t = new Daemon("守护线程");
    22         t.setDaemon(true);
    23         t.start();
    24         for (int i = 0; i < 10; i++) {
    25             sleep(200);
    26             System.out.println(Thread.currentThread().getName() + "     " + i);
    27         }
    28         //Program end here
    29         //and Daemon is end follow it.
    30         System.out.println("啊哦,守护线程应该死了了吧!");
    31     }
    32 }
    Daemon

     

      3、线程睡眠:Sleep()

        让线程暂停,进入阻塞状态。它是Thread的静态方法。

    1 public class SleepThread {
    2     public static void main(String[] args) throws InterruptedException {
    3         for (int i = 0; i < 10; i++) {
    4             System.out.println(Thread.currentThread().getName() + "     "   +   i);
    5             Thread.sleep(300);
    6         }
    7     }
    8 }
    SleepThread

      4、线程让步:yield()

        让线程进入就绪状态,等待CPU重新调度。不过在此期间,只有 相同或更高优先级的进程能被调用,不然的话会继续执行。

      

      5、线程优先级

        范围 1 ~ 10,三个静态常量  MAX_PRIORITY:10, MAX_PRIORITY:1, MAX_PRIORITY:5.

         t.setPriority(MAX_PRIORITY);

     

     

     

     

    四、线程同步

     

      1、线程安全

        银行取钱问题。

          a. 用户输入帐号、密码,系统验证是否通过

          b. 用户输入取款金额

          c. 系统判断账户余额是否大于取款金额

          d. 如果余额大于取款金额,成功;  如果余额小于取款金额,取款失败。

     

        以下是没有同步控制的代码,也许有时候会正确,但只要有一次错误,那整个代码就是错误的。

     

     

     

     

      2、同步代码快

        synchronized (obj) {},  obj就是同步监视器,线程执行之前必须获得同步监视器的锁定。

        流程:加锁==>操作==>释放锁。

     

     1         synchronized (account) {
     2             // If balance > drawAmount, check out!
     3             if (account.getBalance() > drawAmount) {
     4                 System.out.println(this.getName() + "取钱成功!吐出钞票:" + this.drawAmount);
     5                 try {
     6                     Thread.sleep(1);
     7                 } catch (Exception e) {
     8                     e.printStackTrace();
     9                 }
    10                 account.setBalance(account.getBalance() - this.drawAmount);
    11                 System.out.println("	余额为:" + account.getBalance());
    12             } else {
    13                 System.out.println(getName() + "余额不足,取款失败!");
    14             }
    15         }
    View Code

     

      3、同步方法

        用同步方法可以非常方便的实现线程安全类。任意线程都可以安全访问。但无法显示指定同步监视器,同步监视器就是本身。

     

        代码如下:

     

     1      public synchronized void draw(double drawAmount) {
     2      // If balance > drawAmount, check out!
     3      if (this.getBalance() > drawAmount) {
     4      System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" +
     5      drawAmount);
     6      try {
     7      Thread.sleep(1);
     8      } catch (Exception e) {
     9      e.printStackTrace();
    10      }
    11      this.setBalance(this.getBalance() - drawAmount);
    12      System.out.println("	余额为:" + this.getBalance());
    13      } else {
    14      System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!");
    15      }
    16      }
    View Code

      4、释放同步监视器的锁定

        a.同步方法、同步代码快执行结束,当前线程释放同步监视器。

        b.break、reutrn终止了该代码块,该方法的继续执行,当前线程会释放同步监视器。

        c. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法的异常结束时,当前线程将会释放同步监视器。

        d. 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。

     

        以下不会释放:

        a.sleep().yield();

        b.suspend(). resume();  容易弄成死锁,不推荐

     

     

     

      5、同步锁

        lock.lock(),  lock.unlock();

     

     1 lock.lock();
     2         try {
     3             // If balance > drawAmount, check out!
     4             if (this.getBalance() > drawAmount) {
     5                 System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount);
     6                 try {
     7                     Thread.sleep(1);
     8                 } catch (Exception e) {
     9                     e.printStackTrace();
    10                 }
    11                 this.setBalance(this.getBalance() - drawAmount);
    12                 System.out.println("	余额为:" + this.getBalance());
    13             } else {
    14                 System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!");
    15             }
    16         } finally {
    17             lock.unlock();
    18         }
    View Code

     

     

      6、死锁

        资源的相互依赖,你把我要用的给锁了,我把你要用的给锁了,我们各自拿着一块等待对方,谁都不肯释放。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

        

  • 相关阅读:
    前言
    实用逆袭课【时间管理、记忆训练、工作效率、人际社交】
    读书确实是一辈子的事
    求职宝典(笔记不详细,但你自己看完消化了真的受用)
    重新认识【时间、金钱、自我、人际关系】
    即兴演讲不是即兴
    大数据和AI的未来畅想
    女性30的思考1
    第四课 人际关系
    第三课 干好工作
  • 原文地址:https://www.cnblogs.com/xunol/p/3285485.html
Copyright © 2020-2023  润新知