Java线程

目录

一、线程入门

二、线程同步

三、死锁

四、线程的方法

五、线程的流程图

六、线程池


一、线程入门

1.进程:每一个软件都是一个进程。

2.线程:进程是由多个线程组成的。

3.进程和线程的关系:一个进程是对应一个或者是多个线程的。

4.我们所有的操作都是由CPU来执行的,但是CPU一次只能处理一个操作,所以我们在打开了多个程序后,只是看似是一起运行的,实际上还是在单个运行:

(1)那么多个程序为什么可以看似是一起运行的,这是因为CUP把执行的时间分成了时间片段;

(2)比如:把1秒分成了多个时间片段,其中一部分时间片段是执行一个程序,而另一部分时间片段又去执行另一个程序,因为时间片段很快,我们是很难用肉眼去分辨的,所以在1秒内,多个程序是可以看作是一起运行的;

  • 秒后面的单位:微秒 -> 纳秒

5.线程类:Thread类

(1)创建线程:new一个线程类,但是一般情况下,我们是不会这么写的,我们会通过匿名内部类的方式来写;

java 复制代码
Thread t = new Thread(); // 创建线程  -- 初始化

(2)使用匿名内部类的方法来创建线程;

java 复制代码
// 创建线程
Thread t = new Thread(){//运行状态  -->>  抢到了CPU资源
    // 重写run方法
    public void run(){
        
    }
};

(3)子线程;

  • 在一个线程中创建了另一个线程,那么被创建出来的那个线程就是那个已有线程的子线程;

(4)线程类中的常用方法;

  • 获取当前线程的名字:Thread.currentThread().getName();
java 复制代码
System.out.println(Thread.currentThread().getName());
  • 启动线程:线程类的对象名.start();

    • 注意线程只有在启动后才会执行里面的代码;
    java 复制代码
    // 就绪状态 -->>  可以开始去抢占CPU的资源了
    t.start(); // 启动线程

(5)设置线程的优先级:线程类的对象名.setPriority(Thread的静态常量);

java 复制代码
   t.setPriority(Thread.MAX_PRIORITY);
  • Thread.MAX_PRIORITY --> 10;

  • Thread.MIN_PRIORITY --> 1;

  • Thread.NORM_PRIORITY --> 5;

  • 设置线程为精灵线程,也可以称为守护线程或者后台线程:线程类的对象名.setDaemon(true);

    • 作用:如果将一个子线程设置为精灵线程,那么这个子线程会在主线程停止执行后一起停止,就算是子线程还没有执行完也会被强制停止;
    java 复制代码
    t.setDaemon(true);

6.线程的状态:

(1)初始化状态:创建线程对象;

java 复制代码
// 创建一个线程类  --  初始化
Thread t = new Thread();

(2)就绪状态:调用start()方法;

  • 就绪状态代表线程可以开始去抢占CPU资源了;
java 复制代码
// 启动线程  --  就绪状态
t.start();

(3)运行状态:执行run()方法;

  • 代表是抢到了CPU资源,开始执行run()方法;
java 复制代码
// 创建线程
Thread t = new Thread(){ // 运行状态  -->>  抢到了CPU资源
    // 执行run方法
    public void run(){
        
    }
};

(4)死亡状态:run()方法运行结束;

(5)阻塞状态:调用sleep()方法的时候;

  • sleep()方法的括号中的参数是以毫秒为单位的;
java 复制代码
Thread.sleep(1);

7.实现线程有三种方法:

(1)实现接口:要将一个普通类变成线程类需要实现Runnable接口;

  • 使用实现接口的方法,那么这个普通类就变成了一个可以被线程类操作的类,然后使用线程类去完成一些操作;
  • 在实现接口后,这个普通类只是可以被线程类操作了,并没有变成一个线程类;
java 复制代码
public class Thread2 implements Runnable {
    @Override
    public void run() {

    }
}

(2)继承类:要将一个普通类变成线程类需要继承Thread类并重写run()方法;

  • 使用继承类的方法,那么将一个普通类变成线程类之后,这个普通类就是一个线程类,它和线程类的用法没有区别;
java 复制代码
public class Thread1 extends Thread {
    @Override
    public void run() {

    }
}

(3)线程池:线程池用来管理线程,下面会介绍;

8.实现线程的方法中,实现接口和继承类这个两个方法推荐使用实现接口的方法,因为java只能单继承,但是可以多实现。

二、线程同步

1.同步代码块:

(1)格式:synchronized(锁旗标){};

(2)含义:在同步块中的代码,同时只能有一个线程去执行;

java 复制代码
public class Ticket implements Runnable {
    int number = 100;

