初识线程--概念、线程状态和创建线程的方式

无论用哪种方式创建线程,线程的启动一律使用Thread.start(),run()不能由用户直接调用,而是由JVM调用,run()如果由用户调用,则和调用普通方法没有区别。start()->start0()[本地方法]->进入到JVM中调用操作系统写好的方法来执行。

一个线程的start()只能调用一次,否则会抛出IllegalThreadStartException异常。

高并发:访问线程量非常高(CPU占用率达99.99%)

JVM的启动是多线程吗?是,因为JVM启动时,至少启动了垃圾回收线程和主方法线程,所以是多线程的。

一、进程和线程的概念

1.进程

进程:操作系统中一个程序的执行周期,是资源分配的最小单位。

操作系统是一组能有效地组织和管理计算机软硬件资源,合理地对各类任务进行调度 ,以及方便用户使用的程序的集合。

操作系统中是这样解释进程的:进程指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。

DOS系统:单进程。
Windows系统:多进程,可以同时执行多个进程。

进程在哪?每当使用java命令去解释程序的时候,就表示启动了一个新的JVM进程。

线程的例子:当在QQ中聊天时,发送消息和接收消息都是同步的。

2.线程

线程:一个程序同时执行多个任务。每一个任务称为一个线程,进程中的一个子任务,任务分配的基本单位。

3.进程和线程的区别与关系

没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。

每个进程拥有自己的一整套变量,而线程则共享数据。多进程中,进程间没有任何关系,两个进程通信用到网络中的TCP/IP协议。多线程间可以共享数据,使得线程之间的通信比进程之间更有效,更方便。

1)调度的基本单位

在传统的操作系统中,进程是作为独立调度和分派的基本单位,因此进程是能够独立运行的基本单位。在引入线程的操作系统中,已经把线程作为调度和分派的基本单位,因此线程是能独立运行的基本单位。

2)并发性

在引入线程的操作系统中,不仅进程间可以并发执行,而且在一个进程中的多个线程间也可以并发执行,甚至一个进程中的所有线程都可以并发执行。从而更加有效地提高资源利用率和系统吞吐量。

3)拥有资源

进程可以拥有资源并作为系统中拥有资源的一个基本单位。然而,线程本身并不拥有系统资源,而是仅有一点必不可少的、能够支持其独立运行的资源。

4)独立性

在同一进程中的不同线程之间显然比不同进程之间的独立性低得多。

5)系统开销

在创建或撤销进程时,操作系统为此付出的开销明显大于线程创建或撤销时所付出的开销。

6)支持多处理机系统

在多处理机系统中,对于传统的进程,不管有多少处理机,该进程只能运行在一个处理机上。但对于多线进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行,加快了进程的完成。

主要区别:

创建或撤销的消耗:创建或销毁一个进程要比创建或销毁一个线程消耗得多。

通信的方便性:线程间通信要比进程间通信方便得多。

5.高并发

含义:访问的线程量非常高。(多用户访问)

高并发引起的问题:服务器内存不够用,程序资源竞争,无法处理新的请求。

二、线程的状态

线程的状态共有:创建、就绪、运行、阻塞、终止。(或者新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Dead))

系统调用通过控制CPU分配时间片来运行线程。

导致阻塞的事件如:没有资源。在阻塞状态中,操作系统收回CPU,执行其它工作。

(1)新建状态:用new语句创建的线程对象处于新建状态(New),此时它和其它对象一样,仅仅在堆区中分配了内存。
(2)就绪状态:当一个线程对象创建好后,其它线程调用该线程对象的start(),该线程就进入了就绪状态(Runnable)
(3)运行状态:处于运行状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只有一个线程处于运行状态,如果有多个CPU,那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有处于就绪状态的线程才有机会转到运行状态。
(4)阻塞状态:线程出于某些原因放弃CPU,暂时停止运行。
    出现阻塞的情况大体分为如下5种:
    1. 线程调用 sleep方法,主动放弃占用的处理器资源。
    2. 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
    3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
    4. 线程等待某个通知。
    5. 程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。
(5)死亡状态:当线程退出run()后,就进入死亡状态(Dead),该线程结束生命周期。线程有可能是正常执行完run()退出,也有可能是遇到异常退出的。

三、线程创建的方式

