进程是程序执行的一个实例
。进程的特点,每一个进程都有自己的独立的一块内存空间、一组资源系统,其内部数据和状态都是完全独立的
。
进程的优点是提高CPU运行效率,在同一时间内执行多个程序,即并发执行。但是从严格上讲,也不是绝对的同一时刻执行多个程序,只不过CPU在执行时通过时间片等调度算法不同进程高速切换。
线程是进程的一个实体,是CPU调度和分派的基本单位
。它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
同类的多个线程共享一块内存空间和一组系统资源
,线程本身的数据通常只有CPU的寄存器数据,以及一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程
。
有以下对比与场景:
等待的操作
,比如网络操作、文件 IO 等,可以利用多线程充分使用处理器资源,而不会阻塞程序中其他任务的执行可分解的大任务
,比如耗时较长的计算任务,可以利用多线程来共同完成任务,缩短运算时间后台运行的任务
,比如一些监测任务、定时任务,可以利用多线程来完成并发:多个事件在同一时间段内一起执行
并行:多个事件在同一时刻同时执行
多线程能不能并行,取决于是否有多cpu分配同时运行。所以系统中很多都是多任务一起执行,但是不一定是并行。
在Java程序中,JVM负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。
调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。
优点:简单、实例化即可使用
缺点:扩展性差,java 中是单继承
// 通过继承Thread类实现自定义线程类
public class MyThread extends Thread {
// 线程体
@Override
public void run() {
System.out.println("Hello, I am the defined thread created by extends Thread");
}
public static void main(String[] args){
// 实例化自定义线程类实例
Thread thread = new MyThread();
// 调用start()实例方法启动线程
thread.start();
}
}
这里运用了静态代理模式:
Thread类和自定义线程类都实现了Runnable接口
Thread类是代理Proxy,自定义线程类是被代理类
通过调用Thread的start方法,实际上调用了自定义线程类的start方法(当然除此之外还有其他的代码)
扩展性好
、可以继承实现其他功能相对繁琐一点
public class MyRunnable implements Runnable {
// 线程体
@Override
public void run() {
System.out.println("Hello, I am the defined thread created by implements Runnable");
}
public static void main(String[] args){
// 线程的执行目标对象
MyRunnable myRunnable = new MyRunnable();
// 实际的线程对象
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
}
}
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable {
@Override
public String call() throws Exception {
return "Hello, I am the defined thread created by implements Callable";
}
public static void main(String[] args){
// 线程执行目标
MyCallable myCallable = new MyCallable();
// 包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口
FutureTask futureTask = new FutureTask<>(myCallable);
// 传入线程执行目标,实例化线程对象
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
String result = null;
try {
// 获取线程执行结果
result = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(result);
}
}
提供返回值
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
在Java中,运行态包括就绪态 和 运行态。
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。
所有就绪态的线程存放在就绪队列中。
获得CPU执行权,正在执行的线程。
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
阻塞态专指请求排它锁失败时进入的状态。塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
进入等待态的线程会释放CPU执行权,并释放资源(如:锁),它们要等待被其他线程显式地唤醒。
当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;
进入该状态后释放CPU执行权 和 占有的资源。
与等待态的区别:无需等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。
线程执行结束后的状态。
1.start():启动线程并执行相应的run()方法
2.run():子线程要执行的代码放入run()方法中
3.currentThread():静态的,调取当前的线程
4.getName():获取此线程的名字
5.setName():设置此线程的名字
6.yield():调用此方法的线程释放当前CPU的执行权
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法
7.join():在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕,
当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
低优先级的线程也可以获得执行
8.isAlive():判断当前线程是否还存活
9.sleep(long l):显式的让当前线程睡眠l毫秒
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
10.线程通信:wait() notify() notifyAll()
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
这三个使用较多:
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
isAlive活着的定义是就绪、运行、阻塞状态
线程是有优先级的,优先级高的获得Cpu执行时间长,并不代表优先级低的就得不到执行
sleep时持有的锁不会自动释放,sleep时可能会抛出InterruptedException。
Thread.sleep(long millis)
一定是当前线程调用此方法,当前线程进入TIME_WAIT状态,但不释放对象锁,millis后线程自动苏醒进入READY状态。作用:给其它线程执行机会的最佳方式。
t.join()/t.join(long millis)
当前线程里调用线程1的join方法,当前线程进入WAIT状态,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
join方法的作用是将分出来的线程合并回去,等待分出来的线程执行完毕后继续执行原有线程。类似于方法调用。(相当于调用thead.run())
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
interrupt一个其他线程t时
如何停止线程?
但是注意!
如果线程中有阻塞操作,在阻塞时是无法去检测中断标志位或自定义标志位的,只能使用1)的interrupt方法才能中断线程,并且在线程停止前关闭引起阻塞的资源(比如Socket)。
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
等待方(消费者)和通知方(生产者)
等待方:
synchronized(obj){
while(条件不满足){
obj.wait();
}
消费;
}
通知方:
synchonized(obj){
改变条件;
obj.notifyAll();
}
条件谓词:
过早唤醒:
虽然在锁、条件谓词和条件队列之间的三元关系并不复杂,但wait方法的返回并不一定意味着线程正在等待的条件谓词已经变成真了。
当执行控制重新进入调用wait的代码时,它已经重新获取了与条件队列相关联的锁。现在条件谓词是不是已经变为真了?或许。在发出通知的线程调用notifyAll时,条件谓词可能已经变成真,但在重新获取锁时将再次变成假。在线程被唤醒到wait重新获取锁的这段时间里,可能有其他线程已经获取了这个锁,并修改了对象的状态。或者,条件谓词从调用wait起根本就没有变成真。你并不知道另一个线程为什么调用notify或notifyAll,也许是因为与同一条件队列相关的另一个条件谓词变成了真。一个条件队列与多个条件谓词相关是一种很常见的情况。
基于所有这些原因,每当线程从wait中唤醒时,都必须再次测试条件谓词。
notify与notifyAll:
由于多个线程可以基于不同的条件谓词在同一个条件队列上等待,因此如果使用notify而不是notifyAll,那么将是一种危险的操作,因为单一的通知很容易导致类似于信号地址(线程必须等待一个已经为真的条件,但在开始等待之前没有检查条件谓词)的问题。
只有同时满足以下两个条件时,才能用单一的notify而不是notifyAll:
所有等待线程的类型都相同。只有一个条件谓词与条件队列相关,并且每个线程在从wait返回后将执行相同的操作。
单进单出:在对象状态上的每次改变,最多只能唤醒一个线程来执行。
判断当前线程是否正在执行,注意优先级是概率而非先后顺序(优先级高可能会执行时间长,但也不一定)
线程优先级特性:
注意,在不同的JVM以及OS上,线程规划会存在差异,有些OS会忽略对线程优先级的设定。
在Runnable的run方法中不能抛出异常,如果某个异常没有被捕获,则会导致线程终止。
要求异常处理器实现Thread.UncaughtExceptionHandler接口。
可以使用setUncaughtExceptionHandler方法为任何一个线程安装一个处理器,
也可以使用Thread.setDefaultUncaughtExceptionHandler方法为所有线程安装一个默认的处理器;
如果不安装默认的处理器,那么默认的处理器为空。如果不为独立的线程安装处理器,此时的处理器就是该线程的ThreadGroup对象
ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,它的uncaughtException方法做如下操作:
如果是由线程池ThreadPoolExecutor执行任务,只有通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler,而通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。