Java多线程学习(初阶)

一.进程与线程的区别

  • 进程是资源分配的最小单位,线程是程序执行的最小单位;

  • 进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;

  • CPU切换一个线程比切换一个进程的花费小,创建一个线程的开销比创建一个进程的开销小;

  • 线程占用的资源要⽐进程少很多;

  • 线程之间的通信更方便;同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)

  • 多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);

  • 进程对资源保护要求高,开销大,效率相对较低,线程对资源保护要求不高,但开销小,效率高,可频繁切换;

注:多进程是指操作系统能同时运行多个任务(程序)。
  多线程是指在同一程序中有多个顺序流在执行。

二.实现多线程的三种方式

(1)继承Thread类

可以通过继承 Thread 类来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过Thread.currentThread() 来获取当前线程的引用。

public class Main{
	private static class MyThread extends Thread {
 		@Override
 		public void run() { 
 			System.out.println("这里是线程体"); 
 		}
 	}
 	public static void main(String[] args){
 		MyThread t = new MyThread(); 
  		t.start(); // 线程开始运行
 	}
}

(2)实现Runnable接口

通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 参数传入来创建线程对象。该方法的好处是可以避免类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用。

public class Main{
	private static class MyRunnable implements Runnable {
 		@Override
 		public void run() { 
 			System.out.println(Thread.currentThread().getName()+"这里是线程体");
 		}
 	}
 	public static void main(String[] args){
 		Thread t = new Thread(new MyRunnable());
 		t.start(); // 线程开始运行
 	}
}

(3)实现Callable接口,并与Future、线程池结合使用(此处略)

总结:
start()方法调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。多线程程序是乱序执行的。

三.Thread类的常见构造方法

在这里插入图片描述
String name是指给线程起一个名字

四.Thread类的常见属性

在这里插入图片描述
(1)Id 是线程的唯一标识,不同线程不会重复
(2)名称在各种调试工具用到
(3)状态表示线程当前所处的一个情况
(4)优先级高的线程理论上来说更容易被调度到
(5)关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
(6)是否存活,即简单的理解,为 run 方法是否运行结束了
(7)线程的中断问题

四.线程的状态

线程的状态是一个枚举类型Thread.State

public class Main {
    public static void main(String[] args) {
        for(Thread.State state:Thread.State.values()){
            System.out.println(state);
        }
    }
}

NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED


  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”状态。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法后,该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

五.线程的状态转换

在这里插入图片描述

六.几个方法的比较

  • Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。

  • Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

  • thread.join() / thread.join(long millis),当前线程里调用其它线程 t 的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程 t 执行完毕或者 millis 时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态
    join是Thread类的一个方法,启动线程后直接调用,即 t.join() 的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

Thread t = new AThread();
 t.start();
  t.join();

注意:为什么要用join()方法?

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时运算,主线程往往将于子线程之前执行结束完成,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再执行,然后结束,这个时候就要用到join()方法了。

  • obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout), timeout时间到自动唤醒。

  • obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。obj.notifyAll()唤醒在此对象监视器上等待的所有线程。


注意:sleep()和yield()的区别是什么?

  • sleep() 使当前线程进入停滞状态,所以执行 sleep() 的线程在指定的时间内肯定不会被再次执行;yield() 只是使当前线程重新回到可执行状态,所以执行yield() 的线程有可能在进入到可执行状态后又抢到了CPU 的执行权,然后又被马上执行。
  • sleep 方法使当前运行中的线程睡眠一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于也处于可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程

注意:wait 和sleep 的区别是什么?

共同点:

  1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。

不同点:

  1. Thread类的方法:sleep(), yield()等
    Object的方法:wait() 和 notify() 等
  2. sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3. wait,notify 和notifyAll 只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。所以sleep()和wait()方法的最大区别是: sleep()睡眠时,保持对象锁,仍然占有该锁; 而wait()睡眠时,释放对象锁。

七.Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现 Runable 或 callable 类的线程,不能直接放入继承Thread的类的线程

发布了62 篇原创文章 · 获赞 6 · 访问量 4459

猜你喜欢

转载自blog.csdn.net/HU1656/article/details/104578515