线程状态切换
线程创建
线程的创建原理
当我们调用 new Thread() 时,JVM 并不会立即创建一个与其对应的系统线程<在堆区创建线程对象>,而是当调用了 start() 方法之后,JVM 才会通过系统调用 clone 来创建一个与其对应的系统线程(参考 pthread_create())。因为 Java 线程最终被映射为系统线程,所以当我们需要创建线程时,尤其是需要大量线程时,我们需要注意:
- 操作系统对线程的数量的限制
- 创建、调度和终止线程的系统开销
- 线程本身对系统资源的消耗(尤其是内存,JVM 需要为每个线程维护一个独立的线程栈 -Xss)
Thread 类中的start() 和 run() 方法有什么区别?
这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
参考:zhuanlan.zhihu.com/p/55819440
线程创建4种方式
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class NewThreadDemo {
public static void main(String[] args) throws Exception {
//第一种方式 Thread
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("第1种方式:new Thread 1");
}
};
t1.start();
TimeUnit.SECONDS.sleep(1);
//第二种方式 Runnable
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("第2种方式:new Thread 2");
}
});
t2.start();
TimeUnit.SECONDS.sleep(1);
//第三种方式 Callable
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
String result = "第3种方式:new Thread 3";
return result;
}
});
Thread t3 = new Thread(futureTask);
t3.start();
// 线程执行完,才会执行get(),所以FutureTask也可以用于闭锁
String result = futureTask.get();
System.out.println(result);
TimeUnit.SECONDS.sleep(1);
//第四种方式 线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
Future<String> future = pool.submit(new Callable<String>(){
@Override
public String call() throws Exception {
String result = "第4种方式:new Thread 4";
return result;
}
});
pool.shutdown();
System.out.println(future.get());
}
}
用Runnable还是Thread?
大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。
Java中Runnable和Callable有什么不同?
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
线程暂停
Thread.sleep() (睡眠,不释放锁)
线程类方法。
sleep()方法需要指定等待的时间,它可以让当前正在执行该语句的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。
public class TestThreadSleep implements Runnable{
public static void main(String[] args) {
TestThreadSleep runnable = new TestThreadSleep();
Thread thread = new Thread(runnable);
thread.start();
}
@Override
public void run() {
System.out.println("i am sleep for a while!");
try {
Date currentTime = new Date();
long startTime = currentTime.getTime();
Thread.sleep(4000);
currentTime = new Date();
long endTime = currentTime.getTime();
System.out.println("休眠时间为:"+(endTime-startTime)+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
thread.join() (主线程等待子线程)
线程对象实例方法。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
在很多情况下,主线程创建并启动了线程,如果子线程中进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
package concurrent;
public class TestJoin {
public static void main(String[] args) {
Thread thread = new Thread(new JoinDemo());
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程第" + i + "次执行!");
if (i >= 2)
try {
// t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class JoinDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程1第" + i + "次执行!");
}
}
}
Thread.yield() (礼让CPU,重新回到就绪态)
线程类方法。
yield()只是使执行语句的当前线程重新回到可执行状态**,****所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。**yield()只能使同优先级或更高优先级的线程有执行的机会。调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-----" + i);
if (i % 20 == 0) {
Thread.yield();
}
}
};
new Thread(runnable, "栈长").start();
new Thread(runnable, "小蜜").start();
}
线程中断
Java中interrupt,interrupted 和 isInterrupted方法的区别?
线程协作(wait,notify,notifyAll)
首先要注意的是:wait,notify,notifyAll 这三个方法跟 sleep,yield,join,interrupt,suspend,resume,stop 这些不一样,后者是 Thread 特有的方法,前者是 Object 的方法。
锁池和等待池
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
notify和notifyAll的区别
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
- 综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
public class WaitNotifyCase {
public static void main(String[] args) {
final Object object = new Object();//锁
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (object) {
try {
System.out.println("thread A get lock");
TimeUnit.SECONDS.sleep(1);
System.out.println("thread A do wait method");
object.wait();
System.out.println("wait end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (object) {
System.out.println("thread B get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
object.notify();
System.out.println("thread B do notify method");
}
}
}).start();
}
}