一、 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
**线程:**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或者是在各个线程之间切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
**程序:**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
**进程:是程序的一次执行过程,是系统运行程序的基本单位因此进程是动态的。系统运行一个程序,即是一个程序从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源,如cpu时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一个角度来说,进程属于操作系统的范畴,在同一段时间内,可以执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序。
二、线程有哪些基本状态?
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java
并发编程艺术》4.1.4 节)。
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
由上图可以看出:线程创建之后处于NEW(新建)状态,调用start()方法开始运行,线程这时候处于READY(可运行)状态,可运行状态获得了CPU的时间片(timeslice)后就处于RUNNING(运行)状态。
当线程执行wait()方法后,线程进入WAITING()等待状态,进入等待状态后的线程,需要等待其他线程的通知才能够返回到运行状态,而TIME_WAITING(超时等待)状态相当于在等待状态的基础上增加了超时限制,比如,sleep(long millis)方法或 wait(long millis)可以将java方法处于TIMED WAITING 状态。当超时时间达到后,java线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,没有获取到锁的情况下,线程会进入到BLOCKED(阻塞)状态,线程在执行RUNNABLE的run()方法之后会进入到TERMINATED(终止)**状态。
3.如何解决线程安全问题?有几种方式?
当线程A在操作共享数据时,其他进程不能参与进来,直到线程A操作完成时,其他线程才可以开始操作共享数据。这种情况即使线程A出现了阻塞也不能被改变。
使用synchronized同步机制或lock锁方式解决。
有两种方式:1.synchronized同步代码块/同步方法
//同步代码块:
synchronized(/同步监视器/){
// 需要被同步的代码
}
1
2
3
4
2.lock锁
四、synchronized与lock有什么异同?
相同点:解决线程安全问题
不同点:
synchronized机制在执行完相应的同步代码以后,自动地释放同步监视器;
1.synchronized是隐式锁,出了自动域自动释放;Lock是显式锁,lock需要手动启动同步lock()/手动结束同步unlock()/在finally里释放,否则容易出现死锁。
2.Lock只有代码块锁,synchronized有代码快锁和方法锁;
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类
优先使用顺序:Lock–>同步代码块(已经进入了方法体,分配了相应资源)–>同步方法(在方法体外)
关于Lock
从JDK5.0开始,java提供了更强大的线程同步机制–>通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.lock.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
五、创建多线程的几种方式
1.继承Thread类实现多线程
run()为线程类的核心方法,相当于主线程的main方法,是每个线程的入口
a.一个线程调用 两次start()方法将会抛出线程状态异常,也就是的start()只可以被调用一次
b.native生明的方法只有方法名,没有方法体。是本地方法,不是抽象方法,而是调用c语言方法
registerNative()方法包含了所有与线程相关的操作系统方法
c. run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)
2.覆写Runnable()接口实现多线程,而后同样覆写run().推荐此方式
a.覆写Runnable接口实现多线程可以避免单继承局限
b.当子类实现Runnable接口,此时子类和Thread的代理模式(子类负责真是业务的操作,thread负责资源调度与线程创建辅助真实业务。
3.覆写Callable接口实现多线程(JDK1.5)
a.核心方法叫call()方法,有返回值
b.有返回值
4.通过线程池启动多线程
通过Executor 的工具类可以创建三种类型的普通线程池: