JavaSE——基础知识回顾16之线程部分

系列文章目录

线程部分知识属于java进阶的前奏部分,将会说到一些关于程序之间的运行机制,和多个线程之间和底层JVM以及硬盘之间的交互关系,实用性比较强。



前言

你要偷偷地学习然后拖垮他们所有人~~


以下是本篇文章正文内容,下面案例可供参考

一、基础知识网络图

二、“进程”与“线程”

1、什么是“进程”

概念:生活中我们使用电脑的时候会与许多的程序打交道,然而程序本来都是禁止的,只有当它被启动后获取了电脑的CPU资源的时候运作起来过后才能被称为进程。
举例:我们可以把电脑的CPU(中央处理器)当做是一个工厂,而这个工厂中的各个部门的车间就是程序,当一个车间被运作起来后,这个车间就是一个进程,然而在这个车间中负责不同工作的工人就是该进程的个个线程。
说明: 单核CPU在任何时间点上只能运行一个进程;宏观并行,微观串行。解释:我们平时使用电脑打开程序的时候(例如:QQ,微信,迅雷)等,看起来我们使用这些程序的时候它们是一起运行的,实则不然,它们在CPU分配资源的同时会获取一个“时间片”的东西,在这个时间片中它们才是真正处于被激活中(即使用状态),时间片的单位非常小,是以纳秒为单位的,所以在每一次使用其它程序的时候切换的速度非常地快,从而给我们一种错觉感觉这些程序都是同时处于运行的状态中,这也就是“宏观下并行,微观下处于串行(以时间片为主要限制单位)”的意思

2、什么是“线程”

概念:线程,也称轻量级进程(Light Weight Process)。程序中的一个顺序控制流程,同时也是CPU的基本调度单位,进程是由多个线程组成的,彼此之间完成不同的工作,交替执行,因此称为多线程(几乎所有的进程内部都是多线程)
举例:我们使用迅雷下载器的时候,迅雷软件就是一个进程,而其中的多个下载任务就是多个线程。我们平时写Java的时候Java虚拟机就是一个进程,当众默认包含了主线程(Main),可以通过代码创建多个独立线程,与Main并发执行。

3、进程与线程的区别

进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
一个程序运行后至少有一个线程
一个进程可以包含多个线程,但是至少需要一个线程
进程间不能共享数据段地址,但同进程的线程之间可以。

三、线程的组成

1、线程的基本组成部分

1、CPU时间片:操作系统(OS)会为每个线程分配执行时间

2、运行数据:
堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

线程的逻辑代码,就下来的两种创建方式中会有所展示

2、两种方式创建线程

方式一、通过创建线程类(继承Thread关系下完成创建)

//创建线程类
//需要继承Thread 并重写其中的  run()方法。
public class Mythread extend Thread{
          public void run(){
          //编写线程功能完后曾五十次的打印
           for(int i=1;i<=50;i++){
            System.out.println("MyThread:"+i);
      }
   }
}
//编写测试类
public class Test01{
   public static void main(String [] args){
         //创建线程的子类对象
         Mythread t1=new Mythread();
         t1.start();//调用start方法,不要直接调用run()方法,虽然结果一样,但并非线程的内容
   }

}

方式二、通过实现Runnable接口并实现其中的run()方法

public class TestCreateThread{
    public static void main(String [] args){
    //创建线程任务类对象task
       MythreadTask task=new MythreadTask();
       Thread t2=new Thread(task);//将任务递给线程对象,注意Thread是java提供的一个类,可以直接进行实例化
       t2.start();//启动线程
       
    }
}
//创建线程任务类,并实现Runnable接口
class MythreadTask implements Runnable{
       //重写其中的run()方法
       public void run(){
           for(int i=1;i<=50;i++){
             System.out.println("MyRunnable"+i);
        }
   }
}

四、线程的状态

1、线程的生命周期

在这里插入图片描述

一个线程的生命周期一共需要经历四个大阶段:1、初始状态 2、就绪状态 3、运行状态4、中止状态
其中需要注意的就是 CPU中关于时间片的使用机制,它控制着线程的运行过程。

2、线程操作的常用方法

(1)休眠—sleep()

语法: public static void sleep(long millis)
注意:休眠的时间单位为毫秒,其间隔非常短
案例如下:

//任务需求,让线程休眠
/**
*知识点:让线程休眠
*需求:编写一个抽取学员的花名册,要求倒数三秒后输出被抽中的学生名字
*/
Random ran=ran.nextInt(names.length);
//名单如下
String [] names ={"赵信","盖伦","李青","嘉文","亚索","永恩","薇恩","卢锡安","赛娜"};
int index=ran.nextInt(names.length);
for(int i=3;i>=1;i--){
    System.out.println(i);
    Thread.sleep(1000);//使用休眠  sleep()  让线程暂停 1秒
}
System.out.println(names[index]);

