Thread类中常用API
Thread类中有static方法也有非静态方法,static方法是通过Thread类直接调用作用在此时正在执行的线程,而非静态方法则作用于具体的线程对象
-
static Thread currentThread()
返回对当前正在执行的线程对象的引用。 -
static boolean interrupted()
测试当前线程是否已经中断。 -
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。 -
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 -
long getId()
返回该线程的标识符。 -
String getName()
返回该线程的名称。 -
int getPriority()
返回线程的优先级。 (优先级仅仅是为该线程分配时间片的概率,仅此而已) -
void interrupt()
中断线程。 -
void join()
等待该线程终止。 -
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。 -
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 -
boolean isAlive()
测试线程是否处于活动状态。 -
boolean isDaemon()
测试该线程是否为守护线程。
补充几个在Objct类内的方法
- void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 - void wait(long timeout)
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待 - void notify()
唤醒在此对象监视器上等待的单个线程。 - void notifyAll()
唤醒在此对象监视器上等待的所有线程。 - void wait(long timeout, int nanos)
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
为了能够掌握这些API的使用,我们需要理解Thread线程在执行的状态转换过程:
值得注意的是:sleep()方法是不会释放线程同步锁的,因此在sleep时间内,同锁的线程是不能占用CPU的,而obj.wait()方法释放线程同步锁,其他线程是可以分配到CPU时间片的,而该阻塞线程在唤醒之前无权占用CPU
sleep和wait区别检测
先来看看sleep()
public class TestSleep {
static Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("t1获取了锁进入了同步语句块");
try {
long sleepbegin = System.currentTimeMillis();
Thread.sleep(5000);
System.out.println("睡眠了"+(System.currentTimeMillis()-sleepbegin)+"ms");
for(int i = 0 ; i < 20 ; i ++)
System.out.println(Thread.currentThread().getName()+":"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("切换到t2");
for(int i = 0 ; i < 20 ; i ++)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
t1.start();
t2.start();
}
}
结果如下:我们看到sleep()并不会释放锁供其他同锁线程任务使用CPU。
t1获取了锁进入了同步语句块
睡眠了5000ms
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
Thread-0:17
Thread-0:18
Thread-0:19
切换到t2
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Thread-1:11
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-1:19
接下来是wait的测试
public class TestWait {
static Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("t1线程开始释放锁等待CPU唤醒!");
try {
lock.wait();
System.out.println("t1线程重新得到锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("切换到t2");
lock.notify();
for(int i = 0 ; i < 50 ; i ++)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
t1.start();
t2.start();
}
}
结果如下:我们发现即便是在同锁的同步语句内,一旦wait后只能释放锁lock让出CPU分配权。
t1线程开始释放锁等待CPU唤醒!
切换到t2
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Thread-1:11
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-1:19
Thread-1:20
Thread-1:21
Thread-1:22
Thread-1:23
Thread-1:24
Thread-1:25
Thread-1:26
Thread-1:27
Thread-1:28
Thread-1:29
Thread-1:30
Thread-1:31
Thread-1:32
Thread-1:33
Thread-1:34
Thread-1:35
Thread-1:36
Thread-1:37
Thread-1:38
Thread-1:39
Thread-1:40
Thread-1:41
Thread-1:42
Thread-1:43
Thread-1:44
Thread-1:45
Thread-1:46
Thread-1:47
Thread-1:48
Thread-1:49
t1线程重新得到锁
线程同步与线程异步
- 线程同步:是指多线程在并发执行任务的时候,对于特定的资源,只有当一个线程对资源访问结束后,另一个线程才能够访问,也即线程有序地访问资源。
- 线程异步:是指多线程在执行任务时,按照随机给定的时间片,各自抢夺特定的资源,因此异步虽然不需要时刻检查是否有线程正在访问一个资源,效率高,但线程必然不安全。
打个比方:火车站的买票机制,像这类高并发的情形,倘若采用不安全的线程机制,就会导致最后一张票可能同时会被多个用户购买,也就出现了很大的问题了,而此时采用同步机制,每张票在已经有用户申请购买的过程中,不会允许其他用户试图买这张票,显然线程安全了。
特殊情况——线程死锁
public class sisuo {
static Object LockA = new Object();
static Object LockB = new Object();
public void a(){
System.out.println(Thread.currentThread().getName()+"开始执行a方法");
synchronized (LockA) {
System.out.println(Thread.currentThread().getName()+"拿到了LockA");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("准备执行b方法");
b();
}
}
public void b() {
System.out.println(Thread.currentThread().getName()+"开始执行b方法");
synchronized (LockB) {
System.out.println(Thread.currentThread().getName()+"拿到了LockB");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("准备执行a方法");
a();
}
}
public static void main(String[] args) {
sisuo k = new sisuo();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
k.a();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
k.b();
}
});
t1.start();
t2.start();
}
}
结果如下:我们发现在Thread0执行b方法时无法进入synchronized(lockB),而Thread1在执行a方法时无法进入synchronized(lockA)。这是因为Thread0 run方法在a方法执行完之前永远不会释放lockA,导致Thread1永远无法获得lockA完成a方法而永远不会释放lockB,而Thread0需要lockB来完成b方法。
Thread-1开始执行b方法
Thread-0开始执行a方法
Thread-1拿到了LockB
Thread-0拿到了LockA
Thread-0准备执行b方法
Thread-0开始执行b方法
Thread-1准备执行a方法
Thread-1开始执行a方法
(此后一直程序运行阻塞。。。)
线程池
线程池的概念
• 首先创建一些线程,它们的集合称为线程池,当服务器接收一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
• 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。
• 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
- 总接口: ExecutorService
- 实体类:ThreadPoolExecutor
- 工具类:Excutors——用于构建不同分配任务策略的线程池ThreadPoolExecutor对象
public class ThreadPoolDemo {
static int index = 0 ;
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);//设定该线程池内线程个数
for(int i = 0 ; i < 10 ; i ++) {//建立十个任务对象分配给线程池
es.execute(new Runnable() {//将任务对象封装入线程池,线程池分配任务给池内空闲线程,并开启线程执行任务
@Override
public void run() {//只有空闲线程对象才能被授予任务
synchronized (es) {
System.out.println(Thread.currentThread().getName()+": "+ index ++);
}
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+": 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
es.shutdown();
}
}
结果如下:我们看到——实际上总共就2个线程在反复的分配任务,只有池内有空闲的线程才能执行任务
pool-1-thread-1: 0
pool-1-thread-2: 1
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
pool-1-thread-1: 2
pool-1-thread-2: 3
pool-1-thread-2: 执行完毕
pool-1-thread-1: 执行完毕
pool-1-thread-1: 4
pool-1-thread-2: 5
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
pool-1-thread-1: 6
pool-1-thread-2: 7
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
pool-1-thread-1: 8
pool-1-thread-2: 9
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
线程池的submit和execute方法区别
线程池中的execute方法,即开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果。下面简要介绍一下两者的三个区别:
1、submit有返回值,而execute没有
用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。
然后我就可以把所有失败的原因综合起来发给调用者。
个人觉得cancel execution这个用处不大,很少有需要去取消执行的。
而最大的用处应该是第二点。
2、submit方便Exception处理
意思就是如果你在你的task里会抛出checked或者unchecked exception,
而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。
public class RunnableTestMain {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
/**
* execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。
*/
pool.execute(new RunnableTest("Task1"));
/**
* submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。请看下面:
*/
Future future = pool.submit(new RunnableTest("Task2"));
try {
if(future.get()==null){//如果Future's get返回null,任务完成
System.out.println("任务完成");
}
} catch (InterruptedException e) {
} catch (ExecutionException e) {
//否则我们可以看看任务失败的原因是什么
System.out.println(e.getCause().getMessage());
}
}
}
public class RunnableTest implements Runnable {
private String taskName;
public RunnableTest(final String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("Inside "+taskName);
throw new RuntimeException("RuntimeException from inside " + taskName);
}
}