Java多线程机制

182 阅读6分钟

Java多线程机制

线程概念

什么是进程?

进程是一个具有一定独立功能的程序,是系统进行资源分配和调度运行的基本单位;

什么是线程?

线程是程序执行流的最小单元;

什么的多线程并发执行?

多线程分时地占用CPU的时间片的情况称为线程的并发执行(个人理解:现代计算机的处理器性能可以达到每秒执行上亿条指令,所以cpu并不是一个线程执行到底,在当前线程阻塞、等待的情况下可以切换到其他线程继续执行,我们可以理解成CPU一秒的工作时间被分割成100个时间片,在每个时间片里选择等待处理的线程进行处理,即多线程的分时占用时间片)

线程的作用

多线程是程序并发执行的基础,同时使得java面向对象编程凸显优势

线程与进程的区别

  1. 线程是进程内的一个执行单元,进程至少有一个线程,多线程共享进程的地址空间,而进程有自己独立的地址空间
  2. 操作系统以进程为单位分配资源,同一个进程内的线程共享进程的资源;
  3. 线程是处理器调度的基本单位,但进程不是,进程是系统调度的基本单位;

线程创建的方法

java中常用的创建线程的方法有三种,分别是集成Thread类,实现Runnable接口,可以用Callable接口和FutureTask类实现

此处有很多免费课程的视频有讲,但我个人就听了好几个版本了,最后还是翻了下书确认的,大家学习的时候最好注意下资源的正确性。

1.扩展Thread类

创建一个继承于java.lang.Thread类的线程类,并重写该类的run方法,是最简单直接的创建线程的方法。run方法是一个线程的核心,一个运行的线程实际上是该线程的run()方法呗调用。

/*
 * 线程类
 */
public class ExtThread extends Thread{
    public ExtThread(String name){
        super(name);
    }

    @Override
    public void run(){
        //do something
        for(int i=0;i<50;i++){
            System.out.println(this.getName()+":"+i);
        }
    }
}

eg:线程调用类 eg:调用测试类

public class ThreadTest {
    public static void main(String[] args){
        //1.利用thread创建线程测试
        Thread t1 = new ExtThread("A");
        Thread t2 = new ExtThread("B");
        t1.start();
        t2.start();
    }
}

2.实现Runnable接口

由于java不支持多继承,在已有一个父类的情况下不能再对Thread扩展,这种情况下可以通过实现接口Runnable来创建线程类。 eg:线程类

public class ImpRunnable implements Runnable{
    private String name;
    public ImpRunnable(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            if(i%5 == 0){
                try {
                    Thread.sleep(1000);
                    System.err.println(this.name+"线程休眠");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //Thread.yield();
            System.out.println(this.name+":"+i);
        }
    }
}

eg:调用测试类

public class ThreadTest {
    public static void main(String[] args){
        //2.利用Runnable创建线程测试
        ImpRunnable ds1 = new ImpRunnable("A");
        ImpRunnable ds2 = new ImpRunnable("B");
        Thread t1 = new Thread(ds1);
        Thread t2 = new Thread(ds2);
        t1.start();
        t2.start();
    }
}
  • 备注:要启动线程必须调用线程Thread中的方法start()。所以,即使用Runnable接口实现线程,也必须有Thread类的对象,并且该对象的run方法由实现Runnable接口的类的对象提供。

3.用Callable和FutureTask定义线程

第三种方法通常使用于需要返回值的情况 步骤详解

  1. 创建Callable接口的实现类,并实现call方法,该call方法为线程执行体。
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象。
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  4. 调用FutureTask对象的get方法来获得子线程执行结束后的返回值。

eg:线程类

public class ExtFutureTask implements Callable<Integer> {
    private String name;
    public ExtFutureTask(String name){
        this.name = name;
    }

