Java线程基础

一、线程的基本概念

1.什么是线程和进程

  • 进程:进程是具有一定独立功的程序(例如:QQ.exe),关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
  • 线程:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位;线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
  • 两者关系
  • 1.一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
  • 2.相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
  • 3.总结:一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈空间。
  • 4.区别:地址空间和资源拥有。
    • a、线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
    • b、每个进程都有自己一套独立的资源(数据),供其内的所有线程共享。
    • c、不论是大小,线程开销更“轻量级”。
    • d、一个进程内的线程通信比进程之间的通信更快速有效(因为共享变量)。
  • 作用:在串行程序基础上引入现场和进程是为了提高程序的并发度;从而提高程序运行效率和响应时间。

2.线程的特点

  1. 线程是轻量级的进程
  2. 线程没有独立的地址空间(内存空间)
  3. 线程是由进程创建的(寄生在进程)
  4. 一个进程可以拥有多个线程(多线程编程)

3.线程的状态

1.线程的5种状态:a、新建状态(New)b、就绪状态(Runnable)c、运行状态(Running)d、阻塞状态(Blocked)e、死亡状态(Dead)
2.状态转换图具体详情
Java线程状态转换图

4.并发与并行

并发:两个或多个事件在在同一时间段内发生。(交替执行)
并行:两个或多个事件在同一时刻发生(同时发生),CPU多个线程执行

5.线程调度

分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
抢占式调度:优先级高的线程先使用CPU,如果优先级相同则随机选择一个

6.创建线程的三种方式

1. 继承Thread类
步骤
a、定义类继承Thread类,并重写Thread类的run()方法,run()方法称为线程的执行体(方法体就是线程要完成的任务)
b、创建该类的实例对象,即创建线程对象
c、调用线程对象的start()方法来启动线程

 /**
 * 通过继承Thread实现线程
 */
public class ThreadTest extends Thread{
  
  private int i = 0 ;

    @Override
    public void run() {
        for(;i<50;i++){
            System.out.println(Thread.currentThread().getName() + " is running " + i );
        }
    }

    public static void main(String[] args) {
        for(int j=0;j<50;j++){if(j=20){
                new ThreadTest().start() ;
                new ThreadTest().start() ;
            }
        }
    }
}

2. 实现Runnable类
步骤
a、定义类实现Runnable接口
b、创建该类的实例对象Object
c、将Object作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象
d、调用线程对象的start()方法启动该线程

 public class ImpRunnable implements Runnable {
	
	private int i;
	
	@Override
	public  void run() {
		for(;i < 50;i++) {	
			//当线程类实现Runnable接口时,要获取当前线程对象只有通过Thread.currentThread()获取
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
	}
 
	public static void main(String[] args) {
		for(int j = 0;j < 30;j++) {
			System.out.println(Thread.currentThread().getName() + " " + j);
			if(j == 10) {
				ImpRunnable thread_target = new ImpRunnable();
				//通过new Thread(target,name)的方式创建线程
				new Thread(thread_target,"线程1").start();
				new Thread(thread_target,"线程2").start();
			}
		}
	}
}

代码相关:
1、实现Runnable接口的类的实例对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅仅作为线程执行体,而实际的线程对象依然是Thread实例,这里的Thread实例负责执行其target的run()方法
2、通过实现Runnable接口来实现多线程时,要获取当前线程对象只能通过Thread.currentThread()方法,而不能通过this关键字获取;
3、从JAVA8开始,Runnable接口使用了**@FunctionlInterface修饰**,也就是说Runnable接口是函数式接口可使用lambda表达式创建对象,使用lambda表达式就可以不像上述代码一样还要创建一个实现Runnable接口的类,然后再创建类的实例。

3. 实现Callable和Future接口
干货
(1)Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,体现在:a、call()方法可以有返回值;b、call()方法可以声明抛出异常
(2)Callable对象不能作为Thread的target的原因:
1.Callable接口是Java5新增的接口,而且它不是Runnable即可的子接口,所以Callable对象不能直接作为Thread的target。
2.call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的,所以这里涉及call()方法返回值的问题。
(3)JAVA5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。

步骤
a、创建Callable接口实现类,并实现call()方法,该方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例;
b、使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
c、使用FutureTask对象作为Thread对象的target创建并启动新线程;
d、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

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

public class CallableTest {
    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest() ;
        //因为Callable接口是函数式接口,可以使用Lambda表达式
        //call()方法的返回值类型与创建FutureTask对象时<>里的类型一致。
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
           int i = 0 ;
           for(;i<100;i++){
               System.out.println(Thread.currentThread().getName() + "的循环变量i的值 :" + i);
           }
           return i;
        });
       for(int i=0;i<100;i++){
           System.out.println(Thread.currentThread().getName()+" 的循环变量i : + i");
           if(i==20){
               new Thread(task,"有返回值的线程").start();
           }
       }
       try{
           System.out.println("子线程返回值 : " + task.get());
        }catch (Exception e){
           e.printStackTrace();
        }
    }
}
public class CallTest implements Callable {
    @Override
    public Object call() throws Exception {
        return "hello world";
    }
 
    public static void main(String[] args){
        FutureTask<String> futureTask = new FutureTask<String>(new CallTest());
        new Thread(futureTask).start();
        try {
            String result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

7.三种创建方式优缺点对比

通过继承Thread类实现多线程:
优点:1.实现简单,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程
缺点:1.线程类已经继承Thread类,不能再继承其他类(单继承原则)
2.多个线程不能共享同一份资源(如共享类的成员变量,多个线程访问同一资源时,如果资源没有加锁,那么会出现线程安全问题(这是线程同步的知识,这里不展开))
通过实现Runnable接口或者Callable接口实现多线程:
优点:1.线程类只实现了接口,还可以继承其他类
2.多个线程可以使用同一target对象,适合多个线程处理同一份资源的情况
缺点:1.较第一类方式,编程较复杂
2.要访问当前线程,必须调用Thread.currentThread()方法
综上:一般采用第二类方式实现多线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值