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、死锁

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    

原文地址:https://www.cnblogs.com/xunol/p/3285485.html