如何实现多线程
一. 线程的状态
1).新建状态(New)
2).就绪状态(Runnable):
当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3).运行状态(Running)
4).阻塞状态(Blocked)
阻塞状态又可以分为三种:
a.等待阻塞: 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b.同步阻塞: 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c.其他阻塞: 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5).死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二. 实现多线程的4种方式
1. 实现Thread类
1 public class TestThread extends Thread { 2 @Override 3 public void run() { 4 System.out.println("当前线程名" + "->" + Thread.currentThread().getName()); 5 } 6 } 7 8 public static void main(String[] args) { 9 TestThread testThread = new TestThread(); 10 testThread.start(); 11 }
2. 实现Runnable接口
1 public class TestRunnable implements Runnable { 2 @Override 3 public void run() { 4 System.out.println("当前线程名" + "->" + Thread.currentThread().getName()); 5 } 6 } 7 8 public static void main(String[] args) { 9 TestRunnable testRunnable = new TestRunnable(); 10 Thread thread = new Thread(testRunnable); 11 testThread.start(); 12 }
3. 实现Callable接口,实例化FutureTask类
1 public class TestCallable implements Callable<Object> { 2 @Override 3 public Object call() throws Exception { 4 System.out.println("当前线程名" + "->" + Thread.currentThread().getName()); 5 return null; 6 } 7 } 8 9 public static void main(String[] args) { 10 TestCallable testCallable = new TestCallable(); 11 FutureTask futureTask = new FutureTask(testCallable); 12 new Thread(futureTask).start(); 13 }
FutureTask 实现了接口 RunnableFuture,RunnableFuture继承了Runnable接口,所以这种实现等同于方式2实现Runnable接口。
4. 线程池
参见《线程池详解》
三. 守护线程和用户线程
守护线程,是指在程序运行时在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。通俗点讲,任何一个守护线程都是整个JVM中所有非守护线程的"保姆"。
用户线程和守护线程几乎一样,唯一的不同之处在于如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了。因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有工作可做,当然也就没有继续执行的必要了,程序就会终止,同时会杀死所有的"守护线程",也就是说只要有任何非守护线程还在运行,程序就不会终止。
守护线程的一个典型的例子就是垃圾回收器。只要JVM启动,它始终在运行,实时监控和管理系统中可以被回收的资源
小结:
1. 守护线程一般具有较低的优先级。
2. 守护线程结束的时候,不会执行finally的语句块。
3. setDaemon要先于start()前执行,否则会报 IllegalThreadStateException。
四. sleep、join、yield、wait、notify/notifyAll
1. sleep
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,释放CPU,millis后线程自动苏醒进入就绪状态。
作用:给其它线程执行机会的最佳方式。
有时候会使用sleep(0)使CPU重新发起一次调度。
2. join
等待该线程终止,join是基于wait实现的。父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程
3. yield
yield()方法作用是放弃当前CPU资源,让给其他线程去使用,但是放弃时间不确定。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁,由运行状态变为就绪状态, 但yield()从未导致线程转到等待/睡眠/阻塞状态,让OS再次选择线程。
作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
4. wait、notify、notifyAll
Object.wait(long)要跟Object.notify()/notifyAll()搭配使用。
wait 与 notify/notifyAll 方法必须在synchronized 同步代码块中使用,即要先对调用对象加锁,不放在synchronized 中则会在program runtime时扔出“java.lang.IllegalMonitorStateException”异常。
如果指定了wait的时间,到时间会自动唤醒;否则就需要 notify / notifyAll 去唤醒。
wait等待的其实是对象monitor,由于Java中的每一个对象都有一个内置的monitor对象,自然所有的类都理应有wait/notify方法。
注意点:
1). sleep和yield的区别在于, sleep可以使优先级低的线程得到执行的机会, 而yield只能使同优先级的线程有执行的机会
2). wait()和sleep()都可以通过interrupt()方法立即打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
wait() 是Object类 的方法,sleep()是 Thread 类的方法。
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
都可以指定阻塞指定秒数,并返回。
sleep方法没有释放锁,而wait方法释放了锁。
3). interrupt方法不会中断一个正在运行的线程.就是指线程如果正在运行的过程中, 去调用此方法是没有任何反应的。 因为这个方法只是提供给被阻塞的线程, 即当线程调用了 Object.wait, Thread.join, Thread.sleep三种方法之一的时候, 再调用interrupt方法, 才可以中断刚才的阻塞而继续去执行线程.
-------------------------------------------
个性签名:恬淡的生活态度,适当的坚持付出,散发出你闪耀的人生哲学!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个 [推荐]哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!