1.进程和线程
(1)进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.一个程序至少有一个进程。
(2)线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.是在进程内的多条执行路径。
(3)区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
2.Thread和Runnable
(1)Thread方式启动线程
private StartByThread() {
new Thread() {
@Override
public void run() {
//耗时操作
}
}.start();
}
(2)Runnable方式启动线程
private StartByThread() {
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
}
}) .start();
}
看起来区别不大,(1)中覆写了Thread类中的run函数执行耗时操作,(2)中向Thread构造函数中传递了一个Runnable对象,而在Runnable对象中执行了耗时操作。
Thread和Runnable关系
public class Thread implements Runnable {
//所属的线程组
volatile ThreadGroup group;
//要执行的目标任务
Runnable target;
public Thread() {
create(null, null, null, 0);
}
public Thread(Runnable runnable) {
create(null, runnable, null, 0);
}
private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
Thread currentThread = Thread.currentThread();
//group参数为空,获取当前线程的线程组
if (group == null) {
group = currentThread.getThreadGroup();
}
this.group = group;
//设置Target
this.target = runnable;
this.stackSize = stackSize;
// add ourselves to our ThreadGroup of choice
this.group.addThread(this);
}
//启动一个新线程,如果target不为空,则执行target的run函数,
//否则执行当前对象的run函数
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
//调用native函数启动新线程
nativeCreate(this, stackSize, daemon);
}
}
由上可知,实际上最终被线程执行的任务是runnable,而非Thread。Thread只是对Runnable的包装,并且通过一些状态对Thread进行管理和调度,Runnable接口定义了可执行的任务,有一个无返回值的run()函数。Runnable的声明如下:
public interface Runnable {
public void run();
}
当启动一个线程时,如果Thread的target不为空,则会在子线程中执行这个target,否则虚拟机就会执行当前线程自身的run函数。
3.线程的wait,sleep,join,yield
(1)wait
当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁,使得其他线程可以访问。用户可以使用notify、notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。
注意:wait、notify、notifyAll必须放在synchronized block中,否则抛出异常。
private static Object sLockObject = new Object();
static void waitAndNotifyAll() {
System.out.println("主线程运行");
//创建并启动子线程
Thread thread = new WaitThread();
thread.start();
long startTime = System.currentTimeMillis();
try {
synchronized (sLockObject) {
System.out.println("主线程在等待");
sLockObject.wait();
}
}catch (Exception e){
}
long timesMs = System.currentTimeMillis() - startTime;
System.out.println("主线程等待耗时——>"+timesMs+" ms");
}
//等待线程
static class WaitThread extends Thread {
@Override
public void run() {
try {
synchronized (sLockObject) {
Thread.sleep(3000);
sLockObject.notifyAll();
}
}catch(Exception e) {
}
}
}
在waitAndNotifyAll()函数中,会启动一个WaitThread线程,在线程中会调用sleep函数睡眠3秒钟。线程启动之后在主线程调用sLockObject的wait函数,使线程进入等待状态,此时将不会执行。等waitThread在run函数中沉睡3s钟之后调用sLockObject的notifyAll函数,此时就会重新唤醒正在等待中的主线程,因此会继续执行下去。
执行结果:
主线程运行
主线程在等待
主线程等待耗时——>3002 ms
wait、notify机制通常用于等待机制的实现,当条件未满足时调用wait进入等待状态,一旦条件满足,调用notify、notifyAll唤醒等待的线程继续执行。
(2)sleep
该函数是Thread的静态函数,作用是调用线程进入睡眠状态。因为sleep是Thread的static方法,因此它不能改变对象的机锁。所以,当在一个synchronized中调用Sleep方法时,线程虽然休眠了,但对象的机锁并没有释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
(2)join
等待目标线程执行完成之后再继续执行。
阻塞当前调用join函数时所在的线程,直到接收线程执行完毕后再继续。
举例说明:
static void joinDemo() {
Worker worker1 = new Worker("worker-1");
Worker worker2 = new Worker("worker-2");
worker1.start();
System.out.println("启动线程1");
try {
worker1.join();
System.out.println("启动线程2");
worker2.start();
worker2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程继续执行");
}
//等待线程
static class Worker extends Thread {
public Worker(String name) {
super(name);
}
@Override
public void run() {
try {
Thread.sleep(3000);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println("Work in" + getName());
}
}
输出结果:
启动线程1
Work inworker-1
启动线程2
Work inworker-2
主线程继续执行
joinDemo函数中,首先创建了两个线程,然后启动worker1,下一步再调用worker1的join函数。此时,主线程进入阻塞状态知道worker1执行完毕,同理worker2.
如果不加join函数,输出结果:
启动线程1
启动线程2
主线程继续执行
Work inworker-1
Work inworker-2
(2)yield
线程礼让。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程优先执行,但其他线程能否优先执行是未知的。
static void yieldDemo() {
YieldThread t1 = new YieldThread("thread-1");
YieldThread t2 = new YieldThread("thread-2");
t1.start();
t2.start();
}
//等待线程
static class YieldThread extends Thread {
public YieldThread(String name) {
super(name);
}
public synchronized void run() {
for(int i = 0;i < 5;i++) {
System.out.printf("%s[%d]----->%d\n",this.getName(),this.getPriority(),i);
//i=2,调用当前线程的yield函数
if(i == 2) {
Thread.yield();
}
}
}
}
执行结果:
thread-1[5]----->0
thread-1[5]----->1
thread-1[5]----->2
thread-2[5]----->0
thread-2[5]----->1
thread-2[5]----->2
thread-2[5]----->3
thread-2[5]----->4
thread-1[5]----->3
thread-1[5]----->4
通常情况下,t1首先执行,让t1的run函数执行到i等于2时,让出当前线程的执行时间,因此调用yield就是让出当前线程的执行权,这样一来就可以让其他线程得到优先执行。