    @Override
 public void run() {
        while (true) {
            // 同步代码块
            synchronized ("") {
                if (number > 0) {// 还有票
                    try {
                        Thread.sleep(50);// 模拟卖票时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("同步代码块:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
                }
            }
        }
    }
}

(3)注意:synchronized括号中可以放字符串或对象,只有括号中是同一个字符串或同一个对象时才可以达到同步的效果,一般括号中放的是空字符串或者this对象;

2.同步方法:

(1)同步方法锁住的是当前对象;

java 复制代码
public class Ticket implements Runnable {
    int number = 100;

    @Override
    public void run() {
        while(true){
            sale();
        }
    }

    // 卖票的方法
    // 同步方法 -->>  锁住的是当前对象
    public synchronized void sale(){//this
        if (number > 0) {// 还有票
            try {
                Thread.sleep(50);// 模拟卖票时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("同步方法:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
        }
    }
}

3.总结:

(1)每个对象身上都有一把锁,它的状态不是1就是0;

(2)我们锁住的东西称为锁旗标,如:this,空字符串都可以称为锁旗标;

(3)想要线程同步,只需要让多个线程拥有同一个锁旗标;

三、死锁

1.互相拿着对方的锁,但是需要对方来解锁;

java 复制代码
public class Ticket implements Runnable {
    int number = 100;
    String s = "a";

    @Override
    public void run() {
        while (true) {
            if ("a".equals(s)) {
                while (true) {
                    sale();
                }
            } else {
                while (true) {
                    synchronized ("") {
                        if (number > 0) {// 还有票
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println("同步代码块:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
                            // 会造成死锁
                            synchronized (this){

                            }
                        }
                    }
                }
            }
        }
    }

    //卖票的方法
    //同步方法 -->>  锁住的是当前对象
    public synchronized void sale(){//this
        if (number > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("同步方法:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
            //会造成死锁
            synchronized (""){

            }
        }
    }
}

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        Thread t4 = new Thread(ticket);
        t1.start();
        t2.start();
        Thread.sleep(1);
        t.s = "b";
        t3.start();
        t4.start();
    }
}

四、线程的方法

1.挂起:线程对象名.suspend();

(1)此方法已过时,作为了解。

java 复制代码
Thread t1 = new Thread();
t1.suspend();

2.取消挂起:线程对象名.resume();

(1)此方法已过时,作为了解。

java 复制代码
Thread t1 = new Thread();
t1.resume();

3.礼让:线程对象名.yield();

(1)线程在抢到CPU的资源后,会让出来,但是在让出来后,还会和其他线程去抢CPU的资源(只让一次)。

java 复制代码
Thread t1 = new Thread();
t1.yield();

4.加入:线程对象名.join();

(1)在一个线程执行的途中,可以加入另一个正在执行的线程,然后正在执行的线程就会停下来让新加入的线程先执行完,等到新加入的线程执行完毕后,原来的线程才会继续执行。

java 复制代码
public class ThreadB extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
    }

    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        ThreadA a = new ThreadA(b);
        a.start();
        b.start();
    }
}

public class ThreadA extends Thread {
    private ThreadB b;

    public ThreadA(ThreadB b){
        this.b = b;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i == 10){
                try {
                    //当A线程执行到10的时候,将B线程加入进来
                    b.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
    }
}

5.休息/等待:线程对象名.wait()或者this.wait();

(1)让一个正在执行的线程停止执行,进入休息状态。

6.唤醒当前线程:线程对象名.notify()或者this.notify();

(1)如果当前线程是休息状态,则唤醒当前线程。

7.唤醒所有线程:线程对象名.notifyAll()或者this.notifyAll();

(1)唤醒全部已经进入休息状态的线程。

8.使用生产者和消费者案例来解释wait和notifyAll方法,notify方法和notifyAll方法使用方法差不多,一个是唤醒当前线程,一个是唤醒全部线程;

java 复制代码
/**
* 产品
*/
public class Product {
    int number = 0;
    int max = 100;

