Java 多线程

有关的概念

  • 进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能;
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序;
  • 单线程:指在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行;
  • 多线程:指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务;

多线程优缺点

  • 优点

    1. 多线程技术使程序的响应速度更快,因为用户界面可以在进行其他工作的同时一直处于活动状态;
    2. 当前没有进行处理的任务可以将处理器时间让给其他任务;
    3. 占用大量处理时间的任务可以定期将处理器时间让给其他任务;
    4. 可以随时停止任务;
    5. 通过设置各个任务的优先级来优化性能。

  • 缺点

    1. 多线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
    2. 多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
    3. 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
    4. 线程太多会导致控制太复杂,最终可能是使系统Bug频发。

程序运行原理

  1. 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  2. 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。因此,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

主线程

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

当程序的主线程执行时,如果遇到了循环等而导致程序在指定位置停留时间过长,则无法马上执行下面的程序,需要等待循环结束后能够执行;因此需要 Java 中的多线程技术来解决此类问题;Java 中的多线程技术可以实现一个主线程负责执行部分代码,再由另一个线程负责其他代码的执行,最终实现多部分代码同时执行的效果。

Thread类

Thread 是程序中的执行线程。Thread 类用来描述线程,具备线程应该有功能。Java 虚拟机允许应用程序并发地运行多个执行线程。

Java 创建新执行线程有两种方法:
1. 将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法。
2. 声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

创建线程方式1——继承 Thread 类

创建线程的步骤:
1.定义一个类继承 Thread;
2.重写 run 方法;

public class MyThread extends Thread {

    private int ticket = 30;

    public void run() {
        for (int i = 0; i < 30; i ++) {
            if (this.ticket > 0) {
                System.out.println(Thread.currentThread().getName() + this.getName() + "卖票: ticket" + this.ticket--);
            }
        }
    }

}

3.创建子类对象,就是创建线程对象;
4.调用 start 方法,开启线程并让线程执行,同时还会告诉 jvm 去调用run方法;

public class ThreadTest {
    public static void main(String[] args) {
        // 启动3个线程t1,t2,t3;每个线程各卖10张票!
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

5.执行结果:

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

创建线程方式2——实现 Runnable 接口

创建线程的步骤:
1.定义类实现 Runnable 接口;
2.覆盖接口中的 run 方法;

public class MyThread2 implements Runnable {

    private int ticket = 20;

    @Override
    public void run() {
        for (int i = 0; i < 20; i ++) {
            if (this.ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票: ticket" + this.ticket--);
            }
        }
    }
}

3.创建 Thread 类的对象;
4.将 Runnable 接口的子类对象作为参数传递给 Thread 类的构造函数;
5.调用 Thread 类的 start 方法开启线;

public class ThreadTest {
    public static void main(String[] args) {
        MyThread2 mt=new MyThread2();
        // 启动3个线程t1,t2,t3(它们共用一个Runnable对象),这3个线程一共卖10张票!
        Thread t1=new Thread(mt);
        Thread t2=new Thread(mt);
        Thread t3=new Thread(mt);
        t1.start();
        t2.start();
        t3.start();
    }
}

6.执行结果:

创建 Thread 类的对象,只有创建 Thread 类的对象才可以创建线程。线程任务已被封装到 Runnable 接口的 run 方法中,而这个 run 方法所属于 Runnable 接口的子类对象,所以将这个子类对象作为参数传递给 Thread 的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。

继承Thread类和实现Runnable接口区别

  • 继承 Thread 类,线程对象和线程任务耦合在一起。一旦创建 Thread 类的子类对象,既是线程对象,有又有线程任务。
  • 实现 Runnable 接口避免了单继承的局限。实现 runnable 接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable 接口对线程对象和线程任务进行解耦。

多线程的内存

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间,进行方法的压栈和弹栈;
当执行线程的任务结束了,线程自动在栈内存中释放了;当所有的执行线程都结束了,那么进程就结束了。

线程的匿名内部类使用

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
方式1:创建线程对象时,直接重写 Thread 类中的 run 方法:

new Thread() {
    public void run() {
        for (int x = 0; x < 40; x++) {
            System.out.println(Thread.currentThread().getName() + x);
        }
    }
}.start();

方式2:使用匿名内部类的方式实现 Runnable 接口,重新Runnable接口中的 run 方法:

Runnable r = new Runnable() {
    public void run() {
        for (int x = 0; x < 40; x++) {
            System.out.println(Thread.currentThread().getName() + x);
        }
    }
};
new Thread(r).start();