1. 在java里如何创建多线程
如何创建多线程?
答:java创建多线程有两种方式,可以同实现 Runnable
接口,或者继承 Thread
类。
如下面示例:
package com.tenchael.forjob;
public class Demo {
public static void main(String[] args) {
new Thread(new A()).start();
new Thread(new B()).start();
}
}
class A implements Runnable {
public void run() {
try {
Thread.currentThread().sleep(1000);
System.out.println("Thread A");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class B extends Thread {
@Override
public void run() {
System.out.println("Thread B");
}
}
类A
实现 Runnable
接口,要实现run()
方法;类B
继承自Thread
,重写了run()
方法。程序是顺序执行的,若按单线程,会先打印Thread A
再打印Thread B
,但由于采用了多线程,A
类实例在run()
方法内休眠了1000ms,结果是先打印Thread B
。
2. 多线程状态
JAVA中线程有哪些状态?如何切换?
答:Java中线程有新建
、运行
、就绪
、休眠
和终止
五个状态,这五个状态的含义分别是指:
状态 | 描述 |
---|---|
新建 | 一个新产生的线程从新状态开始了它的生命周期。它保持这个状态知道程序start这个线程。 |
运行 | 当一个新状态的线程被start以后,线程就变成可运行状态,一个线程在此状态下被认为是开始执行其任务。 |
就绪 | 当一个线程等待另外一个线程执行一个任务的时候,该线程就进入就绪状态。当另一个线程给就绪状态的线程发送信号时,该线程才重新切换到运行状态。 |
休眠 | 由于一个线程的时间片用完了,该线程从运行状态进入休眠状态。当时间间隔到期或者等待的时间发生了,该状态的线程切换到运行状态。 |
终止 | 一个运行状态的线程完成任务或者其他终止条件发生,该线程就切换到终止状态。 |
状态间如下图所示:
3. 资源竞争
如何做到同步?
答:Java里采用synchronized
关键字,既可以修饰代码块,也能作用在资源上。下面用一个实例说明其用法。
下面程序有三个类,CallMe、Caller、SyncDemo。SyncDemo中创建CallMe资源;CallMe里有call()方法,call方法在打印消息的时候中途会被要求休眠1s钟;Caller里有CallMe作为调用target。
package com.tenchael.jobinterview;
public class SyncDemo {
public static void main(String[] args) {
CallMe target = new CallMe();
new Caller(target, "Hello");
new Caller(target, "Synchronized");
new Caller(target, "World");
}
}
class CallMe {
void call(String msg) {
System.out.print("[");
System.out.print(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
CallMe target;
Thread t;
Caller(CallMe target, String msg) {
this.target = target;
this.msg = msg;
t = new Thread(this, msg);
t.start();
}
public void run() {
target.call(msg);
}
}
打印结果:
[Hello[World[Synchronized]
]
]
可以看到输出的结果异常,字符串乱序,这主要是由于,线程在执行了System.out.print(msg);
后被中断,切换到其他的线程,最后在切换回来时,中途打印了其它线程的内容。
为了保证只有一个线程使用call方法,Java中可以对方法使用synchronized
修饰,实现访问的序列化。
package com.tenchael.jobinterview;
public class SyncDemo {
public static void main(String[] args) {
CallMe target = new CallMe();
new Caller(target, "Hello");
new Caller(target, "Synchronized");
new Caller(target, "World");
}
}
class CallMe {
synchronized void call(String msg) {
System.out.print("[");
System.out.print(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
CallMe target;
Thread t;
Caller(CallMe target, String msg) {
this.target = target;
this.msg = msg;
t = new Thread(this, msg);
t.start();
}
public void run() {
target.call(msg);
}
}
打印结果:
[Hello]
[World]
[Synchronized]
在类中创建synchronized
方法是一种简单的有效的解决同步的方式,然而,它并不是在所有情况下都有效果。考虑下面场景:假设你希望同步访问没有设计为多线程访问的类的对象,也就是说类中你希望同步访问的资源没有使用synchronized
,更普遍的情况,很多第三方的类库这是提供编译打包好的形式给你使用,你怎样才能使其同步访问呢?
Java中解决这个问题的方式是,对将该类的方法置于一个synchronized
的代码块中,代码块的一般形式是:
synchronized (object) {
//需要同步访问的方法
}
其含义是:
进入
synchronized
修饰的这段代码内的线程必须先去获取一个特定对象的锁定标示,并且虚拟机保证这个标示一次只能被一条线程拥有。
下面是后一种替代方案:
package com.tenchael.jobinterview;
public class SyncDemo {
public static void main(String[] args) {
CallMe target = new CallMe();
new Caller(target, "Hello");
new Caller(target, "Synchronized");
new Caller(target, "World");
}
}
class CallMe {
void call(String msg) {
System.out.print("[");
System.out.print(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
CallMe target;
Thread t;
Caller(CallMe target, String msg) {
this.target = target;
this.msg = msg;
t = new Thread(this, msg);
t.start();
}
public void run() {
synchronized (target) {
target.call(msg);
}
}
}
要注意的是,上面代码,target.call(msg);
语句块在被synchronized
修饰时,并不要求括号里的对象是target
,只要不同线程在互斥访问使用的是同一个对象,都能起到同步作用。如上面的代码也可写为:
package com.tenchael.jobinterview;
public class SyncDemo {
public static void main(String[] args) {
Object lock = new Object();// 同步锁
CallMe target = new CallMe();
new Caller(target, "Hello", lock);
new Caller(target, "Synchronized", lock);
new Caller(target, "World", lock);
}
}
class CallMe {
void call(String msg) {
System.out.print("[");
System.out.print(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
CallMe target;
Thread t;
Object lock;
Caller(CallMe target, String msg, Object lock) {
this.target = target;
this.msg = msg;
this.lock = lock;
t = new Thread(this, msg);
t.start();
}
public void run() {
synchronized (lock) {
target.call(msg);
}
}
}