Java多线程基础

说到线程,一般就离不开进程的概念。

进程:一个正在执行的程序

线程:进程中负责程序执行的执行单元,一个进程中至少有一个线程

Java创建多线程的方法

1.继承Thread类,重写该类的run()方法。

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
    }
}

Thread类有很多个构造器来创建一个线程(Thread)实例:

Thread(); // 创建一个线程。
Thread(Runnable target); // 创建一个线程,并指定一个目标。
Thread(Runnable target, String name); // 创建一个名为name的目标为target的线程。
Thread(String name); // 创建一个名为name的线程。
Thread(ThreadGroup group, Runnable target); // 创建一个隶属于group线程组,目标为target的线程。

2.实现Runnable接口,重写该接口的run()方法。该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

public class MyRunnable implements Runnable {
    @Override 
    public void run() {
        super.run();
    }
}

下面代码即为创建一个线程对象

public class TestRunnable {
    @Test
    public void testRunnable() {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
    }
}

Thread和Runnable的区别:如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable较Thread的优点:

  • 适合多个相同的程序代码的线程去处理同一个资源
  • 可以避免java中的单继承的限制
  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
  • 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类

3 使用Callable和Future接口创建线程。 具体是创建Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。

public class MyCallable implements Callable<Object> {
    @Override
    public Object call() throws Exception {
        return null;
    }
}

测试类

@Test
public void test() {
    Callable<Integer> callable = new MyCallable();
    FutureTask<Integer> futureTask = new FutureTask<>(callable);  // 使用FutureTask来包装MyCallable对象

    Thread thread = new Thread(futureTask);
    thread.start();

    try {
        Object sum = futureTask.get();            // 取得新创建的新线程中的call()方法返回的结果
        System.out.println("sum = " + sum);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

FutureTask类的定义

public class FutureTask<V> implements RunnableFuture<V> {
    // ...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

FutureTask类同时实现了RunnableFuture接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

线程的状态及转换

  • 新建状态(new): 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态(runnable):当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态(running):如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态(blocked):如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep()join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。但是sleep()不会释放锁。
  • 死亡状态(dead):一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

下图是状态转换图

阻塞状态与线程控制

  1. join():让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行。
  2. sleep():让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,处于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。
  3. 后台线程(Daemon Thread)

后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。

  • 生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡。
  • 设置后台线程:调用Thread对象的setDaemon(true)方法将指定的线程设置为后台线程。
  • 判断线程是否是后台线程:调用thread对象的isDeamon()方法。
  • main线程默认是前台线程,前台线程创建中创建的子线程默认是前台线程,后台线程中创建的线程默认是后台线程。调用setDeamon(true)方法将前台线程设置为后台线程时,需要在start()方法调用之前。前天线程都死亡后,JVM通知后台线程死亡,但从接收指令到作出响应,需要一定的时间。

    1. 改变线程的优先级/setPriority():每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同。main线程默认具有普通优先级。

    设置线程优先级:setPriority(int priorityLevel)。参数priorityLevel范围在1-10之间,常用的有如下三个静态常量值:

    MAX_PRIORITY: 10

    MIN_PRIORITY: 1

    NORM_PRIORITY: 5

    获取线程优先级:getPriority()。

    1. **线程让步:**Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

停止线程

  1. 使用退出标志,使线程正常退出,也就是当run()方法完成后线程终止。
  2. 使用stop()方法强行终止线程,一般不推荐此方法。
  3. 使用interrupt()方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断(isInterrupted()方法)才可以完成线程的停止。

线程同步

  1. 使用synchronized代码块进行同步,如以下使用。
  2. notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
  3. notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。代码块进行同步,如以下使用。

注意点

  • wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行。
  • notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的(任意一个notify()/所有notifyAll())线程对象,但是,此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象。
  • notify()/notifyAll()执行后,如果右面有sleep()方法,则会使当前线程进入到阻塞状态,但是同步对象锁没有释放,依然自己保留,那么一定时候后还是会继续执行此线程,接下来同上面。
  • wait()/notify()/nitifyAll()完成线程间的通信或协作都是基于不同对象锁的,因此,如果是不同的同步对象锁将失去意义,同时,同步对象锁最好是与共享资源对象保持一一对应关系。
  • 当wait线程唤醒后并执行时,是接着上次执行到的wait()方法代码后面继续往下执行的。
public synchronized void method() {} // 在方法上加synchronized关键字
synchronized(this) {
    // 同步代码块。this可以是其他对象
}
  1. 使用Lock进行同步
    • lock():加锁。
    • unlock():释放锁。
    • tryLock():尝试获得锁。

wait()/notify()/notifyAll()线程通信

  1. wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。
  2. notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
  3. notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

注意点

  • wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行。
  • notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的(任意一个notify()/所有notifyAll())线程对象,但是,此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象。
  • notify()/notifyAll()执行后,如果右面有sleep()方法,则会使当前线程进入到阻塞状态,但是同步对象锁没有释放,依然自己保留,那么一定时候后还是会继续执行此线程,接下来同上面。
  • wait()/notify()/nitifyAll()完成线程间的通信或协作都是基于不同对象锁的,因此,如果是不同的同步对象锁将失去意义,同时,同步对象锁最好是与共享资源对象保持一一对应关系。
  • 当wait线程唤醒后并执行时,是接着上次执行到的wait()方法代码后面继续往下执行的。

常见方法

// 静态方法
public static native Thread currentThread(); // 获得正在执行的线程
public static native void yield(); // 线程让步
public static native void sleep(long millis) throws InterruptedException; // 线程休眠
// 对象方法
public synchronized void start(); // 启动线程
public static boolean interrupted(); // 中断线程
public boolean isInterrupted(); // 判断线程是否中断
public final void setPriority(int newPriority); // 设置线程优先级
public final int getPriority(); // 获取线程优先级
public final synchronized void join(); // 等待线程结束
isDaemon(); //  一个线程是否为守护线程。
setDaemon(); // 设置一个线程为守护线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值