进程和线程
-
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。程序(任务)执行的过程,动态性
-
进程持有资源(共享文件,共享内存)和线程
-
线程是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
-
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序 健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
线程和多线程
打开上电脑的任务管理器,就可以看到有一项名为"进程"的栏目,点击到里面可能就会发现一系列熟悉的名称:微信,360等等。
所以首先知道了,微信、360之类的应用软件在计算机上被称为一个进程。
而一个应用程序都会有自己的功能,用以执行这些进程当中的个别功能的程序执行流就是所谓的线程。
所以,线程有时候也被称为轻量级进程,是程序执行流当中的最小单元。
线程的划分尺度小于进程,其不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,所以能极大地提高了程序的运行效率。
所以简而言之的概括的话,就是:一个程序至少有一个进程,一个进程至少有一个线程。
以qq管家来说,里面的一项功能任务“病毒查询”就是该应用程序进程中的一个线程任务。
而除此任务之外,我们还可以同时进行多项操作。例如:“木马查杀”、“垃圾清理”等。
那么,以上同时进行的多项任务就是所谓的存活在qq管家应用程序进程中的多线程并发。
利与弊:
得益于多线程能够实现的并发操作,即使执行过程中某个线程因某种原因发生阻塞,也不会影响到其它线程的执行。
也就是说,多线程并发技术带来的最大好处就是:很大程度上提高了程序的运行效率。
多线程并发技术,在某种程度上会导致任务执行效率的降低,多开一个线程,CPU就在多个线程之间做切换的操作,会导致某个线程执行任务的时间延长
可以设置线程优先级来提高CPU随机访问该线程的概率。
线程的生命周期和转态
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
-
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
-
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
-
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
java提供了三种创建线程的方法
- 通过实现 Runnable 接口;
-
Thread(RunnablethreadOb,StringthreadName);
这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。
-
public class ThreadDemo implements Runnable { @Override public void run() { } }
- 通过继承 Thread 类本身;
-
public class ThreadDemo extends Thread { @Override public void run() { super.run(); } }
- 通过 Callable 和 Future 创建线程。
序号 | 方法描述 |
---|---|
1 | public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() 中断线程。 |
8 | public final boolean isAlive() 测试线程是否处于活动状态。 |
注意:
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
多线程编程
线程同步:
通过上图例子,我们可以看出,2号售票口售出了座位号为0的票,这个是不正确的,原因就在于当number=1时,由于1号售票口延迟了100ms,线程阻塞,这时候2号售票口也进入到if循环体中,导致1号和2号售票口都唤醒后,继续执行对应的逻辑。
多线程操作并不会保证当处理一个线程时,一定会把当前线程的所有代码执行完,因为CPU的处理方式是在不同的线程间进行切换,
所以多线程有一定的安全隐患。
同步锁
解决线程隐患的办法可以引入同步锁,每次只能有一个线程才能获取到这把锁,只有持有锁的线程才能执行同步当中的代码,其它线程被拒之门外。
java同步的方式:同步代码块和同步函数
- //同步代码块
- synchronized (对象锁) {
- //同步代码
- }
- //同步函数
- synchronized void method(){
- //同步代码
- }
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。同理,线程也会出现死锁现象。
- Queue q1 = new Queue(true);
- Queue q2 = new Queue(false);
- Thread t1 = new Thread(q1, "线程1");
- Thread t2 = new Thread(q2, "线程2");
- t1.start();
- t2.start();
- while (true) {
- if (flag) {
- synchronized (MyLocks.LOCK_A) {
- System.out.println(threadName + "获取了锁A");
- synchronized (MyLocks.LOCK_B) {
- System.out.println(threadName + "获取了锁B");
- }
- }
- } else {
- synchronized (MyLocks.LOCK_B) {
- System.out.println(threadName + "获取了锁B");
- synchronized (MyLocks.LOCK_A) {
- System.out.println(threadName + "获取了锁A");
- }
- }
- }
1.生产者生产商品;
2.消费者购买商品。
3.可能会同时存在多个生产者与多个消费者。
4.多个生产者中某个生产者生产一件商品,就暂停生产,并在多个消费者中通知一个消费者进行消费;
消费者消费掉商品后,停止消费,再通知任一一个生产者进行新的生产工作。
- public class ThreadCommunication {
- public static void main(String[] args) {
- Queue q = new Queue();
- Customer c = new Customer(q);
- Producer p = new Producer(q);
- Thread t1 = new Thread(c, "消费者1-");
- Thread t2 = new Thread(c, "消费者2-");
- Thread t3 = new Thread(p, "生产者1-");
- Thread t4 = new Thread(p, "生产者2-");
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
- class Queue {
- //当前商品数量是否为0
- private boolean isEmpty = true;
- //生产
- public synchronized void put() {
- String threadName = Thread.currentThread().getName();
- //如果生产者线程进入,而现在还有剩余商品
- while (!isEmpty) {
- try {
- wait();//则该生产者暂时等待,不进行生产
- } catch (InterruptedException e) {
- }
- }
- //否则则生产一件商品
- isEmpty = false;
- System.out.println(threadName + "生产了一件商品");
- //唤醒阻塞的线程,通知消费者消费
- this.notifyAll();
- }
- //消费
- public synchronized void take() {
- String threadName = Thread.currentThread().getName();
- //消费者前来消费,如果此时没有剩余商品
- while (isEmpty) {
- try {
- wait();//则让消费者先行等待
- } catch (InterruptedException e) {
- }
- }
- //否则则消费掉商品
- isEmpty = true;
- System.out.println(threadName + "消费了一件商品");
- //通知生产者没有商品了,起来继续生产
- this.notifyAll();
- }
- }
- class Customer implements Runnable {
- Queue q;
- Customer(Queue q) {
- this.q = q;
- }
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- q.take();
- }
- }
- }
- class Producer implements Runnable {
- Queue q;
- Producer(Queue q) {
- this.q = q;
- }
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- q.put();
- }
- }
- }