概念
何为进程
**进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
何为线程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
线程和进程有何不同
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
何为多线程
多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。
为什么多线程是必要的
- 使用线程可以把占据长时间的程序中的任务放到后台去处理
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
多线程创建
thread/runnable
继承Thread类,重写run()方法
实现Runnable接口,重写run()方法
两种启动线程方法的区别
实现Runnable接口相对于继承Thread类来说,有如下显著的优势:
(1)、 适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。
(2)、 可以避免由于Java的单继承特性带来的局限。开发中经常碰到这样一种情况,即:当要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么就只能采用实现Runnable接口的方式了。
(3)、 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程可以操作相同的数据,与它们的代码无关。当共享访问相同的对象时,即共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
可以将一个Runnable接口的实例化对象作为参数去实例化Thread类对象。在实际的开发中,希望读者尽可能去使用Runnable接口去实现多线程机制。
start方法与run方法区别
调用start方法,线程不会立即执行,会等待系统调用
调用run方法,会立即执行,相当于普通函数调用,不再具有多线程特性
线程的生命周期
每个线程都要经历新建、就绪、运行、阻塞、死亡5种状态。
- 新建状态:就是通过new关键字创建线程对象时的状态。
- 就绪状态:即通过线程对象的start()方法启动线程时对应的状态,此时线程并不一定马上能进入运行状态,线程的运行由操作系统的调度程序进行线程的调度。
- 运行状态:是指线程获得cpu的执行权,线程正在执行需要执行的代码。
- 阻塞状态:当发生以下情况时线程进入阻塞状态。
- 线程调用sleep()方法主动放弃所占有的处理器资源。
- 线程调用了一个阻塞式IO方法,在方法返回之前线程阻塞。
- 线程试图获得一个同步监视器,但该监视器正在被其他线程所持有。
- 线程正在等待某个通知(notify)。
- 程序调用了线程的suspend()方法将该线程挂起。
- 死亡状态:线程会以以下三种方式结束,结束后的线程处于死亡状态。
- run()方法和call()方法执行完成,线程正常结束。
- 线程抛出一个未捕获的Exception或Error。
- 直接调用线程的stop()方法来结束线程。
线程间通信
synchronized关键字
synchronized是Java中的关键字,用来给某个对象加锁的,在程序运行到被锁住的代码部分时,必须获取该对象才能继续执行。
synchronized可以锁住方法体中的代码部分,这时锁对象可以是外置对象,可以是调用者本身(this);当synchronized在方法声明上,有两种情况:该方法是非静态的,此时锁对象为调用者本身(this);该方法是静态的,锁对象是类本身(packageName.ClassName.class),并且在方法中不能使用this作为锁对象。
底层实现:
执行monitorenter(监视器),将计数器+1,;释放monitorenter(监视器),将计数器-1;
当线程判断到计数器为0时,表示当前锁空闲,可以占用;反之线程进入等待
用途:
锁
实现线程间通信,不同线程共享同一个变量来进行锁的管理
volatile
被该关键字修饰的变量可以在多个线程间共用,在一个线程中修改了该变量后,会迫使其它线程重新读取该值,只保证可见性,不保证原子性(就是在多线程同时修改,会出现并发问题)
实现:在多个线程在各自的工作内存中维护了一个被volatile修饰的变量后,当其中某个线程将该值改变,会强制将该变量的值在主内存修改;修改完之后,会将其它线程工作内存中的该值设置过期;这样,在其它线程读取该值时,会被强制重新从主内存中读取该变量的值。
扩展:volatile主要作用保证可见性以及有序性,有序性设计复杂的指令重排、内存屏障等概念
lock
Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能
需要手动的设置加锁的起始结束位置
wait/notify机制
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。上诉两个线程通过对象O来完成交互,而对象上的wait()方法和notify()/notifyAll()方法的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作
2.1 我的第一个等待/通知机制程序
public class Run {
public static void main(String[] args) {
try {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b = new ThreadB(lock);
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyList {
private static List<String> list = new ArrayList<String>();
public static void add() {
list.add("anyString");
}
public static int size() {
return list.size();
}
}
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
lock.wait();
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadB extends Thread {
private Object lock;
public ThreadB(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
if (MyList.size() == 5) {
lock.notify();
System.out.println("已发出通知!");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
从运行结果:”wait end 1521967322359”最后输出可以看出,notify()执行后并不会立即释放锁。(当方法wait()被执行后,锁自动被释放,但执行玩notify()方法后,锁不会自动释放。必须执行完otify()方法所在的synchronized代码块后才释放。)
synchronized关键字可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了等待/通知(wait/notify)机制的相关方法,它们必须用在synchronized关键字同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify()方法可以唤醒一个因调用wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新唤醒的线程会视图重新获得临界区的控制权也就是锁,并继续执行wait方法之后的代码。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。
Thread.join()的使用
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行