    public synchronized void add(){
        if(number < max){
            number++;
            System.out.println("生产了一件产品,现在的数量是:" + number);
            //当生产了一件产品的时候,唤醒全部线程开始消费
            this.notifyAll();
        }else {
            try {
                //当现有的产品数量大于等于最大的库存容量时,休息一下
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void remove(){
        if(number > 0){
            number--;
            System.out.println("消费了一件产品,现在的数量是:" + number);
            //当消费了一件产品的时候,唤醒全部线程开始生产
            this.notifyAll();
        }else {
            try {
                //当没有产品时,先休息一下
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 生产者
 */
public class Produce extends Thread {
    Product pro = null;
    public Produce(Product pro){
        this.pro = pro;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            pro.add();
        }
    }
}

/**
 * 消费者
 */
public class Saler extends Thread {
    Product pro = null;
    public Saler(Product pro){
        this.pro = pro;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            pro.remove();
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Product pro = new Product();
        new Produce(pro).start();
        new Produce(pro).start();
        new Saler(pro).start();
    }
}

五、线程的流程图

六、线程池

1.线程池的概念:

(1)将多个线程放入到一个类似池子的空间中,然后如果要使用某个线程,就从线程池中取出使用,使用完成后再将线程放入到线程池中。

2.Java通过Executors提供了四种线程池:

(1)newCacheThreadPool:创建一个可缓存的线程池,如果线程池长度超过处理需要,可以灵活的回收空闲线程,若没有可回收的线程,则会新创建一个;

  • 可缓存的线程池为无限大,当执行第二个任务时第一个任务已经完成,第二个任务会复用第一个任务的线程,而不用每次都新创建一个线程;
  • 使用execute方法来执行线程;
java 复制代码
// 可缓存的线程池,线程池为无限大,当执行第二个任务时如果第一个任务已经完成,会复用第一个任务的线程,而不用每次新建线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    final int index = i;
 try {
        Thread.sleep(1000 * index);
 } catch (InterruptedException e) {
        e.printStackTrace();
 }
    cachedThreadPool.execute(new Runnable() {
     @Override
        public void run() {
            System.out.println(index);
        }
 });
}

(2)newFixedThreadPool:创建一个固定长度的线程池,可控制线程的最大并发数,如果超出,线程会在队列中等待;

  • 创建一个固定长度的线程池,这个线程池的长度是固定的,每次执行线程的时候,会按最大长度去限制执要行线程的个数;
    • 如果最大长度为3,则一次性最多只能执行3个线程,如果为5,则一次性最多只能执行5个线程;
  • 使用execute方法来执行线程;
java 复制代码
// 创建固定长度的线程池,因为线程池的大小为3,每个任务休息3秒之后,会打印三个数字,所以是每三秒打印三个数字
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
   for (int i = 0; i < 10; i++) {
    final int index = i;
       fixedThreadPool.execute(new Runnable() {
        @Override
           public void run() {
            try {
                   Thread.sleep(3000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
            }
               System.out.println(index);
           }
       });
   }

(3)newScheduledThreadPool:创建一个定长的线程池,支持定时及周期任务;

  • 使用schedule方法来定时执行线程,就是让一个线程在多长时间后执行;
java 复制代码
// 创建一个定长的线程池,支持定时及周期任务执行
// 定时:表示延迟3秒后执行一次
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(new Runnable() {
    @Override
    public void run() {
        System.out.println("delay 3 seconds");
    }
},3, TimeUnit.SECONDS);
  • 使用scheduleAtFixedRate方法来定时及定周期执行线程,就是让一个线程,在多长时间后执行,每多长时间执行一次;
java 复制代码
// 创建一个定长的线程池,支持定时及周期任务执行
// 延迟1秒之后,每隔3秒执行一次
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
    @Override
    public void run() {
        System.out.println("delay 1 seconds, and execute every 3 seconds");
    }
},1,3,TimeUnit.SECONDS);

(4)newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,可以保证所有的任务按照指定的顺序来执行;

  • 创建一个单线程化的线程池,也就是说这个线程池只会创建一个线程,然后使用这个线程去执行各个任务,相当于顺序执行各个任务;
  • 使用execute方法来执行线程;
java 复制代码
// 创建一个单一的线程池,结果按照执行顺序依次输出,相当于顺序执行各个任务
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}
相关推荐
孑么3 分钟前
力扣 二叉树的最大深度
java·算法·leetcode·职场和发展·深度优先·广度优先
mikey棒棒棒5 分钟前
SSM-Spring-IOC/DI注解开发
java·后端·spring·ssm·ioc·di
我不是你的灯笼9 分钟前
Go语言的 的垃圾回收(Garbage Collection)基础知识
开发语言·后端·golang
高山莫衣23 分钟前
Python实现批量找出两个文件夹中同名文件
开发语言
xweiran25 分钟前
Spring源码分析之事件机制——观察者模式(二)
java·开发语言·spring·观察者模式·底层源码
深鱼~28 分钟前
【多线程初阶篇¹】线程理解| 线程和进程的区别
java·开发语言·人工智能·深度学习·计算机视觉
shinelord明38 分钟前
【再谈设计模式】观察者模式~对象间依赖关系的信使
开发语言·数据结构·观察者模式·设计模式·软件工程
Q_19284999061 小时前
基于Spring Boot的前后端分离的外卖点餐系统
java·spring boot·后端
鲤籽鲲1 小时前
C# 字符串文本 详解
开发语言·c#
xmh-sxh-13141 小时前
Redis中字符串和列表的区别
java