一、概念题
1. 简述程序、进程和线程之间的关系,什么是多线程程序?
程序、进程和线程之间的关系:
程序: 程序是一组有序指令的集合,并存放于某种介质中,是一个静态概念。
进程: 进程是为使程序能并发执行,且为了对并发执行的程序加以描述和控制而引入的概念。
在了解进程前,先说明下进程实体。进程实体是由程序段、相关的数据段和PCB三部分组成,是一个能独立运行、独立分配资源和独立接受调动的基本单位。
现在来说明进程的定义:进程是进程实体的一次执行过程,是系统进行资源分配和调度的一个独立单位。它是一个动态概念。
线程: 随着多处理机系统的发展,提高程序的并发执行程度的要求越来越高。为提高系统的并发执行度,进而引入了线程的概念。线程是比进程更小的能独立运行的基本单位(故又称“轻型进程”),更好的提高了程序的并发执行程度充分发挥了多处理机的优势。
多线程程序:
多线程:在同一个进程中同时运行的多个任务
举个例子,多线程下载软件,可以同时运行多个线程,但是通过程序运行的结果发现,每一次结果都不一致。 因为多线程存在一个特性:随机性。造成的原因:CPU在瞬间不断切换去处理各个线程而导致的,可以理解成多个线程在抢CPU资源。
2. 线程有哪五个基本状态?他们之间如何转化?简述线程的生命周期。
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
1.新建状态(New): 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
2.就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
3.运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
4.阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态: (1)线程通过调用sleep方法进入睡眠状态; (2)线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; (3)线程试图得到一个锁,而该锁正被其他线程持有; (4)线程在等待某个触发条件;
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
5.死亡状态(Dead)
有两个原因会导致线程死亡: (1) run方法正常退出而自然死亡, (2) 一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
3. Runable接口中包括哪些抽象方法?Thread类中有哪写主要的成员变量和方法?
Runable接口中包括的抽象方法:
Runnable 接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为 run()
Thread类中主要的成员变量和方法
变量(属性):
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
*/
private long stackSize;
/*
* JVM-private state that persists after native thread termination.
*/
private long nativeParkEventPointer;
/*
* Thread ID
*/
private long tid;
/* For generating thread ID */
private static long threadSeqNumber;
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
volatile Object parkBlocker;
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
//线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public enum State {// 线程状态
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
方法:
(1)新建线程:
Thread t1 = new Thread();
t1.start();
新建线程,应该调用start()方法启动线程;如果直接调用run()方法,该方法也会执行,但会被当做一个普通的方法,在当前线程中顺序执行;而如果使用start()方法,则会创建一个新的线程执行run()方法。
(2)线程中断:
public void interrupt();
public boolean isInterrupted();
public static boolean interrupted();
三个方法很相似,线程中断只是通知目标线程有人希望你退出,而并不是使目标线程退出。 第一个方法是通知目标线程中断,即设置目标线称的中断标志位; 第二个方法判断当前线程是否被中断,如果被中断(即中断标志位被设置),则返回true,否则返回false; 第三个方法判断当前线程的中断状态,并清除该线程的中断标志位(也就意味着,如果连续调用两次该方法,并且中间没有再次设置中断标志位,第二次会返回false,因为中断标志位已经被清除)。
public static native void sleep(long millis) throws InterruptedException;
sleep()方法会将当前线程休眠若干ms,如果在休眠期间被调用interrupt()方法,则会抛出InterruptedException异常。如下:
public class TestThread implements Runnable{
@Override
public void run() {
while(true) {
if(Thread.currentThread().isInterrupted()){ //如果当前线程已经被设置了中断标志位,则返回true
System.out.println("Interrupted");
break;
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interruted when sleep!");
Thread.currentThread().interrupt(); //Thread.sleep()方法由于中断而抛出异常,此时,它会清除中断标记;
}
Thread.yield();
}
}
public static void main(String[] args){
Thread t1 = new Thread(new TestThread());
t1.start();
t1.interrupt(); //设置目标线程的中断标志位,中断标志位表示当前线程已经被中断了
}
}
(3)等待(wait)和通知(notify):
public final void wait() throws InterruptedException;
public final native void notify();
public final native void notifyAll();
obj.wait()是设置当前线程在该对象上等待,直到有线程调用obj.notify()方法(或notifyAll()方法)。当调用wait()方法后,该线程会进入一个等待队列,等待队列中可能有多个线程,notify()会随机唤醒其中一个线程,而notifyAll()会唤醒所有线程。 wait()和notify()方法必须在sychronized代码块中,调用这些方法时都需要先获得目标对象的一个监视器,然后调用这些方法时会释放监视器 与sleep不同的是,sleep()会一直占有所持有的锁,而wait()会释放锁。
(4)等待线程(join)和谦让(yield)
public final void join() throws InterruptedException;
public static native void yield();
如果一个线程的执行需要另一个线程的参与(比如当前线程执行需要另一个线程执行完毕才能继续执行),这时候可以调用join()方法。t1.join()方法表示等待线程t1执行完毕之后,当前线程再继续执行。当然也可以给join()设置时间参数。 注:join()的本质是让调用线程wait()在当前线程对象实例上,其部分源码如下:
while (isAlive()) {
wait(0);
}
当线程执行完毕后,它会让被等待的线程在退出前调用notifyAll()通知所有等待的线程继续执行。因此不要在Thread对象实例上使用类似wait()或者notify()等方法。 yield()方法是使当前线程让出CPU,但该线程会再次抢夺CPU。
4. start()方法和run()方法的区别,sleep()和wait()的异同
start()方法和run()方法的区别:
/**
* 那为什么不 cat.run(); 呢? 因为这样就只有main 线程了,因为cat.run();就只是一个简单的方法,并没有其他子线程
* 源码:
*1. public synchronized void start() {
* start0();
* }
* // start0(); 是本地方法,是由 JVM 来调用的,底层为C/C++实现
* // 真正实现多线程的效果,是start0(),而不是 run
* 2. private native void start0();
*/
sleep()和wait()的异同:
首先sleep 是让线程休眠,到时间后会继续执行,wait 是等待,需要唤醒再继续执行,另外sleep 和wait 在多线程中还有许多不同:
使用方面: 从使用的角度来看sleep方法是Thread线程类的方法,而wait是Object顶级类的方法。 sleep可以在任何地方使用,而wait只能在同步方法和同步块中使用。 CPU及锁资源释放: sleep、wait调用后都会暂停当前线程并让出CPU的执行时间,但不同的是sleep不会释放当前持有对象的锁资源,到时间后会继续执行,而wait会释放所有的锁并需要notify/notifyAll后重新获取到对象资源后才能继续执行。 异常捕获方面: sleep需要捕获或者抛出异常,而wait/notify/notifyAll则不需要。
5. 如何在java程序中实现多线程?简述使用Thread字类和实现Runnable接口两种方法的异同。
一个类通过继承Thread类或者实现Runnable 接口就能够实现多线程
有经验的程序员都会选择实现Runnable接口 ,其主要原因有以下两点:
首先,java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。
其次,如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
通过下面的实例可以清晰的看出两种方式的区别所在。
继承Thread 类:
package test;
public class main1 {
public static void main(String[] args) {
testThread mTh1=new testThread("A");
testThread mTh2=new testThread("B");
mTh1.start();
mTh2.start();
}
}
class testThread extends Thread{
private int count=5;
private String name;
public testThread(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 count= " + count--);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
实现Runnable 接口:
package test;
public class main2 {
public static void main(String[] args) {
testRunnable mTh = new testRunnable();
new Thread(mTh, "C").start();//同一个mTh,但是在Thread中就不可以,如果用同一个实例化对象mt,就会出现异常
new Thread(mTh, "D").start();
new Thread(mTh, "E").start();
}
}
class testRunnable implements Runnable{
private int count=15;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "运行 count= " + count--);
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6. 了解lambada表达式(了解即可)
二、编程题
-
使用Thread.sleep()编写一个倒计时10秒的java程序,输出10,9,8....1;
public class ThreadTest01 { public static void main(String[] args) { for (int i=10;i>=1;i--){ System.out.print(i+"\t"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行示例:
-
分别用Thread的方式和实现Runnable接口方法创建分线程,并遍历100以内的自然数
继承Thread类:
public class ThreadTest02 {
public static void main(String[] args) {
Thread01 thread01 = new Thread01();
thread01.start();
}
}
class Thread01 extends Thread{
@Override
public void run() {
for (int i=1;i<=100;i++){
System.out.println(i);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行示例:
实现Runnable接口:
public class ThreadTest02 {
public static void main(String[] args) {
Runnable01 runnable01 = new Runnable01();
Thread thread = new Thread(runnable01);
thread.start();
}
}
class Runnable01 implements Runnable{
@Override
public void run() {
for (int i=1;i<=100;i++){
System.out.print(i+" ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行示例:
3. 三个黄牛同时抢200张票,打印出哪个黄牛买了第几张票
package xiancheng.JavaEETest;
public class ThreadTest03 {
public static void main(String[] args) {
Ticket ticket01 = new Ticket();
ticket01.setName("黄牛一");
Ticket ticket02 = new Ticket();
ticket02.setName("黄牛二");
Ticket ticket03 = new Ticket();
ticket03.setName("黄牛三");
// 开抢
ticket01.start();
ticket02.start();
ticket03.start();
}
}
class Ticket extends Thread{
private static int count=200;
private static boolean loop=true;
@Override
public void run() {
while (loop){
seel();
}
}
public synchronized static void seel(){
if (count<=0){// 如果没票
System.out.println("票已卖完!!!");
loop=false;
}
else {// 如果还有票,暂停100毫秒
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到第"+count+"张票,"+"剩余"+(--count)+"张票");
}
}
}
运行示例:
4. 编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC…重复三遍
package xiancheng.JavaEETest;
public class ThreadTest04 {
public static void main(String[] args) {
for (int i=0;i<3;i++){
TestID01 test1 = new TestID01();
TestID02 test2 = new TestID02();
TestID03 test3 = new TestID03();
test1.setPriority(Thread.MAX_PRIORITY);
test2.setPriority(Thread.NORM_PRIORITY);
test3.setPriority(Thread.MIN_PRIORITY);
test1.start();
test2.start();
test3.start();
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n");
}
}
}
class TestID01 extends Thread{
private int i=1;
private char ID='A';
@Override
public void run() {
while (i++<=5){
System.out.print(ID);
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestID02 extends Thread{
private int i=1;
private char ID='B';
@Override
public void run() {
while (i++<=5){
System.out.print(ID);
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestID03 extends Thread{
private int i=1;
private char ID='C';
@Override
public void run() {
while (i++<=5){
System.out.print(ID);
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行示例:
三、挑战题
制作一个给定时间的倒计时器,在每个数字之间暂停1秒,每隔10个数字输出一个字符串
package xiancheng.JavaEETest;
import java.util.Scanner;
public class ThreadTest06 {
public static void main(String[] args) {
TimeLock lock = new TimeLock();
lock.start();
}
}
class TimeLock extends Thread{
private int time = 0;
@Override
public void run() {
System.out.println("请设置倒计时器的时间 time (S/秒)");
time = new Scanner(System.in).nextInt();
int i=0,j=0;
while (time>0){
time--;
i++;
if (i==10){
j+=i;
System.out.println("时间已经过去了"+j+"秒,"+"还剩"+time+"秒");
i=0;
}
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(time);
}
System.out.println("计时结束!!!");
}
}
运行示例: