程序、进程、线程概念

  • 程序是为了完成特定任务,用某种语言编写的一组指令的集合,是一段静态代码,静态对象
  • 进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程:有它自身的产生、存在和消亡的过程--生命周期
    • 如:运行中的QQ、运行中的MP3播放器
    • 程序是静态的,进程是动态的
    • 进程作为资源分配的单位,系统在运行时会为每一个进程分配不通的内存区域
  • 线程,进程可进一步细化为线程,是一个程序内部的一条执行路径
    • 若一个进程同一时间并行执行多个线程,就是支持多线程的
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
    • 一个进程中的多个线程共享相同的内存单元/内存地址空间-》他们从同一堆分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便、高效,单多个线程操作共享系统资源可能会带来安全隐患

单核CPU和多核CPU

  • 单核CPU,其实就是一种假的多线程,因为在一个时间单位内,也只能执行一个线程的任务,例如:有多车道,但是收费站只有一个工作人员在收费,只有收费才能通过,那么CPU就好比收费人员,因为CPU单位时间特别短,因此感觉不出来
  • 多核CPU,可以更好的发挥多线程的效率
  • 一个Java应用程序,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程,当然如果发生异常,会影响主线程

并行和并发

  • 并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
  • 并发:一个CPU(采用时间片)同时执行多个任务,比如:淘宝秒杀

使用多线程的有点优点

背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需要多线程呢

优点:

  • 提高应用程序的响应,对图形化界面更有意义,可增强用户体验
  • 提高计算机系统的CPU利用率
  • 改善程序结构,将长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

  • 程序需要执行两个或多个任务
  • 程序需要实现一些需要等待的任务,如用户输入,文件读写操作,网络操作,搜索等
  • 需要一些后台运行的程序

线程创建和使用

import sun.util.resources.ms.CalendarData_ms_MY;

/**
 * 多线程创建,方式一:继承Thread类
 * 1、创建一个继承于Thread类的子类
 * 2、重写Thread类中的run()
 * 3、创建Thread类的子类对象
 * 4、通过此对象去调用start()
 * 例子:遍历100以内
 */
public class ThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();

        for (int i = 0; i < 100; i++) {
            if(i%2 != 0){
                System.out.println(Thread.currentThread().getName()+":" + i +"*");
            }
        }

    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 ==0 ){
                System.out.print(Thread.currentThread().getName()+":" + i+"&");
            }
        }
    }
}

问题

  • 我们不能通过直接调用run()方法的方式启动线程
  • 再启动一个线程,遍历一个0-100,不可以,让已经start的线程去执行,会报以下的错

总结

要想跑多个线程,就需要创建多个对象

练习

package Thread;

/**
 * 练习:创建两个分线程,其中一个线程遍历100以内偶数,另一个线程遍历100以内奇数
 *
 */
public class ThreadDemo {
    public static void main(String[] args) {
        Even even = new Even();
        Odd odd = new Odd();

        even.start();
        odd.start();
    }

}

class Even extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 ==0){
                System.out.println(Thread.currentThread() + ":" + i +"$");
            }
        }
    }
}

class Odd extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 !=0){
                System.out.println(Thread.currentThread() + ":" + i +"$");
            }
        }
    }
}

只用一次对象,可以用匿名子类方式

package Thread;
/**
 * 创建窗口买票,总票数为100张
 * 使用Runnable接口方式实现
 */
class Window1 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:"+ ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

 

package Thread;

/**
 * 练习:创建两个分线程,其中一个线程遍历100以内偶数,另一个线程遍历100以内奇数
 *
 */
public class ThreadDemo {
    public static void main(String[] args) {
        //只用一次对象,可以用匿名子类方式
        new Even(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 ==0){
                        System.out.println(Thread.currentThread() + ":" + i +"$");
                    }
                }
            }
        }.start();

        new Odd(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 !=0){
                        System.out.println(Thread.currentThread() + ":" + i +"$");
                    }
                }
            }
        }.start();
    }

}

class Even extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 ==0){
                System.out.println(Thread.currentThread() + ":" + i +"$");
            }
        }
    }
}

class Odd extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 !=0){
                System.out.println(Thread.currentThread() + ":" + i +"$");
            }
        }
    }
}

Thread类方法

  • void start():启动线程,并执行对象的run方法
  • run():线程在被调用时执行的操作
  • String getName():返回现场的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread():返回当前线程,在Thread子类中就是this,通常用于主线程和Runable实现类
  • static void yield():线程让步
    • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    • 若队列中没有同优先级的线程,忽略此方法

  • join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止;在线程a中调用线程b的jion方法,此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
    • 低优先级的线程可以获得执行

  • static void sleep(long millis):指定时间:毫秒
    • 令当前活动线程在指定时间段内放弃对CPU的控制,使其他线程有机会被执行,时间到后重排队
    • 抛出InterruptedException异常

  • stop():强制线程生命周期结束,不推荐使用,过期
  • boolean isAlive():返回boolean,判断线程是否还活着

package Thread;

public class ThreadMethod {
    public static void main(String[] args) {
        HelloThread t1 = new HelloThread("Thread:1");

        //分线程命名
//        t1.setName("线程一");

        t1.start();
        //主线程命名
        Thread.currentThread().setName("主线程");

        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" +i);
            }
        }
    }
}

class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 ==0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    public HelloThread(String name){
        super(name);
    }

}

线程的创建和使用

  • 调度策略
    • 时间片
    • 抢占式:高优先级的线程抢占CPU
  • Java的调度方法
    • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
    • 对高优先级,使用优先调度的抢占式策略
  • 线程的优先级登记
    • MAX_PRIORITY:10
    • MIN_PRIORITY:1
    • NORM_PRIORITY:5
  • 涉及的方法
    • getPriority():返回线程优先值
    • setPriority(int newPriority):改变线程的优先级
  • 说明
    • 线程创建时继承父线程的优先级
    • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

例子:

package Thread;

/**
 * 创建窗口买票,总票数为100张
 存在线程安全问题
 */
class Window extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true){
            if (ticket>0){
                System.out.println(getName() + ": 卖票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}



public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

Runnable和Thread选择

开发中,优先选择Runnable接口方式,原因

  1. 实现的方式没有类的单继承性的局限性
  2. 实现的方式更适合来处理多个线程有共享数据的情况

联系

public class Thread implements Runnable

  1. 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明