Java多线程详解

内容:

1、什么是多线程

2、两种创建线程方式

3、线程的匿名内部类使用

4、线程安全

5、线程状态图

 

 

 

1、什么是多线程

学习多线程之前,我们先要了解几个关于多线程有关的概念。

进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,

进程是处于运行过程中的程序,并且每个进程都具有一定独立功能

线程:线程是进程中的一个执行单元,来完成进程中的某个功能

 

进程实例:

 

线程实例:

 

一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序可以称之为多线程程序

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

 

什么是多线程呢?即就是一个程序中有多个线程在同时执行。

通过下图来区别单线程程序与多线程程序的不同:

单线程程序:

  • 若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。
  • 去网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。

多线程程序:

  • 若有多个任务可以同时执行。
  • 去网吧上网,网吧能够让多个人同时上网。

 

主线程(单线程程序):

回想我们以前学习中写过的代码,当我们在dos命令行中输入java空格类名回车后,启动JVM,并且加载对应的

class文件。虚拟机并会从main方法开始执行我们的程序代码,一直把main方法的代码执行结束。如果在执行过程

遇到循环时间比较长的代码,那么在循环之后的其他代码是不会被马上执行的。如下代码演示:

 1 class Person{
 2     String name;
 3     Person(String name){
 4         this.name = name;
 5     }
 6     void music()    {
 7         for (int i=1;i<=20;i++ )        {
 8             System.out.println(name+"在听第"+i+"首歌");
 9         }
10     }
11     void eat()    {
12         for (int i=1;i<=20;i++ )        {
13             System.out.println(name+"在吃第"+i+"口饭");
14         }
15     }
16 
17 }
18 
19 class MainThreadDemo{
20     public static void main(String[] args)     {
21         Person p = new Person("xxx");
22         p.music();        
23         p.eat();
24         System.out.println("听完歌吃完饭了,该睡觉了zzZZ~~~");
25     }
26 }

若在上述代码中music方法中的循环执行次数很多,这时在p.music();下面的代码是不会马上执行的,并且在dos窗口

会看到不停的输出”xxx在吃第几口饭”,这样的语句。为什么会这样呢?

原因:

jvm启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为

主线程(main线程)。当程序的主线程执行时,如果遇到了循环而导致程序在指定位置停留时间过长,则无法马上

执行下面的程序,需要等待循环结束后能够执行。

那么,能否实现一个主线程负责执行其中一个循环,再由另一个线程负责其他代码的执行,最终实现多部分代码同时执行的效果?

答:当然能够实现同时执行,只要通过Java中的多线程技术来解决该问题。

 

 

2、两种创建线程方式 

(1)创建线程的两种方式

  • 将类声明为 Thread 的子类,子类重写Thread类的run方法。创建对象,开启线程。run方法相当于其他线程的main方法
  • 声明实现Runnable接口的类,实现 run 方法,然后创建实现类对象并传入到某个线程的构造方法中,开启线程

 

(2)Thread类

Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程,Thread类就是我们说的线程类

构造方法:

  • public Thread();  // 创建一个默认名字的线程对象
  • public Thread(String name);  // 创建一个指定名字的线程对象

常用方法:

  • public void start();  // 使该线程开始执行,Java虚拟机调用该线程的run方法
  • public void run();  // 该线程要执行的操作,比如循环100次打印变量的值
  • public static void sleep(long millis);  // 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  • public static Thread currentThread();  // 返回对当前正在执行的线程对象的引用
  • public String getName();  // 返回该线程的名称

实例:

 1 // 1、继承
 2 class Mythread extends Thread {
 3     // 2、重写run方法
 4     public void run() {
 5         // 3、具体任务代码
 6         for (int i = 1; i < 20; i++) {
 7             System.out.println("线程任务中的" + i);
 8         }
 9     }
10 }
11 
12 public class ThreadDemo {
13 
14     public static void main(String[] args) {
15         // 4、创建子类线程对象
16         Mythread mt = new Mythread();
17         // 5、开启线程 JVM自动告诉CPU去执行线程任务代码
18         mt.start();
19         for (int i = 100; i < 120; i++) {
20             System.out.println("main线程的" + i);
21         }
22     }
23 
24 }

 

(3)Runnable接口

Runnable接口用来指定每个线程要执行的任务。包含了一个 run方法,需要由接口实现类重写该方法

然后创建Runnable的实现类对象,传入到某个线程的构造方法中,开启线程 

此时某个线程的构造方法如下:

创建线程的步骤:

  • 定义类实现Runnable接口
  • 覆盖接口中的run方法
  • 创建Thread类的对象
  • 将Runnable接口的子类对象作为参数传递给Thread类的构造函数
  • 调用Thread类的start方法开启线程

实例如下:

// 1、实现Runnable接口
class MyRunnable implements Runnable {
    // 2、重写run方法
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("创建线程的任务" + i);
        }
    }

}

