扩大
缩小

如何实现多线程

一. 线程的状态

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方法, 才可以中断刚才的阻塞而继续去执行线程.

posted @ 2023-05-19 10:10  流华追梦  阅读(69)  评论(0编辑  收藏  举报