进程和线程
-
进程
进程是操作系统分配资源的基本单位,是程序的一次执行过程。例如,在windows中,我们可以在任务管理器里看到运行中的进程。
-
线程
线程是操作系统调度的基本单位,它是比进程粒度更细的执行单位。一个进程是由一个或多个线程组成的,进程内的线程共享进程的资源。 -
操作系统中进程的通信方式
1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全 -
Java线程得通信方式
1.使用synchronized关键字
/**
* 示例对象
*/
public class DemoObject {
public synchronized void method1(){
System.out.println("this function name is method1");
}
public synchronized void method2(){
System.out.println("this function name is method2");
}
}
/**
* 线程1
*/
public class Thread1 extends Thread{
private DemoObject demoObject;
public Thread1(DemoObject demoObject){
this.demoObject = demoObject;
}
@Override
public void run() {
demoObject.method1();
}
}
/**
* 线程2
*/
public class Thread2 extends Thread {
private DemoObject demoObject;
public Thread2(DemoObject demoObject){
this.demoObject = demoObject;
}
@Override
public void run() {
demoObject.method2();
}
}
/**
* 测试类
*/
public class Test01 {
public static void main(String[] args) {
//创建示例对象
DemoObject demoObject = new DemoObject();
//线程1
Thread thread1 = new Thread1(demoObject);
//线程2
Thread thread2 = new Thread2(demoObject);
thread1.start();
thread2.start();
}
}
由于我们将synchronized关键字修饰在实例方法,所以在执行该方法时,线程会在执行完该方法之前一直示例对象的对象锁;虽然线程1和线程2调用示例对象的方法不同,但不能同时获取对象锁,所以可以同步两个线程。
2.使用while循环
示例代码
/**
* 线程1
*/
public class Thread1 extends Thread{
private ArrayList<Integer> list;
public Thread1(ArrayList<Integer> list){
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
list.add(i);
System.out.println("count:"+i);
}
}
}
/**
* 线程2
*/
public class Thread2 extends Thread {
private ArrayList<Integer> list;
public Thread2(ArrayList<Integer> list){
this.list = list;
}
@Override
public void run() {
while(true){
if(list.size() == 10){
System.out.println("thread2 开始工作");
System.out.println("thread2 结束工作");
break;
}
}
}
}
/**
* 测试类
*/
public class Test01 {
public static void main(String[] args) {
//创建示例对象
ArrayList<Integer> list = new ArrayList();
//线程1
Thread thread1 = new Thread1(list);
//线程2
Thread thread2 = new Thread2(list);
thread1.start();
thread2.start();
}
}
只有当list的size等于10的时候,线程2才会开始工作;不过这样很浪费系统资源,因为线程2获得CPU使用权时只在轮询判断条件是否满足,不推荐使用这种方式。
3.使用notify和wait
示例代码
/**
* 线程1
*/
public class Thread1 extends Thread{
private ArrayList<Integer> list;
public Thread1(ArrayList<Integer> list){
this.list = list;
}
@Override
public void run() {
synchronized (list){
for (int i = 0; i < 100; i++) {
list.add(i);
if(i == 10){
list.notify();
System.out.println("已经通知其他线程停止等待状态");
}
System.out.println("count:"+i);
}
}
}
}
/**
* 线程2
*/
public class Thread2 extends Thread {
private ArrayList<Integer> list;
public Thread2(ArrayList<Integer> list){
this.list = list;
}
@Override
public void run() {
synchronized (list){
if(list.size() < 10){
System.out.println("不满足执行条件,进入等待状态");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已被通知,停止等待");
}
}
}
}
/**
* 测试类
*/
public class Test01 {
public static void main(String[] args) {
//创建示例对象
ArrayList<Integer> list = new ArrayList();
//线程1
Thread thread1 = new Thread1(list);
//线程2
Thread thread2 = new Thread2(list);
thread2.start();
thread1.start();
}
}
在线程2发现不满足执行条件时,释放对象锁并进入等待状态;线程1发现当前满足线程1状态时,唤醒线程1,这样便减少了CPU资源的浪费,不过也可能提早唤醒线程2,但是并不满足线程2的条件,存在使线程2一直处于等待状态。
线程的生命周期
- 创建
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
- 就绪
当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。
- 运行
线程获得CPU资源正在执行任务(run()方法),此时除非线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
- 阻塞
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
- 死亡
一般有三种情况会使线程进入到死亡状态:run()或call()方法执行完成,线程正常结束、线程抛出一个未捕获的Exception或Error以及直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。
多线程的优缺点
- 优点:一般情况下,可以提高系统的资源利用率。
- 缺点:程序设计复杂,稍有不慎,可能会造成内存泄漏,死锁以及系统资源闲置等问题。
死锁
- 示例代码
public class DeadLockDemo extends Thread{
private Object object1;
private Object object2;
public DeadLockDemo(Object o1,Object o2){
this.object1 = o1;
this.object2 = o2;
}
@Override
public void run() {
method1();
}
public void method1(){
synchronized (object1){
System.out.println("已经请求到了对象1的锁");
System.out.println("正在请求对象2的锁");
synchronized (object2){
System.out.println("已经请求到了对象2的锁");
}
}
}
}
public class DeadLockDemo2 extends Thread{
private Object object1;
private Object object2;
public DeadLockDemo2(Object o1,Object o2){
this.object1 = o1;
this.object2 = o2;
}
@Override
public void run() {
method2();
}
public void method2(){
synchronized (object2){
System.out.println("已经请求到了对象2的锁");
System.out.println("正在请求对象1的锁");
synchronized (object1){
System.out.println("已经请求到了对象1的锁");
}
}
}
}
public class DeadLockTest {
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
DeadLockDemo deadLockDemo1 = new DeadLockDemo(object1,object2);
DeadLockDemo2 deadLockDemo2 = new DeadLockDemo2(object1,object2);
deadLockDemo1.start();
deadLockDemo2.start();
}
}
- 死锁的四个必要条件
1.资源互斥:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2.不可剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
3.占有且等待:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
4.形成环路:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。
网友评论