(2)放弃(礼让)—yield()

语法:public static void yield()
讲解:当前县城主动放弃时间片,回到就绪状态,竞争下一次时间片。
案例如下:

/*
*知识点:线程礼让-yield()
*需求:创建两个线程A,B,分别各打印1-100的数字,其中B一个线程,每打印一次,就礼让一次,观察实验结果
*/
public class Test{
   public static void main(String[] args){
           A a = new A();
		B b = new B();
		
		a.start();
		b.start();
  }
}
class A extends Thread{
   @Override
   public void run(){
    for(int i=1;i<=100;i++){
      System.out.println("A:"+i);
     }
   }
}

class B extends Thread{
   @Override
   public void run(){
    for(int i=1;i<=100;i++){
      System.out.println("B:"+i);
      Thread.yield();//礼让:让当前线程对象退出CPU,成为就绪状态,但是并不代表线程A就一定会抢先一步
     }
   }
}

(3)结合—join()

语法:public final void join()
讲解:允许其他县城加入到当前线程中(多用于主线程与子线程的结合中)
案例如下:

//测试结核方法---join()
public class Test01 {
	
	public static void main(String[] args) throws InterruptedException {
		/**
		 * 知识点:线程的合并 - join
		 * 
		 * 需求:主线程和子线程各打印200次,从1开始每次增加1,
		 * 	         当主线程打印到10之后,让子线程先打印完再打印主线程
		 */		
		MyThread t = new MyThread();
		t.start();	
		for (int i = 1; i <= 100; i++) {
			System.out.println("主线程:" + i);
			if(i == 10){
				t.join();
			}
		}	
	}
}

class Mythread extends Thread{
        @Override
        public void run(){
        for(int i=1;i<=100;i++){
            System.out.println("子线程"+i);
        }    
    }
}

(4)线程中断—interrupt()

该方法 主要用于手动改变线程状态
案例如下:

public class Test01 {
	
	public static void main(String[] args) throws InterruptedException {
		/**
		 * 知识点:线程中断
		 * 
		 */
		MyThread t = new MyThread();
		t.start();
		Thread.sleep(3000);	
		t.interrupt();//改变线程状态
	}	
}
 class MyThread extends Thread {
	@Override
	public void run() {		
		//isInterrupted() - 获取当前线程状态(true-中断 false-存活)
		while(!Thread.currentThread().isInterrupted()){		
	     	System.out.println("111");
			System.out.println("222");
			System.out.println("333");
			System.out.println("444");
		}
	}
}

(5)守护线程

解释:守护线程一般是java提供的,例如JVM会在每次程序运行完毕后进行垃圾回收机制,而这个垃圾回收便是守护线程之一
案例如下:

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		/**
		 * 知识点:后台线程/守护线程
		 */
		GuardThread guardThread = new GuardThread();
		guardThread.setDaemon(true);//将此线程设置为后台线程
		guardThread.start();
		for (int i = 1; i <=5; i++) {
			System.out.println("前台线程:" + i);
			Thread.sleep(1000);
		}	
	}
}
 class GuardThread extends Thread{
	@Override
	public void run() {
		while(true){
			System.out.println("守护线程默默守护着前台线程...");
			try {
				//父类run方法没有抛异常,子类重写后也不能抛异常
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}
		}
	}
}

五、线程的安全

1、线程不安全的原因:

1、当多线程并发访问临界资源时,如果破坏原子操作,可能胡照成数据不一致(线程之间互相争夺资源造成)
2、临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
3、原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省

2、线程同步(synchronized)俗称“线程锁”

线程同步方法一:

/*语法: 
    synchronized(临界资源对象){//对临界资源对象枷锁
    代码(原子操作)
    }
*/

注意:
1、每个对象都有一个互斥锁标记,用来分配给线程的
2、只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
3、线程退出同步代码块时,回什邡相应的互斥锁标记

线程同步方法二

/**
  synchronized 返回值类型 方法名称(形参列表){
    //当前对象(this)加锁
    //代码(原子操作)
}
*/

注意:
1、只有拥有对象互斥所标记的线程,才能进入到该对象加锁的同步方法中
2、线程退出同步方法时,回什邡相应的互斥锁标记

使用synchonized注意事项:

避免锁的嵌套使用,不然非常容易出现“死锁”情况,容易影响用户体验度

3、线程通信

(1)等待(wait)

public final void wait();
public final void wait(long timeout)
必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,这个线程会释放其拥有的所有锁标记,同时此线程阻塞在线程的等待队列中,进入等待队列。

(2)唤醒(notify)

public final void notify()
public final void notifyAll();
必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个货全部线程,对自身没有任何影响

六、线程池(高级多线程部分)

