【Java】多线程学习笔记


进程:资源分配的最小单位,进程在运行过程中都是相互独立,但是线程之间运行可以相互影响。

线程:CPU调度的最小单位,一个进程包含多个线程且它们共享同一进程内的系统资源
进程间通过管道、 共享内存、信号量机制、消息队列通信

在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口。


一、继承Thread类实现多线程

import org.junit.Test;



class Thread1 extends Thread {
    private String name;

    public Thread1(String name) {
        this.name = name;
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class test03 {

    @Test
    public void test01(){
       Thread1 thread1 = new Thread1("a");
        Thread1 thread2 = new Thread1("b");
        thread1.start();
        thread2.start();
    }
}

二、实现Runnable接口实现多线程

class Thread2 implements Runnable{
    private String name;

    public Thread2(String name) {
        this.name=name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                Thread.sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}


public class test03 {

    @Test
    public void test01(){
      new Thread(new Thread2("a")).start();
        new Thread(new Thread2("b")).start();
    }
}


三、Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

四、线程状态转换

在这里插入图片描述
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

五、常用函数

sleep(): 强迫一个线程睡眠N毫秒。
 isAlive(): 判断一个线程是否存活。
 join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
 isDaemon(): 一个线程是否为守护线程。
 setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等 待主线程依赖于主线程结束而结束)
  setName(): 为线程设置一个名称。
  wait(): 强迫一个线程等待。
  notify(): 通知一个线程继续运行。
  setPriority(): 设置一个线程的优先级。

六、线程同步

Java中的线程同步主要依靠的是synchronized关键字在实现的,它是一种同步锁。
它修饰的对象有以下几种:
  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

作用演示

package com.heima.user.service;


import org.junit.Test;

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public  void run() {
        synchronized (this){
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("线程名:"+Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public int getCount() {
        return count;
    }
}
public class test04 {


    public static void main(String[] args) {
        System.out.println("使用关键字synchronized");
        SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(syncThread, "SyncThread1");
        Thread thread2 = new Thread(syncThread, "SyncThread2");
        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

同步代码块在被SyncThread1执行时不会被SyncThread2访问

   SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(syncThread, "SyncThread1");
        Thread thread2 = new Thread(syncThread, "SyncThread2");

注意这里的syncThread对象应该是一个对象,如果两个对象不一样则会出现以下结果:

public class test04 {


    public static void main(String[] args) {
        System.out.println("使用关键字synchronized");
//        SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
        Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

七、线程池

创建Java线程需要给线程分配堆栈内存以及初始化内存,还需要进行系统调用,频繁地创建和销毁线程会大大降低系统的运行效率,采用线程池来管理线程有以下好处:

1.提升性能:线程池能独立负责线程的创建、维护和分配
2.线程管理:每个Java线程池会保持一些基本的线程统计信息,对线程进行有效管理

线程池都实现于顶层接口Executor。
在这里插入图片描述

Executors创建线程的4种方法

  • newSingleThreadExecutor创建“单线程化线程池”:单次执行
  • newFixedThreadPool创建“固定数量的线程池:执行固定次数
  • newCachedThreadPool创建“可缓存线程池”:根据内存大小动态执行
  • newScheduledThreadPool创建“可调度线程池”

线程池的任务调度流程

在这里插入图片描述

1.如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从线程队列中获取一个空闲线程。
2.如果线程池中总的任务数量大于核心线程池数量,新接收的任务将被加入阻塞队列中,一直到阻塞队列已满。
3.当完成一个任务的执行时,执行器总是优先从阻塞队列中获取下一个任务,并开始执行,一直到阻塞队列为空。
4.在核心线程池数量已经用完、阻塞队列也已经满了的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务。
5.在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出maximumPoolSize。如果线程池的线程总数超过。6.maximumPoolSize,线程池就会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略。

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值