public class RunnableDemo {
    
    public static void main(String[] args) {
        // 3、创建实现类对象
        MyRunnable mr = new MyRunnable();
        // 4、创建Thread对象,并把刚刚的实现类对象作为参数传递
        Thread td = new Thread(mr);
        // 5、开启线程
        td.start();
        for(int i=100; i<120; i++){
            System.out.println("main线程的任务" + i);
        }
    }
}

 

(4)线程的执行原理

线程对象调用run方法和调用start方法区别?

线程对象调用run方法不开启线程,仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行

 

Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢而要继承Thread类呢?

如下:

Thread t1 = new Thread();

t1.start(); 

这样做没有错,开启一个线程,由线程自己去调用run方法,那么这个run方法就在新的线程中运行起来了,但是我们直接创建

Thread对象,调用start方法,该start调用的是Thread类中的run方法,而这个run方法没有做什么事情,更重要的是这个run方法中

并没有定义我们需要让线程执行的代码。java设计师认为他们不知道程序员会用run方法执行什么代码,所有没写run方法

 

(5)两种创建线程方式的比较

程序设计遵循的原则:开闭原则,对修改关闭,对扩展开放,减少线程本身和任务之间的耦合性

从耦合性分析:

第一种方式继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务

第二种方式实现Runnable接口避免了单继承的局限性,同时还对线程对象和线程任务进行解耦。

实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。

实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。

从代码的拓展性分析:

第一种方式是由于继承Thread类,那么子线程不能继承别的类了

第二种方式是由于实现Runnable接口,同时可以继承别的类

第二种方式的拓展性更好

综上所述,开发中强烈使用第二种方法来创建线程

 

 

3、线程的匿名内部类使用

匿名内部类:快速创建一个类的子类对象或一个接口的实现类对象

格式:

1 new 父类(){
2    重写方法 
3 };
4 
5 new 接口{
6    实现方法 
7 };

实例:

 1 public class nimiDemo {
 2 
 3     public static void demo1() {
 4         // 方式1:创建线程对象时,直接重写Thread类中的run方法
 5         new Thread() {
 6             public void run() {
 7                 for (int x = 0; x < 40; x++) {
 8                     System.out.println(Thread.currentThread().getName()
 9                             + "...X...." + x);
10                 }
11             }
12         }.start();
13     }
14 
15     public static void demo2() {
16         // 方式2:使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法
17         Runnable r = new Runnable() {
18             public void run() {
19                 for (int x = 0; x < 40; x++) {
20                     System.out.println(Thread.currentThread().getName()
21                             + "...Y...." + x);
22                 }
23             }
24         };
25         new Thread(r).start();
26 
27     }
28 
29     public static void main(String[] args) {
30 
31     }
32 
33 }

 

 

4、线程安全

(1)什么是线程安全

当有多个线程在同时运行,这些线程同时运行一段代码(即同一个任务,同一个run方法),操作同一个共享数据时,这时候可能就会出现

线程的安全问题,即线程不安全的.

注意:如果是单线程,或者多线程操作的还是不同数据,那么一般是没有问题的

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,

这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

 

(2)Synchronized

java中提供了线程同步机制,它能够解决上述的线程安全问题。

线程同步的方式有两种:

  • 方式1:同步代码块
  • 方式2:同步方法
 1 // 同步代码块: 在代码块声明上 加上synchronized
 2 synchronized (锁对象) {
 3 
 4     可能会产生线程安全问题的代码
 5 
 6 }
 7 // 注:同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
 8 
 9 
10 // 同步方法:在方法声明上加上synchronized
11 public synchronized void method(){
12 
13    // 可能会产生线程安全问题的代码
14 
15 }
16 // 注:同步方法中的锁对象是 this

 

(3)Lock接口

查阅API,查阅Lock接口描述,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作

Lock接口的实现类:ReentrantLock类

Lock接口中的常用方法:

  • public void lock();  // 获取锁
  • public void unlock();  // 释放锁
 1 class X {
 2    private final ReentrantLock lock = new ReentrantLock();
 3      public void run() { 
 4      lock.lock();  // block until condition holds
 5      try {
 6        // ... method body
 7      } finally {
 8        lock.unlock()
 9      }
10    }
11  }

 

 

5、线程状态图

如果我们多次调用线程对象的start方法,那么久会出现一个异常,查阅API关于IllegalThreadStateException这个异常说明信息

发现,这个异常的描述信息为:指示线程没有处于请求操作所要求的适当状态时抛出的异常。这里面说适当的状态,

这是什么意思呢?难道是说线程还有状态吗?

每个线程是有自己状态的,就好比人的一生从出生到死亡一样,线程也有,具体状态可以查看Thread的一个内部枚举Thread.State.

 

posted @ 2019-02-08 22:31  woz333333  阅读(144)  评论(0编辑  收藏  举报