八股文系列之多线性(一)

267 阅读7分钟

一、多线程相关概念

1、进程与线程

  • 程序:是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

  • 进程:是程序的一次执行过程,他是操作系统进行资源分配与调度的基本单位。每个进程都有拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要进行进程间通信(管道、文件等)

  • 线程:线程是进程的一个执行单元,他是操作系统运算调度的最小单位。一个进程中可以有多个线程,每条线程并行执行不同的任务。每个线程在JVM中都有独立的栈空间。

2、主线程与子线程

  • 主线程:JVM 启动时会创建一个主线程,该主线程负责执行 main 方法 . 主线程就是运行 main 方法的线程。

  • 子线程:Java 中的线程不孤立的,线程之间存在一些联系. 如果在 A 线程中创建了 B 线程, 称 B 线程为 A 线程的子线程, 相应的 A 线程就是 B 线程的父线程。

3、用户线程和守护线程

3.1 概念

  • 用户线程(User Thread):是系统的工作线程,它会完成这个程序需要完成的业务操作

  • 守护线程(Daemon Thread):是一种特殊的线程,主要被用做程序中后台调度以及支持性的工作。

3.2 区别

两者的主要区别在于离开JVM的顺序,守护线程随着JVM一同结束工作,当JVM中的用户线程全部退出,只剩下守护线程,JVM也就退出了。

3.3 创建、使用守护线程

Thread.setDaemon(true) 可以将一个线程设置为守护线程。

守护线程相当于后台管理者,比如:GC垃圾回收等。

4、串行、并行、并发

串行:每次只能执行一个任务,一个任务完成后,另一个任务才能执行;

并行:多个任务同时执行(并发的理想状态)。从硬件角度来说,单核CPU无法实现真正意义上的并行(一段时间上的并行)。

并发:在一段时间内,多个任务交替进行。

二、多线程的优缺点

1、优势

  • 提高吞吐率

  • 提高响应性:Web 服务器会采用一些专门的线 程负责用户的请求处理,缩短了用户的等待时间

  • 充分利用多核处理器资源

2、存在的问题和风险

2.1 线程安全问题

多线程共享数据时,如果没有采取正确的并发访问控制措施,就有可能产生数据一致性问题,如脏堵、数据丢失等问题。

诱因:

  • 存在共享数据;

  • 存在多个线程共同操作这些共享数据;

解决方法:

同一时刻仅有一个线程在操作共享数据,即加锁。

2.2 线程活性问题

  • 死锁:两个或多个线程因竞争共享资源造成的相互等待现象。

  • 活锁:一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有 可能发生活锁。发生活锁的线程无法继续执行,但是他们并没有阻塞,只是一直忙着响应对方无法恢复的工作。就比如:两个人在楼道相遇,两个人相互让路,当时每次都会让到相同的位置,导致两人都无法通过。

  • 饥饿 :一个线程的处理器资源被其它线程抢占而一直无法获得资源,这种状态被称之为饥饿。一般是由优先级高的线程抢占优先级低的线程处理器资源而造成的;

2.3 上下文切换

处理器从执行一个线程切换到另一个线程,有一定的性能开销。

2.4 可靠性

可能会由一个线程导致JVM意外中止,其他的线程也无法执行。

三、线程的创建与启动

1、多线程的实现方式

1.1 继承Thread类

public class ThreadDemo1 extends Thread { 
    public ThreadDemo1(String name) { 
        super(name); 
    } 
    @Override 
    public void run() { 
        super.run(); 
    } 
}

1.2 实现Runnable接口

public class ThreadDemo2 implements Runnable{

   @Override

   public void run() {

      System.out.println(Thread.currentThread().getName());

   }
   public static void main(String[] args) {

      Thread thread = new Thread(new ThreadDemo2());

      thread.start();

   }
}

1.3 实现Callable接口(有返回值)

# 通过FutureTask 可以获取线程执行结果
package cn.interview.base.thread;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

import java.util.concurrent.TimeUnit;

public class ThreadDemo3 implements Callable<Object> {

   @Override

   public Object call() throws Exception {

      int result = 1;

      for (int i = 0; i < 5; i++) {

         result += Math.random();

         TimeUnit.SECONDS.sleep(2);

      }
      return result;
   }

   public static void main(String[] args) throws ExecutionException, InterruptedException {

      ThreadDemo3 threadDemo3 = new ThreadDemo3();

      FutureTask<Object> task = new FutureTask<>(threadDemo3);


      Thread thread = new Thread(task);

      thread.start();

      System.out.println("线程:"+thread.getName()+",启动,可以进行其它业务流程");

      Object result = task.get();

      System.out.println("线程任务执行结果:"+result);

   }

}

 

