浅谈java多线程
说起线程就不免提起进程
什么是进程(Process)
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
参考文档 百度百科
简单来说进程就是一个正在运行的应用程序,比如qq、微信、eclipse…等等,电脑可以在任务管理器里查看系统当前运行的的进程。进程是系统进行资源分配和调度的基本单位,操作系统会为进程分配资源。进程是线程的容器,一个进程一般包含多个线程。
什么是线程(Thread)
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程是独立调度和分派的基本单位。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
参考文档 百度百科
提取以上信息:
1.线程是操作系统能够进行运算调度的最小单位
2.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程
3.线程是独立调度和分派的基本单位
4.线程共享进程的全部资源
5.一个进程可以有很多线程
以我的理解,简单来说,线程是一条流程,它共享了进程的资源,可以为我们完成一些任务。进程是拥有系统资源的程序实体,可以比喻成一个正在运作的工厂,里面的生产线就是线程。工厂里面可以包含多条生产线,可以同时运行,生产不同的东西。
这也引入了多线程的概念
多线程:从软件或者硬件上实现多个线程并发执行的技术。在单个程序中同时运行多个线程完成不同的工作。
多线程可以更好地利用 CPU 资源,提高程序完成任务的效率。像做一道番茄炒鸡蛋,单一线程要先洗番茄->洗锅->捣蛋->…串行执行。如果是多线程可以同时一边洗番茄一边洗锅一般捣蛋,大大提升了时间效率。
又比如售卖1000张火车票,多线程就相当于多个窗口同时售卖。。
进程和线程的关系及异同
- 进程是系统进行资源分配和调度的基本单位
线程是独立调度和分派的基本单位 - 进程拥有资源
而线程共享进程的资源 - 进程是程序的实体
线程是进程的实体 - 一个操作系统里有多个进程,一个进程可以又多个线程
- 进程和线程都有三个基本状态就绪状态、运行状态、阻塞状态
java多线程编程
java是支持多线程的编程语言,java创建的线程会映射到操作系统的内核中,其实即使我们不显式创建线程,在运行main方法时JVM会自动创建的一些线程,gc相关的进程(java垃圾回收)还有一些其他处理线程等.
java创建线程的方法主要有两种
- 定义一个类继承 Thread 类并重写它的run()方法,然后用这个子类来创建对象并调用start()方法。
- 定义一个类并实现 Runnable 接口,实现run()方法。
方法一
继承Thread类,实现run方法.
package com.smallchili.demo;
public class CreateThread {
public static void main(String[] args) {
ThreadDemo1 t1 = new ThreadDemo1();
t1.start();//启动线程
}
}
//继承Thread类
class ThreadDemo1 extends Thread{
@Override
public void run() {
System.out.println("我是ThreadDemo1,我被创建了");
}
}
方法二
定义一个类并实现 Runnable 接口,实现run()方法
package com.smallchili.demo;
public class CreateThread {
public static void main(String[] args) {
ThreadDemo2 t = new ThreadDemo2();
Thread t2 = new Thread(t);
t2.start();//启动线程2
}
}
//Runnable是接口
class ThreadDemo2 implements Runnable{
@Override
public void run() {
System.out.println("我是ThreadDemo2,我被创建了");
}
}
关于java线程的状态有的说五种有的说六种,我理解的线程跟进程是相似的。
线程的生命周期大概这样,从线程被new创建处于新建状态,调用start()方法是启动线程,线程进入就绪状态等待cpu的调用,一旦获取到cpu的资源就会执行run()方法里的内容,变成运行状态 ,到run()方法执行完成线程便进入消亡状态.如果调用sleep()或wait()方法会进入阻塞状态,直到sleep时间到了重新回到就绪状态等待cpu的再次调用。
引用一下某某大佬的六种说法,侵删
线程的声明周期共有 6 种状态,分别是:新建New、运行(可运行)Runnable、阻塞Blocked、计时等待Timed Waiting、等待Waiting和终止Terminate。
当你声明一个线程对象时,线程处于新建状态,系统不会为它分配资源,它只是一个空的线程对象。
调用start()方法时,线程就成为了可运行状态,至于是否是运行状态,则要看系统的调度了。
调用了sleep()方法、调用wait()方法和 IO 阻塞时,线程处于等待、计时等待或阻塞状态。
当run()方法执行结束后,线程也就终止了。
意思都差不多,六种是根据Thread类源码里定义的的六种状态来定的。五种是以典型的线程状态来定,只能说是阐述的粒度不一样。
两种实现方式的区别
上面直接继承Thread和实现Runnable接口都两种方式都可以实现创建一个线程,他们有什么区别呢。
1.因为java是单继承的,如果一个类想拥有线程功能同时继承某个类,第一种方法就无法实现,必须要用Runnable的方式。
以下代码实例说明:
package com.smallchili.demo;
public class CreateThread {
public static void main(String[] args) {
ThreadDemo1 t1 = new ThreadDemo1();//创建线程1实例
ThreadDemo2 t = new ThreadDemo2();
Thread t2 = new Thread(t);//创建线程2实例
t1.start();//启动线程1
t2.start();//启动线程2
}
}
//创建线程方法一: 因为继承了Thread,无法再继承其他类
class ThreadDemo1 extends Thread{
@Override
public void run() {
System.out.println("我是ThreadDemo1,我被创建了");
}
}
//继承了其他类并且实现了多线程
class ThreadDemo2 implements Runnable extends Person{
@Override
public void run() {
System.out.println("我是ThreadDemo2,我被创建了");
}
//person父类
class Person{
}
}
2.实现Runnable接口的创建方式,代码可以被多个线程(Thread)共享,方便实现多个线程处理同一资源。
以下用Runnable方式模拟三个窗口同时售票
package com.smallchili.demo;
public class CreateThread {
public static void main(String[] args) {
ThreadDemo2 t = new ThreadDemo2();
Thread t1 = new Thread(t,"窗口1");//创建线程1实例
Thread t2 = new Thread(t,"窗口2");//创建线程2实例
Thread t3 = new Thread(t,"窗口3");//创建线程3实例
t1.start();//启动线程2
t2.start();//启动线程2
t3.start();//启动线程3
}
}
class ThreadDemo2 implements Runnable{
private int tickets = 10;
@Override
public void run() {
while(tickets > 0){
tickets--;
//Thread.currentThread().getName()获取当前线程名字
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数="+tickets);
}
}
}
补充:上面代码其实是可能出问题的,线程共享资源是不安全的,如果不去做限制,多个线程同时去使用资源往往会出差错,比如票数不一致,多买或者少买都有可能。