Java多线程知识点整理(详细)

一、基本概念

1.程序、进程和线程的区别

①程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

②进程(process): 是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
——如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的

③线程(thread): 进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的。

在这里插入图片描述

2.何时需要多线程

① 程序需要同时执行两个或多个任务。
② 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
③ 需要一些后台运行的程序时。

二、线程的常用创建方式

1.通过继承Thread的方式创建线程

创建方式:

① 自定义一个类并继承Thread
② 重写run方法
③ 在run方法中去实现需要在分线程中实现的功能
④ 创建Thread的子类的对象
⑤ 调用start方法

举例:
//1.自定义一个类并继承Thread
class MyThread extends Thread{
    //2.重写run方法
    @Override
    public void run() {
        //3.在run方法中实现需要在分线程中实现的功能
        for (int i = 0; i < 100; i++) {
            //Thread.currentThread():获取当前正在执行的线程。
            //getName() : 获取线程的名字
            System.out.println(Thread.currentThread().getName() + "杀毒中.....");
        }
    }
}

public class ThreadTest2 {
    public static void main(String[] args) {
        //4.创建Thread子类的对像
        MyThread mt = new MyThread();
        //5.调用start方法
        mt.start();//start() :1.开启分线程   2.调用run方法
    }
}

2.通过实现Runnable的方式创建线程

创建方式:

① 自定义类实现Runnable接口
② 重写run方法
③ 在run方法中去实现需要在分线程中实现的功能
④ 创建Runnable实现类的对象
⑤ 创建Thread类的对象,并将Runnable实现类的对象作为实参传给Thread的构造器
⑥ 调用start方法

举例:
//1.自定义一个类并实现Runnable接口
class MyRunnable implements Runnable{
    //2.重写run方法
    @Override
    public void run() {
        //3。在run方法中去实现需要在分线程中实现的功能
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " ==== " + i);
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        //4.创建Runnable接口实现类的对象
        MyRunnable mr = new MyRunnable();
        //5.创建Thread的对象并将Runnable接口实现类的对象作为实参传给Thread的构造器中
        Thread t = new Thread(mr);
        //6.调用start方法
        t.start();
    }
}

3.两种创建线程方式的区别和联系

区别:

① 继承Thread: 线程代码存放Thread子类run方法中。
② 实现Runnable:线程代码存在接口的子类的run方法。

联系:

① 避免了单继承的局限性
② 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

三、线程Thread类常用API方法

多线程常用API方法

四、线程的生命周期

在这里插入图片描述

五、线程的同步

1.举例说明线程同步重要性

此处以三个窗口同步售票为例,总计100张票,多个线程同时运行时会出现错票、重票的现象------

发生问题的原因 :当某个线程正在操作共享数据时,当前正在操作共享数据时还没操作完毕,其它线程也进来操作该数据就发生了线程安全问题。

解决思路 :当某个线程在操作共享数据时,其它线程不能再操作该数据。

java中解决线程安全问题的方法: 1. 同步代码块 2. 同步方法

class MyRunnable implements Runnable{
    private int ticketNumber = 100;
    @Override
    public void run() {
        while(true){
            if(ticketNumber > 0){//有票
                try {
                    Thread.currentThread().sleep(45);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "===" + ticketNumber);
                ticketNumber--;
            }else{
                return;
            }
        }
    }
}


public class TicketTest {

    public static void main(String[] args) {
        //开启三个线程
        MyRunnable mr = new MyRunnable();
        new Thread(mr,"窗口1:").start();
        new Thread(mr,"窗口2:").start();
        new Thread(mr,"窗口3:").start();

    }
}

2. 同步代码块

格式 :
synchronized(同步监视器/锁){
操作共享数据的代码;
}
说明:
① 同步监视器 :可以是任意类的对象但是多个线程使用的锁必须是同一把。
② 如果是继承Thread类的方式那么锁不可以使用this
如果是实现Runnable接口的方式那么锁可以使用this
举例:

//实现Runnable接口的方式
class MyRun implements Runnable{
    private int ticketNumber = 100;
    @Override
    public void run() {

        while(true){
            synchronized (this) {//同步代码块,注意多个线程使用的锁必须是同一把
                if (ticketNumber > 0) {//有票
                    System.out.println(Thread.currentThread().getName() + "===" + ticketNumber);
                    ticketNumber--;
                } else {
                    return;
                }
            }
        }
    }
}

//继承Thread类方式
class MyThr extends Thread{
    private static int ticketNumber = 100;
    //锁
    private static Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj) {
                if (ticketNumber > 0) {//有票
                    System.out.println(Thread.currentThread().getName() + "===" + ticketNumber);
                    ticketNumber--;
                } else {
                    return;
                }
            }
        }
    }
}

3.同步方法

格式 :
权限修饰符 synchronized 返回值类型 方法名(形参列表){
操作共享数据的代码。
}
说明:
① 继承Thread的锁(同步方法用static修饰)是 : 当前类.class
② 实现Runnable的锁是 :this
举例:

class MyRun implements Runnable{
    private int ticketNumber = 100;
    @Override
    public void run() {
        while(true){
            if(!saleTicket()){
                return;//结束线程
            }
        }
    }
    /*
        同步方法:
        默认的锁是this
     */
    public synchronized boolean saleTicket(){
        if (ticketNumber > 0) {//有票
            System.out.println(Thread.currentThread().getName() + "===" + ticketNumber);
            ticketNumber--;
            return true;
        } else {
            return false;
        }
    }
}

//开启三个线程
MyRun mr = new MyRun();
new Thread(mr,"窗口1:").start();
new Thread(mr,"窗口2:").start();
new Thread(mr,"窗口3:").start();

六、继承Thread和实现Runnable的区别

1.单继承多实现的角度:
实现Runnable的方式更好一些

2.同步代码块的角度 :
继承Thread的锁不可以使用this,实现Runnable的锁可以使用this

3.同步方法的角度:
继承Thread中同步方法必须使用static修饰。默认的锁是:当前类.class
实抽Runnable中同步方法的默认锁是this

4.共享资源的角度 :
如果是在继承Thread中声明的共享资源必须使用static修饰(多个线程使用的是同一份)
实现Runnable中声明的共享资源不需要使用static修饰

总结:实现Runnable的方式更好一些。

七、线程安全的单例设计模式

class Bank{
    private Bank(){}
    private static Bank bank = null;
    public static Bank getInstance(){
        if (bank == null) {//增加一次判断以提高效率
            synchronized (Bank.class) {
                if (bank == null) {
                    bank = new Bank();
                }
            }
        }
        return bank;
    }
}

八、死锁

线程死锁的原因 :
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

解决方法:
专门的算法、原则
尽量减少同步资源的定义

举例:

new Thread() {
    public void run() {
        synchronized (s1) {
            s2.append("A");
            try {
                Thread.sleep(45);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (s2) {
                s2.append("B");
                System.out.print(s1);
                System.out.print(s2);
            }
        }
    }
}.start();

new Thread() {
    public void run() {
        synchronized (s2) {
            s2.append("C");
            try {
                Thread.sleep(45);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (s1) {
                s1.append("D");
                System.out.print(s2);
                System.out.print(s1);
            }
        }
    }
}.start();

九、线程通信

1.主要API

① wait(): 令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
② notify(): 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
③ notifyAll (): 唤醒正在排队等待资源的所有线程结束等待

2.经典案例之:生产者与消费者

class Clerk {  //售货员
    private int product = 0;

    /*
        锁 :this
     */
    public synchronized void addProduct() {

        if (product >= 20) {//如果生产的数量已经达到上限那么暂停生产(wait)
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {//数量没有达到上限可以生产,生产完毕必须通知消费者消费
            product++;
            System.out.println("生产者生产了第" + product + "个产品");
            notifyAll();//通知消费赶紧消费
        }
    }

    /*
        锁 :this
     */
    public synchronized void getProduct() {
        if (this.product <= 0) {//如果生产的数量已经达到下限,那么消费者将暂停消费(wait())
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("消费者取走了第" + product + "个产品");
            product--;
            notifyAll();
        }
    }
}

public class TestProduct {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    clerk.addProduct();
                }
            }
        },"生产者").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    clerk.getProduct();
                }
            }
        },"消费者").start();
    }
}

十、创建线程的另外两种方式

1.实现Callable

//1.自定义一个类并实现Callable接口
class MyCallable implements Callable<String>{
    //2.重写call方法
    @Override
    public String call() throws Exception {
        //3.在call方法中去实现需要在分线程中实现的功能同时返回需要返回的内容
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
        return "aaa";
    }
}
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //4.创建Callable接口的实现类的对象
        MyCallable mc = new MyCallable();
        //5.创建FutureTask的对象并将Callable接口的实现类的对象作为实参传到FutureTask的构造器中
        FutureTask<String> ft = new FutureTask<>(mc);
        //6.创建Thread类的对象并将FutureTask的对象作为实参传到Thread类的构造器中
        new Thread(ft).start();

        //获取线程返回的内容
        String info = ft.get();//调用此方法会 阻塞当前线程直到获取到返回结果才会继续向下执行。

        System.out.println("info===" + info);

        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
    }
}

2.线程池

① 概念:

因为线程的创建和销毁会消耗大量的资源所以有了线程池。
线程池可以帮助我们去管理线程。

② 分类:

Executors.newCachedThreadPool():
创建一个可根据需要创建新线程的线程池(有空闲的线程直接可以使用,如果没有创建新的线程)
Executors.newFixedThreadPool(n):
创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :
创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

③ API:

void execute(Runnable command) : 执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task): 执行任务,有返回值,一般又来执行Callable
void shutdown() : 关闭连接池

④ 举例:
//创建线程池
ExecutorService es = Executors.newCachedThreadPool();
//从线程池中调用线程
es.submit(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }
});
es.submit(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }
});
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值