(1)线程池的概述:

线程池出现原因:线程是一个宝贵的内存资源、单个线程约占1MB的空间,过多分配容易照成内存溢出,其次频繁的创建线程以及销毁线程会增加虚拟机的回收效率、资源开销,照成程序性能下降。
线程池:
1、线程容器,可设定线程分配的数量上线
2、将预先创建爱你的线程对象存入池中,并重用线程池中的线程对象
避免频繁的创建和销毁

(2)线程池的原理

在这里插入图片描述

原理:将任务提交给线程池,有线程池分配线程、运行该人物,并在当前任务结束后复用线程

(3)获取线程池

常用的线程池接口和类(java.util.concurrent)
Executor:线程池的顶级接口
ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码
Executors 工厂类:通过此类可以获得一个线程池
重点:通过 new FixedThreadPool(int 线程池数量)获取固定数量的线程池
通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限

(4)常用的三大线程池(案例展示)

1、创建单个线程的线程池
ExecutorService pool= Executors.newSingleThreadExecutor();
风险:无界任务队列,可以无限添加任务,任务多起来就会崩溃

2、创建指定数量的线程的线程池
ExecutorService pool =Executors.newFixedThreadPool(3);
风险:无界任务队列,可以无限添加任务,任务多起来就会崩溃,容易造成超出内存控件

3、创建带有缓冲区的线程池
ExecutorService pool=Executors.newCachedThreadPool();
最大线程数:Integer.MAX_VALUE,-- 最大线程数量
风险:直接提交队列(同步队列):没有容量队列


import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test01 {
    public static void main(String[] args) {
        /**
         * 知识点:线程池
         */
        //1、创建单个线程的线程池
        //ExecutorService pool= Executors.newSingleThreadExecutor();

        //2、创建指定线程个数的线程池,此处创建了三个线程的线程池
       // ExecutorService  pool =Executors.newFixedThreadPool(3);
        //3、创建带有缓冲区的线程池
        ExecutorService pool=Executors.newCachedThreadPool();


        for (int i=1;i<=10;i++){
            Task task=new Task(i);
            //将任务提交给线程池
            //pool/execute(task);//无返回值的提交任务的方法
            pool.execute(task);
        }
        //关闭线程池
        pool.shutdown();
    }
}
class Task implements Runnable{
    private  int i;

    public Task(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println(Thread.class.getName()+"----》"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

七、线程安全的高级拓展

1、Callable接口

解释:JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
Callable具有泛型返回值、可以声明异常

2、语法

public interface Callable<V>{
     public V call() throws Exception;
}

案例如下:


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test01 {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		/**
		 * 1.计算任务,一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出计算的结果。
		 * 
		 * 需求:使用线程池去处理
		 */
		//创建数组
		int[] is = new int[20000];
		//初始化数组中的数据
		for (int i = 0; i < is.length; i++) {
			is[i] = i+1;
		}	
		//创建线程池
		ExecutorService pool = Executors.newFixedThreadPool(4);	
		//提交任务
		Future<Integer> f1 = pool.submit(new Task(is, 0, 5000));
		Future<Integer> f2 = pool.submit(new Task(is, 5000, 10000));
		Future<Integer> f3 = pool.submit(new Task(is, 10000, 15000));
		Future<Integer> f4 = pool.submit(new Task(is, 15000, 20000));	
		//获取任务返回值并合并
		System.out.println(f1.get() + f2.get() + f3.get() + f4.get());	
		//关闭线程池
		pool.shutdown();
	}
}
//带有返回值的任务类
class Task implements Callable<Integer>{
	private int[] is;
	private int startIndex;
	private int endIndex;	
	public Task(int[] is, int startIndex, int endIndex) {
		this.is = is;
		this.startIndex = startIndex;
		this.endIndex = endIndex;
	}
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = startIndex; i < endIndex; i++) {
			sum += is[i];
		}
		return sum;
	}	
}

2、重入锁

ReentrantLock: Lock 接口的实现类,与synchronized一样具有互斥锁功能,甚至其使用度要高一些

案例如下:

public class MyList{
 //创建重入锁对象
  private Lock locker =new ReentrantLock();
  private String[] strs={"赵信","盖伦","嘉文","李青","泰隆"};
  private int count=2;//元素个数
  //添加元素
  public void add(String value){
      //显示开启锁
      locker.lock();
      try{
        strs[count]=value;
        try{
          Thread.sleep(1000);//主动休眠一秒
      }catch(InterruptedException e){
      }coubt ++; 
}finally{
  locker.unlock();//释放锁
    }
  }
}

总结

有关线程部分的内容比较多,难度也比较大,尤其是后面的高级线程部分,线程池的操作,需要花费很多时间来处理(拖更理由)。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sugar-free->小粽子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值