进程和线程
进程
进程是操作系统分配资源的基本单位,是程序的一次执行过程。例如,在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.形成环路:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。