1.4 定时器 Timer

package cn.interview.base.thread;

import java.util.Timer;
import java.util.TimerTask;

/**
 * 通过定时器实现
 */
public class ThreadDemo4 {

   public static void main(String[] args) {
      Timer timer = new Timer();
      timer.schedule(new TimerTask() {

         @Override
         public void run() {
            System.out.println("定时器线程执行了");
         }
      },0,1000);
      //延迟 0,周期 1s
   }
}

1.5 线程池创建

package cn.interview.base.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ThreadDemo5 {

   public static void main(String[] args) {

      //创建一个具有10个线程的线程池
      ExecutorService threadPool = Executors.newFixedThreadPool(10);
      long threadPoolUseTime = System.currentTimeMillis();

      for (int i = 0; i < 10; i++) {
         threadPool.execute(new Runnable() {
            @Override
            public void run() {
               System.out.println(Thread.currentThread().getName()+"-Run");
            }
         });
      }
      long threadPoolEnd = System.currentTimeMillis();
      System.out.println("多线程用时"+(threadPoolEnd-threadPoolUseTime));
      threadPool.shutdown();
   }
}

2、启动线程

通过start()方法启动线程。

四、线程类的常用方法

1、Thread的方法

1.1 Thread.currentThread()

获取当前线程。

1.2 setName("") getName()

设置 获取线程名称。

1.3 isAlive()

判断线程是否存活。

1.4 Thread.sleep(1000)

使当前线程休眠,单位毫秒

1.5 getId()

获取线程唯一ID

1.6 yield()

放弃CPU资源

1.7 setPriority(1)

设置线程优先级,1-10,数字越大,优先级越高

1.8 Daemon 守护线程

isDaemon()  判断当前线程是否是守护线程。setDaemon(true)  设置当前线程为守护线程。

1.9 interrupt()

设置中断标志 。调用 interrupt() 方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程。

1.10 判断线程是否中断状态

isInterrupted()  判断当前线程是否已经中断,不清除状态

interrupted()    判断当前线程是否已经中断,清除状态

 

1.11 join

把指定线程加入当前线程,例如,将线程a 加入当前线程b,会让当前线程b进入等待状态,知道线程a生命周期结束,在此期间当前线程b处于blocked状态。

问:有三个线程 T1 T2 T3,如何保证顺序执行?

  • 可以使用线程类中的join()方法在一个线程中启动另一个线程,另一个线程执行完成后该线程继续执行。在T2线程中 join T1 线程,T3线程中join T2 线程,然后启动三个线程,T1 T2 T3 三个线程会依次执行。

2、Object类中的方法

2.1 wait()

wait()方法的作用是使当前线程进行等待,它是Object类的通用方法。也可以设置等待时间。此方法用来将当前线程置入到 “预执行队列”中,并在wait 代码处停止执行,直到接到通知(notify()方法或notifyAll()方法)或中断为止。

在调用 wait方法之前线程需要获得该对象的对象级别的锁。即,只能在同步方法或者同步代码块内调用,调用wait后当前线程释放锁。

2.2 notify

唤醒正在此对象的监视器上等待的单个线程。如果有多个线程正在等待该对象,则随机选择其中一个被唤醒()。

也需要在同步方法或者同步代码块内调用。

2.3 notifyAll

notify 是将等待队列中的一个线程移动到同步队列中,而notifyAll是将等待队列中的所有线程全部移动到同步队列中。

五、线程的生命周期

线程生命周期.png

1、获取线程的生命周期

getState()

2、生命周期过程

2.1 New

新建状态,创建了线程对象,但是未调用start()启动线程之前的状态;

2.2 Runnable

可运行状态,它包括READY 和RUNNING 两个状态。

(1) READY

被线程调度器选中之前的状态。

(2) RUNNING

如果线程被线程调度器选中,它就是RUNNING状态,代表现在正在执行。如果调用线程的yeild方法,则会使线程状态转换为READY状态。

2.3 BLOCKED

阻塞状态。线程发起阻塞的 I/O 操作,或者申请由其他线程占用的独占资源,线程会转换为 BLOCKED 阻塞状态. 处于阻塞状态的线程不会占用CPU 资源. 当阻塞I/O 操作执行完,或者线程获得了其申请的资源,线程可以转换为 RUNNABLE.

2.4 WAITING

等待状态:线程执行了 object.wait(), thread.join()方法会把线程转换为 WAITING 等待状态

2.5 TIMED_WAITING

超时等待状态:与 WAITING 状态类似,都是等待状态.区别在于处于该状态的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为 RUNNABLE状态。object.wait(1000);sleep(1000)

2.6 TERMINATED

终止状态,线程运行结束。