• 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.

  • 相关阅读:
    UVA 10970 Big Chocolate
    HBuilder 安装uviewui2.0
    域名访问配置支持ipv6
    SSIS学习视频(SQL Server 2008)
    碰到MySQL无法启动1067错误问题
    对存储过程进行加密和解密(SQL 2008/SQL 2012)
    脚本文件比较工具WinMerge
    通过SQL绘制杨辉三角
    通用分页存储过程(SQL Server 2005)
    重新组织和重新生成索引sp_RefreshIndex
  • 原文地址:https://www.cnblogs.com/wyb666/p/10356890.html
Copyright © 2020-2023  润新知