方式一、继承Thread类,覆写run().
方式二、通过实例化Runnable接口/Callable接口的类的对象,覆写run().或者call()
    具体:2.1 无返回值:1)先创建实现Runnable接口的类(类中覆写run())。
                                       2) 然后创建该类对象,通过传对象给Thread对象的构造方法,用Thread对象来调用start()。
               2.2 有返回值:1)创建实现Callable接口的类Mythread,覆写无参的call(),
                                       2)在主函数中用FutureTask<> task=new FutureTask<>(Mythread对象)

                                       3)再用new Thread(task).start()启动线程,用task.get()接收返回结果。
说明:1.多线程的启动永远都是Thread类的start()方法。
          2.此时的Runnable对象可以采用匿名内部类的方法或者lambda表达式定义。

方式三、线程池

具体方法请见线程池

调用run()还是start()?

调用run()不会启动线程,只是方法的普通调用,与多线程无关。而调用start(),启动多线程,最终仍然会执行run(),这个方法只会执行一次,但是每个线程的执行时间不一定,体现了多线程的特点。因此应该调用start()。

调用run():

package com.xunpu.a;

/**
 * 线程创建
 * 方式一:直接继承Thread类,覆写该类中的run()。
 */
class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title=title;
    }
    @Override
    public void run(){
        for(int i=0;i<3;i++){
            System.out.println(this.title+",i="+i);
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread myThread1=new MyThread("thread1");
        MyThread myThread2=new MyThread("thread2");
        MyThread myThread3=new MyThread("thread3");
        myThread1.run();
        myThread2.run();
        myThread3.run();
    }
}

调用start():

package com.xunpu.a;

/**
 * 线程创建
 * 方式一:直接继承Thread类,覆写该类中的run()。
 */
class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title=title;
    }
    @Override
    public void run(){
        for(int i=0;i<3;i++){
            System.out.println(this.title+",i="+i);
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread myThread1=new MyThread("thread1");
        MyThread myThread2=new MyThread("thread2");
        MyThread myThread3=new MyThread("thread3");
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

每次执行的结果都可能不同:

方式二:定义一个实现Runnable接口(克服了单继承的缺陷)的类,实例化该类,通过Thread类的构造方法实现创建。

package com.xunpu.a;

/**
 * 创建线程方式二:
 * 定义一个实现Runnable接口(克服了单继承的缺陷)的类,实例化该类,通过Thread类的构造方法实现创建。
 */
class Mythread implements Runnable{
    private String title;
    public Mythread(String title){
        this.title=title;
    }

    @Override
    public void run() {
        for(int i=0;i<3;i++){
            System.out.println(this.title+",i="+i);
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Mythread mythread1=new Mythread("thread1");
        Mythread mythread2=new Mythread("thread2");
        Mythread mythread3=new Mythread("thread3");
        new Thread(mythread1).start();
        new Thread(mythread2).start();
        new Thread(mythread3).start();
    }
}

上面的几种方式都是没有返回值的写法,下面是有返回值的创建线程方法。

具体做法:实现Callable接口,覆写无参的call(),传对象给FutureTask<>对象,再传FutureTask对象给Thread对象,调用start(),用task.get()接收返回结果。

package com.xunpu.a;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 继承Callable类,有返回值。
 */
class Mythread3 implements Callable<String> {
    private int ticket=5;
    //覆写无参的call方法
    public String call(){
        while(this.ticket>0){
            System.out.println("剩余票数:"+this.ticket--);
        }

        return "票卖完了";
    }
}
public class TestThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Mythread3 mythread=new Mythread3();
        FutureTask<String> task=new FutureTask<String>(mythread);
        new Thread(task).start();
        new Thread(task).start();
        System.out.println(task.get());
    }
}

四、和线程有关的方法

1.public Thread(Runnable runnable,String name)--构造方法,创建线程的时候设置名称。
2.public final synchronized void setName(String name)--设置线程名称
3.public final String getName()--取得线程名称

说明:1)如果线程没有设置名称,那么系统会自动分配一个名字,为Thread-i(i从0开始)。
           2)主线程的名称为:main
           3) 线程名字如果要设置应避免重复,同时中间不能修改。

package com.xunpu.a;

/**
 * 和线程有关方法的使用
 */
public class Demo3{
    public static void main(String[] args) {
        Mythread2 mt=new Mythread2();
        Thread thread=new Thread(mt,"ThreadA");//用构造方法设置线程名称。
        thread.start();

    }
}
class Mythread2 implements Runnable {
    public void run() {
        Thread.currentThread().setName("ThreadB");//通过setName()修改最终的线程名。
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);//使用Thread.currentThread().getName()获取线程名
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值