程序
- 是一段可执行的代码,由线程来执行的
- 一个程序要实现多个代码同时交替运行就需要产生多个线程
进程
- 执行中的程序(程序是静态的概念,进程是动态的概念)
线程
- 定义
- 在单个进程中独立执行的线索
- 主要功能
- 最大限度利用CPU资源,使程序同一时间执行不同任务
- 何时使用
- 一个对象要做出多个动作,并且多个动作需要穿插在一起时,就需要线程来编写程序
- 举个例子,一个厨师既要炒菜又要切菜,两个动作是交替进行的
- 线程与线程通信
- 共享一个变量,并对变量的访问进行同步
- 多个线程一起执行,线程的执行顺序没有保障,但每个线程都有独立的执行路径
- 线程的调度有的平台分时间片段轮流CPU资源,有的平台是抢占式
- CPU每一个核同一时间可以处理一个线程,一个线程同一时间只能被一个核处理
- 当线程对象创建完成后,还只是一个普通对象,要想成为独立执行的线程必须调用start()
- 启动线程是先调用start()为线程的执行准备系统资源,然后再执行run()
进程与线程的关系
- 一个进程可以包含一个或者多个线程
- 线程本身不能运行,栖身于进程中,由进程启动而执行,完成任务后,自动终止,也可由进程强制终止
- 多进程
- 就是我们可以在计算机中同时运行两个或者两个以上的程序
- 我们可以打开一个记事本,再打开一个Excel
- 多线程
- 线程是最小的执行单位,这意味着一个程序可以同时执行多个任务的功能
- Excel打印的同时,我们可以输入内容
- 多个进程的内存空间和系统资源是完全独立的;多线程是共享一块内存空间和一组系统资源,有可能相互影响
- 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以进程之间的转换负担大,线程之间的转换负担小
实现多线程机制的两种方法
- package com.itlwc;
- public class Test {
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(new Thread() {
- public void run() {
- for (int i = 0; i < 3; i++) {
- System.out.println("继承Thread类重写run()实现多线程运行");
- }
- }
- });
- Thread t2 = new Thread(new Runnable() {
- public void run() {
- for (int i = 0; i < 3; i++) {
- System.out.println("实现Runnable接口中run()实现多线程运行");
- }
- }
- });
- t1.start();
- t2.start();
- }
- }
- /*
- 打印结果:
- 继承Thread类重写run()实现多线程运行
- 继承Thread类重写run()实现多线程运行
- 继承Thread类重写run()实现多线程运行
- 实现Runnable接口中run()实现多线程运行
- 实现Runnable接口中run()实现多线程运行
- 实现Runnable接口中run()实现多线程运行
- */
Thread常用方法
- package com.itlwc;
- public class Test {
- public static void main(String[] args) throws InterruptedException {
- // 获取返回对当前正在运行的线程对象的引用
- Thread thread = Thread.currentThread();
- // 返回当前线程的线程组中活动线程的数目
- int ii = Thread.activeCount();
- // 获取当前正在执行main()的线程id
- long l = thread.getId();
- // 获取当前正在执行main()的线程名称
- String s = thread.getName();
- // 获取当前正在执行main()的线程优先级
- int i = thread.getPriority();
- //返回该线程的状态
- Thread.State state =thread.getState();
- //中断线程
- thread.interrupt();
- //测试当前线程是否已经中断
- Thread.interrupted();
- //测试线程是否处于活动状态
- thread.isAlive();
- //测试该线程是否为守护线程
- thread.isDaemon();
- //等待该线程终止
- thread.join();
- //改变线程名称,使之与参数 name 相同
- thread.setName("普通线程");
- //将该线程标记为守护线程或用户线程
- thread.setDaemon(true);
- //更改线程的优先级
- thread.setPriority(1);
- //在指定的毫秒数内让当前正在执行的线程休眠
- Thread.sleep(1000);
- //暂停当前正在执行的线程对象,并执行其他线程s
- Thread.yield();
- }
- }
线程的5种状态
- 新建状态(new)
- 当一个线程对象创建后,处于新建状态,在这种状态下,线程对象还只是一个对象,
- 没有成为一条独立执行的线索,也不可能被线程调度程序调度
- 准备状态(runnable)
- 处于新建状态下的线程被start()调用后进入准备状态,这个状态随时可能被线程调度程序调度,
- 获取CPU执行时间,线程一旦进入准备状态不能回到新建状态
- 运行状态(running)
- 一旦处于准备状态的线程获取CPU时间,就进入了执行状态,
- 线程随时可能被调度程序调度到准备状态,
- 于某种必要的条件或者由于运行特定的方法,可能会进入等待/阻塞状态
- 等待阻塞状态(blocked)
- 因为某种原因放弃了CPU使用权,暂时停止运行
- 直到线程进入就绪状态,才有机会转入运行状态
- 阻塞分为三种
- 等待阻塞
- 运行的线程执行wait()方法,JVM会把该线程放入等待池中
- 同步阻塞
- 运行线程在获取对象的同步锁时,若该同步锁被别的线程占用,JVM会把该线程放入锁池中
- 其他阻塞
- 运行线程执行了sheep()或者join()或者发生了I/O请求时JVM会将线程置为阻塞状态
- 当sheep()超时,join()等待线程终止或者超时,I/O处理完毕,线程重新进入就绪状态
- 死亡状态(dead):
- 当线程的run方法正确执行完毕或者由于发生了异常而终止了执行时,
- 线程就进入了死亡状态,进入死亡状态下的线程不能再被启动
线程优先级
- package com.itlwc;
- /*
- 设置优先级两种方法:
- 1~10 数值越大优先级越高
- Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY
- 优先级会继承的,父线程的优先级是多少,子线程的优先级就是多少
- */
- public class Test {
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(new Thread(){
- //重写了父类Thread中的run()
- public void run() {
- System.out.println("优先级低后执行");
- }
- });
- Thread t2 = new Thread(new Runnable(){
- //实现Runnable接口中的run()
- public void run() {
- System.out.println("优先级高先执行");
- }
- });
- // 设置优先级
- t1.setPriority(Thread.MIN_PRIORITY);
- t2.setPriority(Thread.NORM_PRIORITY);
- t1.start();
- t2.start();
- }
- }
- /*
- 打印结果:
- 优先级低后执行
- 优先级高先执行
- */
sheep()与wait()
- sleep()
- 方法签名
- public static void sheep(long millis)
- 在指定的毫秒数内让当前正在执行的线程休眠
- public static void sheep(long millis,int nanos)
- 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠
- 功能
- 让线程等待而停止执行,不释放对象的锁
- wait()
- 方法签名
- public final void wait()
- 导致当前的线程等待,直到其他线程调用此对象的notify()或notifyAll()
- 功能
- 让线程等待而停止执行,释放对象的锁
线程的让步
- 实际运行中有时需要当前运行线程让出CPU,让其他线程运行,这时就需要线程的让步
- 线程的让步包括两种方式
- 线程只是让出当前的CPU资源,具体让给谁不确定
- 线程将给指定的线程让步,指定的线程没有完成,其绝不恢复运行
- 使用yield()让步
- yield()可以让当前线程让出CPU,回到准备状态,yield()让步不一定成功,
- 有可能线程回到准备状态又立刻被调度再次进入运行状态
- public static void yield()
- 使用join()让步
- 当一个线程必须等待另一个线程执行完成之后才恢复执行时,才使用join()让步
- public final void join()
- 另一个执行完成之后这个线程才恢复运行,相当于合并一个线程
- public final void join(long millis)
- 另一个执行执行millis毫秒之后这个线程才恢复运行
- public final void join(long millis,int nanos)
- 另一个执行执行millis毫秒nanos纳秒之后这个线程才恢复运行
- 三个方法都是final的,继承Thread类时不能重写join(),都会抛出InterruptedException
守护线程
- Java根本没有单线程程序,即使只开发一个主线程,后台还有很多辅助线程,比如线程调度,内存管理等
- 这些在后台运行的线程称为守护线程
- 开发守护线程和普通线程区别不大,只要调用线程对象的setDaemon()进行设置即可
- public final void setDaemon(boolean on)
- 最终方法,继承Thread类时不能被重写
- 参数on == true,守护线程
- 参数on == false,前台普通线程
- 如果所有前台线程执行完毕,守护线程即使没有执行完毕,程序也退出
案例
- package com.itlwc;
- class MyRunnable1 implements Runnable {
- public void run() {
- for (int i = 0; i < 30; i++) {
- System.out.print("[" + i + "]");
- }
- System.out.print("前台用户线程执行完毕");
- }
- }
- class MyDaemon implements Runnable {
- public void run() {
- for (int i = 0; i < 100; i++) {
- System.out.print("<" + i + ">");
- }
- System.out.print("后台守护线程执行完毕");
- }
- }
- public class Test {
- public static void main(String[] args) {
- Runnable r1 = new MyRunnable1();
- Runnable r2 = new MyDaemon();
- Thread t1 = new Thread(r1);
- Thread t2 = new Thread(r2);
- t2.setDaemon(true);
- t1.start();
- t2.start();
- }
- }
线程的同步
- 多线程程序中,由于同时有多个线程并发运行,有时候带来严重问题,甚至引发错误
- 比如:一个银行帐号在同一时刻只能被一个用户操作,如果两个用户同时操作可能会产生错误
- 为了解决这些问题,在多线程开发中就需要使用同步技术
- 同步方法简介
- 同步方法是指用 synchronized 关键字修饰的方法
- 同步方法与普通方法不同的是,同步方法执行线程将获得同步方法所属对象的锁,
- 一旦对象被锁, 其他线程就不能执行,不能获得对象锁的线程进入对象的锁的等待池中
- 直到别的线程释放锁,其获得锁才能执行
- synchronized 只能修饰方法,不能修饰变量
- 若线程获得锁后进入睡眠或让步,则将锁也带着睡眠或让步,这种做法将严重影响等待锁的线程执行
- 同步降低多线程的并发性,在一定程度上影响性能.因此,在不需要同步的时候,一定不能同步
线程同步调度的方法
- public final void wait()
- 使得某一个线程进入该资源的等待池,直到别的线程调用该资源的notify()或notifyAll()
- public final void wait(long timeout)
- timeout表示毫秒
- public final void wait(long timeout,int nanos)
- timeout表示毫秒,nanos表示纳秒
- public final void notify()
- 唤醒等待池中某一个线程,具体唤醒哪个没有保障
- public final void notifyAll()
- 如果多个线程访问同一个资源,使用notifyAll()来唤醒全部等待线程,避免不必要的永远等待
线程的死锁
- 线程之间互相等待对方释放资源对象的锁,而每个线程又持有其他线程需要的锁,造成永久等待
- package com.itlwc;
- class MyThread extends Thread {
- // 需要访问的两个资源对象引用
- private Object resource1;
- private Object resource2;
- public MyThread() {
- }
- public MyThread(Object resource1, Object resource2, String name) {
- // 传递资源对象的引用
- this.resource1 = resource1;
- this.resource2 = resource2;
- // 设置线程的名称
- this.setName(name);
- }
- public void run() {
- // 获得资源o1的锁,对资源o1进行操作
- synchronized (resource1) {
- System.out.println(this.getName()
- + "线程拿到" + resource1 + "对象的锁");
- try {
- Thread.sleep(1000);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println(this.getName()
- + "等待" + resource1 + "对象的锁释放");
- // 在拿着资源o1锁的同时,需要拿到
- // 资源o2的缩对两个资源同时进行操作
- synchronized (resource2) {
- System.out.println(this.getName()
- + "线程拿到了"+ resource2.toString() + "的锁");
- System.out.println(this.getName()
- + "可以对两个资源同时操作了");
- }
- }
- }
- }
- // 主类
- public class Test {
- public static void main(String args[]) {
- // 创建3个资源对象
- String s1 = "tom";
- String s2 = "jerry";
- String s3 = "lucy";
- // 创建3个线程对象
- MyThread mt1 = new MyThread(s1, s2, "MT1");
- MyThread mt2 = new MyThread(s2, s3, "MT2");
- MyThread mt3 = new MyThread(s3, s1, "MT3");
- // 启动上述3个线程
- mt1.start();
- mt2.start();
- mt3.start();
- }
- }
- /*
- 打印结果:
- MT1线程拿到tom对象的锁
- MT3线程拿到lucy对象的锁
- MT2线程拿到jerry对象的锁
- MT1等待tom对象的锁释放
- MT2等待jerry对象的锁释放
- MT3等待lucy对象的锁释放
- */
同步语句块
- 同步方法在同一时刻只能锁住一个对象,但在并发执行中有时需要锁定的对象不止一个
- package com.itlwc;
- //自定义的线程类
- class MyThread extends Thread {
- // 该引用为资源对象
- private Object resource;
- public MyThread() {
- }
- public MyThread(Object resource, String name) {
- this.resource = resource;
- this.setName(name);
- }
- public void run() {
- synchronized (resource) {
- System.out.println(this.getName() + "线程访问了资源");
- try {
- Thread.sleep(1000);
- System.out.println(this.getName() + "线程带着锁睡觉去了");
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println(this.getName() + "线程带着锁睡醒后释放了锁");
- }
- }
- }
- // 主类
- public class Test {
- public static void main(String[] args) {
- // 创建资源对象
- Object resource = new Object();
- // 创建2个线程
- MyThread mt1 = new MyThread(resource, "MT1");
- MyThread mt2 = new MyThread(resource, "MT2");
- // 启动这2个线程
- mt1.start();
- mt2.start();
- }
- }
- /*
- 打印结果:
- MT1线程访问了资源
- MT1线程带着锁睡觉去了
- MT1线程带着锁睡醒后释放了锁
- MT2线程访问了资源
- MT2线程带着锁睡觉去了
- MT2线程带着锁睡醒后释放了锁
- */