1.1、什么是进程
进程是资源分配的基本单位,是程序执行的一个实例。
程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入到进程就绪队列中,
进程调度器选中它的时候就会为它分配CPU执行时间,程序开始得到运行。
1.2、什么是线程
线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。
一个进程可以由多个线程组成,同一个进程下的线程间共享该进程的资源,每个线程有自己的堆栈,和局部变量。
线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。
1.3、两者的区别与优劣
区别:
:线程与资源的分配无关,它属于某一个进程,并与进程内的其他线程共享进程的资源(全局变量、静态变量等数据)。
:每个进程都有自己一套独立的资源,供其内的所有线程共享。
优劣:
:创建一个线程的开销比创建一个进程要小很多,CPU切换一个线程的花费远比进程要小很多。
:线程之间的通信更方便,进程之间的通信需要以通信的方式(IPC)进行
:但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
java多线程状态图:
java多线程的五种状态:
2.1、新建状态(New)
当程序通过new关键字来创建一个线程之后,例如:
Thread thread = new Thread("test");
此时线程处于新建状态,但已经有了相应的内存空间和其他资源 。
2.2、就绪状态(Runnable)
当调用了线程的start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。
2.3、运行状态(Running)
如果处于就绪状态的线程获得了CPU执行权,开始执行run()方法的线程执行体,则该线程处于运行状态,只有处于就绪状态中的线程才有机会进入运行状态。
2.4、阻塞状态(Blocked)
在线程运行的过程中,出于某种原因,线程失去了CPU的执行权而进入了阻塞状态,当被阻塞线程的阻塞解除后就会进入就绪状态再次等待CPU的调度才有机会再进入运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
同步阻塞 : 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
其他阻塞 : 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
2.5、终止状态(Dead)
线程执行完了或因异常退出了run()方法,该线程结束生命周期。
Thread类的中的三种构造方法:
Thread(String name)
Thread(Runnable target)
Thread(Runnable target,String name)
参数:target 称为被创建线程的目标对象。创建目标对象target的类负责实现 Runnable接口,给出该接口中run()方法的方法体。
2.1、继承Thread方式
定义一个线程类,它继承Thread类并重写其中的run()方法,此时初始化这个类的实例是taeget可以为null,用这种方法创建将不能再继承其他类。
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0;i<10;i++){
//获取当前线程名
System.out.println(getName()+":"+i);
}
}
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
2.2、实现Runnable接口方式
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0;i<10;i++){
//获取当前线程名
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
运行截图:
2.3、Callable 方式
public class CallableTest {
public static void main(String[] args) {
CallableDemo cd = new CallableDemo();
// 执行 Callable 方式,需要 FutureTask 实现类的支持
// FutureTask 实现类用于接收运算结果, FutureTask 是 Future 接口的实现类
FutureTask ft = new FutureTask(cd);
Thread t = new Thread(ft);
t.start();
try {
System.out.println(ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class CallableDemo implements Callable{
@Override
public Integer call() {
int i =0;
while (i<100){
i++;
}
return i;
}
}
}
2.4、三者的比较
继承方式
优点可直接this获取当前线程,缺点不能多继承;
采用实现Runnable、Callable接口的方式
优点:1、线程类只是实现了Runnable接口与Callable接口,还可以继承其他类。
2、在这种方式下,多个线程可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,较好地体现了面向对象的思想。
3、Callable方式有返回值
缺点:如果需要访问当前线程,则必须使用Thread.currentThread()方法。
4.1、Thread.currentThread()
返回当前正在执行的线程对象的引用
4.2、isAlive()
测试当前线程是否在活动
4.3、interrupt()
若此时调用线程的interrupt()方法,将线程中断标记设置为true,处于阻塞状态,中断标记将被清除同时产生一个IntertuptedException异常,将异常放在适合的位置将会终止线程。
4.4、isInterrupted()
判断线程中段标记是否为true,默认为false,当调用线程的interrupt()方法时会isInterrupted() 返回true
4.5、sleep(long millis)
使当前执行的线程处于休眠状态(临时停止),此时线程不会释放所拥有的锁。等到指定休眠时间过过后重新进入就绪状态。
4.6、join(long millis)
join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))
join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
源码如下:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
4.7、yield()
yield():和sleep类似,让线程从“运行状态”到“就绪状态”,但是不会阻塞线程,只是让当前线程停一会儿,让同优先级的其他线程获得被执行的机会,但是不保证一定会被执行。yield和sleep一样不会释放同步锁。
由于java的每个对象都有一个内置锁,当用synchronized关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。synchronized可以修饰方法和代码块,但是不能修饰构造器、成员变量等。
public class ThreadTTest {
public static void main(String[] args) {
Runnable r = new ThreadA();
Thread a = new Thread(r, "thread-a");
Thread b = new Thread(r, "thread-b");
a.start();
b.start();
}
static class ThreadA implements Runnable {
@Override
public synchronized void run() {
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
也可以用ReentrantLock(可重入锁)来实现,
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTTest {
public static void main(String[] args) {
Runnable r = new ThreadA();
Thread a = new Thread(r, "thread-a");
Thread b = new Thread(r, "thread-b");
a.start();
b.start();
}
static class ThreadA implements Runnable {
private final ReentrantLock loc = new ReentrantLock();
@Override
public void run() {
loc.lock(); //加锁
try {
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}finally {
loc.unlock(); //解锁
}
}
}
}
Object类提供了wait、notify、notifyAll三个方法:
wait():让当前线程放弃CPU、共享资源,处于等待阻塞状态,直到其他线程调用该同步监视器的notify(),notifyAll()方法来唤醒该线程,进入就绪状态。wait()会释放对当前线程的同步监视器的锁定。
:无时间参数的wait:一直等到,直到其他线程通知唤醒
:带时间参数的wait:等待指定时间后自动唤醒。
notify():唤醒在此同步监视器上等待的单个线程。若监视器上有很多个线程等待,则任意唤醒一个。
notifyAll():唤醒在此同步监视器上等待的所有线程。
Java.lang.Object提供的这三个方法只有在synchronized方法或代码块中才能使用,否则会报出java.lang.IllegalMonitorStateException异常
当线程调用了yield、join、sleep等方法进入阻塞状态,若此时调用线程的interrupt()方法,将线程中断标记设置为true,处于阻塞状态,中断标记将被清除同时产生一个IntertuptedException异常,将异常放在适合的位置将会终止线程。
public class MyThread extends Thread{
@Override
public void run() {
int i =0;
while (!isInterrupted()){
System.out.println(getName()+":"+i++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
Thread.currentThread().sleep(10);
System.out.println(t.isInterrupted());
//设置线程中断标记为true
t.interrupt();
System.out.println(t.isInterrupted());
}
}
Java的线程分为两种:用户线程和守护线程(后台线程),可以用isDaemon()方法来区别:如果返回false则为用户线程,否则为守护线程,JVM的垃圾回收线程就是典型的守护线程(后台线程)
后台线程的特征:如果前台线程都死亡,后台线程会自动死亡。
可以用setDaemon(true)将指定线程设定为后台线程。
public class MyThread extends Thread{
@Override
public void run() {
int i =0;
while (true){
System.out.println(getName()+":"+i++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.setDaemon(true);
t.start();
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}