一、多线程的实现方式
编写多线程程序一般有三种方法,Thread,Runnable,Callable.
1).继承Thread类
class MyThread extends Thread{
public MyThread(String name) {
}
public void run(){
}
}
2).实现Runnable接口
public interface Runnable {
public abstract void run();
}
3).实现Callable接口
public interface Callable {
V call() throws Exception;
}
区别
- Thread不适合于资源的共享
- Thread类也是Runnable接口的子类。
- Callable能返回执行结果,Runnable不能返回结果;
- Callable的call()方法允许抛出异常,Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorsTester {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
// executorService.execute(new ThreadForpools(1));
int a = 1, b = 100;
Future future = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
System.out.println( " 开始处理线程!!!");
Thread.sleep(3000);
return a+b;
}
});
System.out.println(future.isDone());
System.out.println(future.isCancelled());
while (! future.isDone()) {
System.out.println("still waiting....");
Thread.sleep(1000 * 1);
// System.out.println("OK");
}
Integer obj = future.get();
System.out.println(obj);
executorService.shutdown();
}
}
二、线程的状态
- 创建(new)状态: 准备好了一个多线程的对象
- 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
- 运行(running)状态: 执行run()方法
- 阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
- 终止(dead)状态: 线程销毁
当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源),只有线程运行需要的所有条件满足了,才进入就绪状态。
当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。
线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。
当由于突然中断或者子任务执行完毕,线程就会被消亡。
blocked、waiting、time waiting又统称为阻塞状态
注:sleep和wait的区别:
- sleep是Thread类的方法,wait是Object类中定义的方法.
- Thread.sleep不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.
Thread.sleep和Object.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.
线程的sleep()方法和yield()方法有什么区别?
- ① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
- ② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
- ③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
- ④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
请说出与线程同步以及线程调度相关的方法。
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
守护线程(Daemon Thread)和用户线程(User Thread)的区别
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。
守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
三、线程的优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
线程优先级特性:
- 继承性:
比如A线程启动B线程,则B线程的优先级与A是一样的。 - 规则性:
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。 - 随机性:
优先级较高的线程不一定每一次都先执行完。