java——多线程

本文详细介绍了程序、进程和线程的概念,强调了进程的动态性和线程作为调度单位的优势。讲解了单核与多核CPU的区别以及并行与并发的含义。通过实例展示了Java中创建多线程的两种方式,并对比了它们的优缺点。最后讨论了多线程在提高响应速度、CPU利用率和改善程序结构方面的优势,并通过卖票程序实例展示了线程安全问题。
摘要由CSDN通过智能技术生成

1. 程序 进程 线程

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

2. 单核 多核 并行 并发

单核 CPU 和多核 CPU 的理解
单核 CPU ,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU 就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好钱,再去收费)。但是因为CPU 时 间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个 Java 应用程序 java.exe ,其实至少有三个线程: main() 主线程, gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
 
并行与并发
并行: 多个 CPU 同时执行多个任务。比如:多个人同时做不同的事。
并发: 一个 CPU( 采用时间片 ) 同时执行多个任务。比如:秒杀、多个人做同一件事。
 
 
使用多线程的优点
背景: 以单核 CPU 为例,只使用单个线程先后完成多个任务,肯定比用多个线程来完成用的时间更短,因为单核CPU在开多个线程的时候,为了使这两个线程同时工作,CPU需要很快地不停地切换所处理的线程(单核CPU也可以开多线程),切换也需要时间。但是如果是多核CPU就可以不同的核处理不同的线程。
 
为何仍需多线程呢?
多线程程序的优点:
1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2. 提高计算机系统 CPU 的利用率
3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
 
何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,比如说你在看微博的时候,当网络比较慢,下载图片和下载文字如果是同一个线程,那么必须等图片加载完了才可以去加载后面的文字,但是如果是不同的线程加载,就会先加载出存储比较小的文字,图片慢慢加载
需要一些后台运行的程序时
 
 

3. Thread类:创建多线程

Java 语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来体现。
 
Thread类的特性
每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的,经常
run() 方法的主体称为 线程体
通过该 Thread 对象的 start() 方法来 启动这个线程 ,而非直接调用 run()
  • Thread类实现了Runnable()接口

方法一:用继承Thread类的方式

package thread;
/* 多线程的创建
* 方式一:继承与Thread类
*
* 1.创建一个继承于Thread类的子类
* 2.重写Thread类的run()方法,将此线程要执行的方法写在run的方法体中
* 3.创建Thread的子类的对象
* 4.通过子类调用start()方法
*/

class thread extends Thread{

    @Override
    public void run() {
        for(int i=0;i<=10;i++){
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}

class threadTest{
    public static void main(String[] args){
        //main方法内就是一个线程(主线程)

        thread t1 = new thread(); //注意这一条语句仍然是在主线程中执行的
        t1.start(); //从这里开始,又多了一个新的线程,也就是说这个线程执行run方法体的代码,与main()中下面的for循环同时执行。

        for(int i=0;i<=10;i++){
            if(i%2!=0){
                System.out.println(i+"主线程");
            }
        }

    }
}




从程序结果可以看出来,两个线程同时执行。

注意:
  • 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException,需要重新造一个对象来调用start方法。

 

方法二: 实现 Runnable 接口的方式
 
1) 定义子类,实现 Runnable 接口。
2) 子类中重写 Runnable 接口中的 run 方法。
3) 通过 Thread 类含参构造器创建线程对象。
4) Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造器中。
5) 调用 Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法。
 
package thread;

class myThread3 implements Runnable{

    //实现类myThread3实现接口Runnable。(通过重写run方法)
    @Override
    public void run() {
        for(int i=0;i<=10;i++){
            System.out.println(Thread.currentThread().getName()+"**"+i);
        }
    }
}

public class thread2{
    public static void main(String[] args){

        //创建实现类myThread3的对象T
        myThread3 T = new myThread3();

        //将T作为参数传递到Thread的构造器中创建Thread的对象
        Thread t1 = new Thread(T);
        t1.setName("线程一");
        t1.start();

        Thread t2 = new Thread(T);
        t2.setName("线程二");
        t2.start();
    }
}

注意:

  • 上面程序里Thread t1 = new Thread(T); 和Thread t2 = new Thread(T);  传入的实现类对象T是同一个
  • Runnable接口只有一个抽象方法:run()。
  • Thread类定义了一个含参构造器,Thread(Runnable target)。
  • Thread t1 = new Thread(T); 实际传入的是Runnable的实现类,体现了多态性。
  • 然后再调用t1.start()时候,run方法里的线程就开始跑了。

 

4. 例题:

  • 分别利用两种多线程的方式,实现三个窗口同时卖票

1.继承方式

package thread;

class window extends Thread{
    private static int ticket = 100;

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

public class test{
    public static void main(String[] args){
        window w1 = new window();
        window w2 = new window();
        window w3 = new window();
        w1.start();
        w2.start();
        w3.start();
    }
}
注意这里的ticket要定义成static才能让三个线程同时操作同一个ticket。
 
 
2.实现接口的方式
 
package thread;

class window implements Runnable{

    private int ticket = 10;

    @Override
    public void run() {

        while (true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"票号为:"+ticket);
                ticket--;
            }else {
                System.out.println("没票啦");
                break;
            }
        }
    }


}

public class test{
    public static void main(String[] args){
        window T = new window();
        Thread t1 = new Thread(T);
        Thread t2 = new Thread(T);
        t1.start();
        t2.start();
    }
}

注意这里的ticket就不是static,因为多个线程在创建时都是传入的window的T对象,各个线程共享window的T对象中的数据。

第二个卖票程序的输出: 注意这里有两个窗口都卖了第10个票,出现了线程安全问题。线程安全以后再说

 

 

5. 比较两种创建多线程的方式

* 比较创建线程的两种方式

* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没有类的单继承性的局限性,如果用继承的方式,window继承Thread后就不能有其他的父类了。
*      2. 实现的方式更适合来处理多个线程有共享数据的情况。

* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值