    @Override
    public Integer call() throws Exception {
        int i = 1;
        for(;i<=50;i++){
            if(i%5==0){
                System.err.println(this.name+"重新选择线程");
                Thread.yield();
            }
            System.out.println(this.name+"(Priority:"+Thread.currentThread().getPriority()+")" + ":执行" + i);
        }
        return i;
    }
}

eg:调用测试类

public class ThreadTest {
    public static void main(String[] args){
        //3.利用Callablze创建线程测试
        ExtFutureTask task1 = new ExtFutureTask("A");
        ExtFutureTask task2 = new ExtFutureTask("B");

        FutureTask<Integer> f1 = new FutureTask<>(task1);
        FutureTask<Integer> f2 = new FutureTask<>(task2);

        Thread t1 = new Thread(f1);
        Thread t2 = new Thread(f2);

        t1.start();
        t2.start();

        try {
            System.out.println("Task1 result:"+f1.get());
            System.out.println("Task2 result:"+f2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

线程的状态及转换

线程的状态

线程在生命周期中可能经历5中状态,新建(new)、就绪(Ready or Runnable)、运行(Runnging)、阻塞(Blocked)、死亡(Dead).

  1. 新建状态:新创建了一个线程对象,但该对象不能占用CPU,不能运行。
  2. 就绪状态:线程对象创建后调用了start()方法后,该线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态:线程调度器使某个处于就绪状态的线程获得CPU使用权,执行线程体代码的run方法。
  4. 阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。知道满足某个触发条件又使线程进入就绪状态,才有机会转到运行状态。
  5. 死亡状态:线程执行完了或者因异常突出了run方法,该线程结束声明周期。

线程状态转换

阻塞的四种情况:

  1. 等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待池中。需要等待到notify方法通知才解除阻塞状态。
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中,等待当前持锁线程执行完解锁后才结束阻塞阻塞状态。
  3. IO阻塞:JDK1.4开始,IO分为非阻塞和阻塞两种模式。阻塞IO模式下,线程若从网络流中读取不到制定大小的数据量时,IO就在哪里等待着,只有等到数据IO完成才解除阻塞。
  4. 其他阻塞:sleep,join等方法造成的阻塞,需要对应条件执行完成后才能解除阻塞。

线程调度

概念

Java线程调度是抢占式调度策略,在可运行池中的就绪状态具有不同优先级,优先级高的线程将优先得到比优先级低的线程更多的CPU执行时间,相同则随机选择线程,一个时刻(单核)CPU只有一个线程在运行。抢占的意义在于获得更多的时间而不是独占。

设置线程优先级

通过setPriority(int)方法设置线程优先级,参数可取1-10,数值越大代表优先级越高。

线程常用方法

currentThread

返回对当前正在执行的线程对象的引用

/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
public static native Thread currentThread();

getName

返回该线程的名称

getPriority

返回该线程的优先级

interrupt

使一个阻塞状态的线程中断执行

isAlive

测试线程是否处于活动状态

join

等待该线程终止

setPriority

设置线程优先级

setName

设置线程名称

static void sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠

start

使该线程可运行

static void yield

暂停当前正在执行的线程对象,并执行其他线程

线程同步与锁

synchronized关键字

用synchronized修饰方法或代码块即可实现同步锁。

synchronized关键字修饰的方法是不能继承的,父类方法的synchronized方法在子类中并不自动成为synchronized方法,而是变成了非同步方法,子类可以显示地指定他的某个方法为synchronized方法

volatile关键字

用于多线程同步变量。volatile告诉JVM,它所修饰的变量不使用拷贝,直接访问主内存中的变量。volatile一般情况下不能代替synchronized,因为volatile不能保证操作的原子性,只能保证不同线程之间操作的变量是在同一块内存中。

Lock

Java5提供了同步代码块的另一种机制,基于Lock接口及其实现类。

和synchronized关键字相比,锁机制的好处包括以下几点:

  1. 提供了更多的功能,例如tryLock;
  2. 允许分离读和写操作,允许多个读线程和单一写线程;
  3. 具有更好的性能;