join()
“程序(Program)”是一个静态的概念,一般对应于操作系统中的一个可执行文件。
执行中的程序叫做进程(Process),是一个动态的概念。现代的操作系统都可以同时启动多个进程。
特点:
进程是程序的一次动态执行过程, 占用特定的地址空间。
每个进程由3部分组成:cpu、data、code
。
每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西,这样的缺点是:浪费内存,cpu 的负担较重。
多任务 (Multitasking)
操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占CPU的使用权。
进程的查看
Windows系统: Ctrl+Alt+Del
,启动任务管理器即可查看所有进程。
Unix系统: ps
or top
。
一个进程可以产生多个线程。同多个进程可以共享操作系统的某些资源一样,同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process)。
一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。
一个进程可拥有多个并行的 (concurrent)
线程。
一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
线程的启动、中断、消亡,消耗的资源非常少。
最根本区别:进程是资源分配的单位,线程是调度和执行的单位
进程间的切换开销较大(每个进程有独立的code和数据空间【进程上下文】)
线程切换开销小(同一进程的线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器PC)
多进程:在操作系统中能同时运行多个程序(任务)
多线程:在同一应用程序中有多个顺序流同时执行
线程是进程的一部分,所以线程被称作【轻量级进程】
一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
public class MyThread extends Thread { // 线程体 public void run() { try { // getName() 返回线程名称 System.out.println(this.getName() + ":" + i); sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); MyThread t2 = new MyThread(); t2.start(); } }
缺点:若自定义类
MyThread
先前就继承了一个类,则无法再继承Thread
类
开发中常用Runnable
接口实现多线程,在实现Runnable接口的同时还可以继承某个类。
public class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { sout(Thread.currentThread().getName() + ":" + i); } } public static void main(String[] args) { //创建线程对象,把实现了Runnable接口的对象作为参数传入 Thread t1 = new Thread(new MyThread()); t1.start(); Thread t2 = new Thread(new MyThread()); t2.start(); } }
一个线程对象在它的生命周期内,需要经历5个状态。
用 new 关键字建立一个线程对象后,该线程就处于新生状态。
处于新生状态的线程有自己的内存空间,通过调用 start() 进入【就绪状态】。
处于就绪状态的线程已具备了运行条件,但还没被分配到cpu,处于【线程就绪队列】,等待系统分配其CPU。
就绪状态不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。
一旦获得CPU,线程就进入【运行状态】并自动调用自己的 run方法。有4中情况会导致线程进入就绪状态:
新建线程:调用start()方法,进入就绪状态;
阻塞线程:阻塞解除,进入就绪状态;
运行线程:调用yield()方法,直接进入就绪状态;
运行线程:JVM将CPU资源从本线程切换到其他线程
在运行状态的线程执行自己run方法中的代码,直到【调用其他方法而终止】或【等待某资源而阻塞】或【完成任务】而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。有4种原因会导致阻塞:
执行 sleep(int millsecond)
方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
执行 wait()
方法,使当前线程进入阻塞状态。经 nofity()
方法唤醒后,它进入就绪状态。
线程运行时,【某个操作】进入阻塞状态,比如执行IO流操作 (read() / write()
方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
join()
线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用 join()
方法。
死亡状态是线程生命周期中的最后一个阶段。
线程死亡的原因有两个:
正常运行的线程完成了它 run()
方法内的全部工作
另一个是线程被强制终止,如通过执行 stop()
或destroy()
方法来终止一个线程
stop()/destroy()
方法已经被JDK废弃
当一个线程进入死亡状态以后,就不能再回到其它状态了。
public class MyThreadCircle implements Runnable { String name; boolean live = true; // 标记变量 public static void main(String[] args) { MyThreadCircle ttc = new MyThreadCircle("线程A:"); Thread t = new Thread(ttc); // 新生状态 t.start(); // 就绪状态 for (int i = 0; i < 100; i++) { print("主线程:" + i); } ttc.terminate(); print("ttc stop"); } @Override public void run() { int i = 0; while (live) { print(name + (i++)); } } public void terminate() { live = false; } }
sleep()
,(如上Java中实现多线程)可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态yield()
,可以让正在运行的线程直接进入就绪状态,让出CPU的使用权join()
线程A在运行期间,可以调用线程B的 join()
方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。
public class TestThreadJoin { public static void main(String[] args) { System.out.println("线程A就必须等待线程B执行完毕后,才能继续执行"); Thread A = new Thread(new ThreadA()); A.start(); } } class ThreadA implements Runnable { @Override public void run() { Thread B = new Thread(new ThreadB()); B.start(); System.out.println("线程A等待线程B执行完毕"); try { B.join(); } catch (InterruptedException e) { e.printStackTrace(); // 结束JVM。如果是0则表示正常结束;如果是非0则表示非正常结束 System.exit(1); } System.out.println("线程A开始执行\n...."); } } class ThreadB implements Runnable { @Override public void run() { System.out.println("线程B开始执行"); try { for (int i = 0; i < 5; i++) { System.out.println("线程B执行的第" + (i+1) +"分钟"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程B执行完毕"); } }
结果:
线程A就必须等待线程B执行完毕后,才能继续执行 线程A等待线程B执行完毕 线程B开始执行 线程B执行的第1分钟 线程B执行的第2分钟 线程B执行的第3分钟 线程B执行的第4分钟 线程B执行的第5分钟 线程B执行完毕 线程A开始执行 ....
处于就绪状态的线程,会进入“就绪队列”等待 JVM 来挑选。
线程的优先级用数字表示,范围从1到10(1最低),一个线程的缺省优先级是5。
使用下列方法获得或设置线程对象的优先级。
int getPriority();
void setPriority(int newPriority);
优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
synchronized
在方法声明中加入该关键字
public synchronized void accessVal(int newVal);
synchronized
方法控制对【对象的类成员变量】的访问:
synchronized
方法都必须获得调用该方法的对象的锁才能执行,否则所属线程阻塞synchronized
块
synchronized 修饰方法的缺点:另外线程必须等待整个方法执行完毕,将会大大影响效率
synchronized
块可以让我们精确地控制到具体的【成员变量】,缩小同步的范围,提高效率
synchronized(syncObj) { // ... }
线程同步实例:
public class TestThreadSync { } /*简单表示银行账户*/ class Account { int money; String name; public Account(int money, String name) { super(); this.money = money; this.name = name; } } /**/ class Drawing extends Thread { int drawingMoney; // 取多少钱 Account account; // 取钱的账户 int totalDrawing; // 总共取的钱数 public Drawing(int drawingMoney, Account account){ super(); this.drawingMoney = drawingMoney; this.account = account; } void draw() { synchronized(account) { if (account.money - drawingMoney < 0) { print(this.getName() + " 取款,余额不足"); return ; } try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.money -= drawingMoney; totalDrawing += drawingMoney; } System.out.println(this.getName() + "-->总共取了:" + totalDrawing); System.out.println(this.getName() + "-->账户余额:" + account.money); } @Override public void run() { draw(); } }
Thread-1,取款,余额不足 Thread-0-->总共取了:80 Thread-0-->账户余额:20
如果没有线程同步机制,两个线程同时操作同一个账户对象:
Thread-0-->总共取了:80 Thread-0-->账户余额:20 Thread-1-->总共取了:80 Thread-1-->账户余额:-60
TODO
TODO
TODO
速学堂Notes