java面试题 --- 进程与线程

1.进程的定义

    进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。进程实体由程序段, 数据段 PCB(进程控制块)组成。

2.线程的定义

   线程可以看做轻量级进程,线程是进程的执行单元,是进程调度的基本单位。

3.并行与并发的定义

    并行(parallel)是指在同一时刻,有多条指令在多个处理器上同时运行;并发(concurrency)指的是在同一时刻只能有一条指令执行,即每个指令以时间片为单位来执行 。

4.线程的创建与启动

4.1 继承Thread类来创建并启动多线程

public class FirstThread extends Thread {
    private int i;
    @Override
    public void run () {
      for(i=0;i<100;i++) {
       // 返回当前线程的名字
       System.out.println(getName() + " " + i);
      }
    }
    public static void main(String[] args){
       for (int i=0;i<100;i++) {
          System.out.println(Thread.currentThread().getName() + " " + i);
          if (i == 20) {
            //创建并启动第一个线程
            new FirstThread().start();
            //创建并启动第二个线程
            new FirstThread().start();
          }
       }
    }
}

4.2 实现Runnable接口

public class SecondThread implements Runnable{

    private int i;
    @Override
    public void run() {
        for(;i<100;i++) {
            //实现Runnable接口时,想获取当前线程只能用Thread.currentThread
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20) {
                SecondThread  st = new SecondThread();
                new Thread(st,"新编程1").start();
                new Thread(st,"新编程2").start();
            }
        }
    }
}

4.3 实现Callable接口通过FutureTask包装器来创建Thread线程

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThirdThread {
    public static void main(String[] args) {
        //创建Callable对象
        ThirdThread rt = new ThirdThread();
        //先使用Lambda表达式创建Callable<Integer>对象
        //使用FutureTask来包装Callable对象
        FutureTask<Integer> task = new FutureTask<>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName()+"循环变量i的值:"+i);
            }
            return i;
        });
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值:"+i);
            if(i==20) {
                //实质还是以Callable对象来创建并启动的
                new Thread(task,"有返回值的线程").start();

                /*Thread t = new Thread(task);
                t.start();*/
            }
        }
        try {
            System.out.println("子线程的返回值:"+task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

5.Runnable,Callable接口与Thread类的对比

1>继承Thread类,重写该类的run()方法,也就是这个线程需要完成的任务,线程对象的start()方法可以用来启动该线程 
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

2>实现Runnable接口,同样的在run()方法中编写需要线程实现的操作,采用线程对象的start()方法启动, 
使用实现Runnable接口的方法来创建线程类时,多个线程之间可以共享线程类的实例变量。

3>采用实现Runnable,Callable接口优缺点: 
1,接口可以多继承,继承了Runnable接口还能继承其他接口 
2,适合多个相同线程来处理同一份资源的情况, 
3,缺点是,编程稍微复杂,访问当前线程必须使用Thread.currentThread() 
采用继承Thread类优缺点: 
1,编写简单,访问当前线程可直接用this 
2,缺点是,不能再继承其他类 
综上,建议采用实现Runnable接口的方法来创建和启动线程

6.线程的生命周期

在线程的生命周期中,要经过新建(new),就绪(Runnable),运行(Running),阻塞(Blocked)和死亡(Dead)5种状态,CPU会在不同的线程之间来回切换,线程会不断的经历运行,阻塞

1>新建状态:使用new关键字创建了一个线程,如Thread t = new MyThread();

2>就绪状态:线程对象调用了start()方法后,线程进入就绪状态,就绪状态就是等待CPU的调度执行

3>运行状态:CPU调用就绪状态的线程,此时线程进入运行状态

4>阻塞状态:处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态

5>死亡状态:线程执行完毕或者异常退出,也可以用stop()方法来结束线程,但是容易造成死锁

7.常用方法介绍

1>sleep()

 功能:使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。

例:有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有 Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会

     Thread.sleep(long millis)必须带有一个时间参数。 sleep(long)使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级的线程有执行的机会;sleep(long)是不会释放锁标志的

2>wait()和notify()、notifyAll()

       这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。

      wait() 方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法 后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

      常用的wait方法有wait()和wait(long timeout),其中,

      void wait() :使得当前线程等待,直到其他线程调用此对象的notify() 方法或者notifyAll()方法。

      void wait(long timeout):超过指定的时间量前,当前线程等待。直到其他线程调用此对象的notify() 方法 或者 notifyAll()方法。

     注:wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其他shnchronized数据可被别的线程使用。wait() 和notify()因为会对对象的“锁标志”进行操作,所以他们必需在Synchronized函数或者 synchronized block中进行调用。如果在non-synchronized函数或 non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

3>yield()

     该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会

     yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。yield()不会释放锁标志。

     实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。

      sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程在此时获取CPU占有权。 在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。

      yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会。

4>wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法

5>join方法

join()等待该线程终止。运行结束会释放对象锁。

等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束。

8.常用类的线程安全性

线程安全的集合对象:Vector、HashTable、StringBuffer

非线程安全的集合对象:ArrayList、LinkedList、HashMap、HashSet、TreeMap、TreeSet、StringBulider

发布了42 篇原创文章 · 获赞 3 · 访问量 3939

猜你喜欢

转载自blog.csdn.net/zhangting19921121/article/details/102844630
今日推荐