JAVA多线程基础知识总结归纳

本文深入探讨了Java中的多线程概念,包括进程与线程的定义,线程的基本API,以及线程的创建方式。详细阐述了线程池的重要性和使用,如ExecutorService和Executors接口,以及它们的不同实现。此外,文章还讨论了线程安全问题,提供了三种解决线程安全的方法:同步代码块、同步方法和显式锁,并通过卖票示例进行了说明。最后总结了显式锁与隐式锁的区别,并给出了处理线程安全问题的判断步骤。
摘要由CSDN通过智能技术生成

多线程

1.引言

计算机的配置越来越高,CPU计算速度越来越快,如果能够将一个单一执行的程序进行优化,分解成多个小块(几乎同时)进行,能够大大提高开发效率,对CPU的利用率也能够提升一布。

2.进程和线程

进程:进程是执行程序的一个过程,一个程序从打开到关闭算做一个进程。进程是一个动态的过程,每个进程都有自己独立的一块内存空间,有自己的生存周期。

线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

并发和并行

并发:发生在同一时间间隔内,即理解为一个人同时做很多事

并行:发生在同一时刻,即理解为不同的人同时在做不同的事

3.线程的基本API

1.start():1.启动当前线程2.调用线程中的run方法

2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

3.currentThread():静态方法,返回执行当前代码的线程

4.getName():获取当前线程的名字

5.setName():设置当前线程的名字

6.yield():主动释放当前线程的执行权

7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去

8.stop():过时方法。当执行此方法时,强制结束当前线程。

9.sleep(long millitime):线程休眠一段时间

10.isAlive():判断当前线程是否存活

4.线程的创建方式

1.继承Thread抽象类

​ 创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。

​ 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。

2.继承Runnable接口

​ 实现Runnable接口,重写run方法

3.继承Callable接口

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

范例:线程基本创建

/**
 * Runnable接口
 */
public class ThreadDemo{
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Thread t =  new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是谁");
            }
        });
        t.start();
    }
}

线程池

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)

好处:提高响应速度(减少了创建新线程的时间)

降低资源消耗(重复利用线程池中线程,不需要每次都创建)

便于线程管理

ExecutorService 和 Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.

void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable

Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable

void shutdown():关闭连接池。

Executors 工具类,线程池的工厂类,用于创建并返回不同类型的线程池

Executors.newCachedThreadPool() 创建一个可根据需要创建新线程的线程池

Executors.newFixedThreadPool(n) 创建一个可重用固定线程数的线程池

Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池

Executors.newScheduledThreadPool(n) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

范例:创建一个定长线程池

/**
 *定长线程池
 * (长度为指定数值)
 * 任务加入后的执行流程:
 *          1.判断线程池中是否有空闲线程
 *          2.存在则使用
 *          3.不存在且线程池未满,则添加新的线程进入线程池,并执行
 *          4.不存在且线程池已满,则排队等待空闲线程
 */
public class DemoFixedThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.execute(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾");
            }
        }));
    }
}

范例:创建一个缓存线程池

/**
 *缓存线程池
 * (长度没有限制)
 * 任务加入后执行流程:
 *     1.判断线程池中是否又空闲线程
 *     2.有则使用
 *     3.没有则创建新的线程添加到线程池,然后使用
 */
public class DemoCachedThreadPool {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"楚河");
            }
        }));
    }
}

范例:创建一个周期线程池

/**
 * 周期任务 定长线程池
 * 执行流程:
 *    1.判断线程是否为空闲线程
 *    2.存在则使用
 *    3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
 *    4.不存在空闲线程,且线程池以满的情况下,则等待线程池存在空闲线程
 */
public class Demo周期线程池 {
    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"楚河");
            }
        },5,1, TimeUnit.SECONDS);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"楚河");
            }
        },5, 1,TimeUnit.SECONDS);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"楚河");
            }
        },5, 1,TimeUnit.SECONDS);
    }
}

5.线程安全

创建三个窗口卖票,总票数为100张票

1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。

2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)

因此,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

Java中,有三种方式解决这个问题:

5.1同步代码块

同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

范例:

/**
 * 解决线程不安全,同步代码块
 */
public class Demo1 {
    public static void main(String[] args) {
        Runnable ticket = new Ticket();
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }

    static class Ticket implements Runnable{
        private int count = 10;
        Object o = new Object();//创建一个Object类型的变量,充当锁
        @Override
        public void run() {
            while (true){//通过锁使得线程排队完成,获得锁的线程继续走,没有获得锁的线程等待
                synchronized (o) {
                   if (count>0) {
                       System.out.println("正在卖票");
                       try {
                           Thread.sleep(1000);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       count--;
                       System.out.println(Thread.currentThread().getName() + "出票成功,当前票数剩余" + count);
                   }else {
                       break;
                   }
               }
            }
        }
    }
}

5.2同步方法

用同步方法,对方法进行synchronized关键字修饰

将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。

对于runnable接口实现多线程,只需要将同步方法用synchronized修饰

而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

/**
 * 同步方法
 */
public class Demo2 {
    public static void main(String[] args) {
        Runnable ticket = new Ticket();
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }

    public static class Ticket implements Runnable{
        private int count = 10;
        @Override
        public void run() {
            while (true){
                sale();
            }
        }

        private synchronized void sale(){
                if (count>0) {
                    System.out.println("正在卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,当前票数剩余"+count);
                }
        }
    }
}

5.3显式锁

实际定义出来一个锁。

/**
 * 显式锁
 */
public class Demo3 {
    public static void main(String[] args) {
         Runnable ticket = new Ticket();
         new Thread(ticket).start();
         new Thread(ticket).start();
         new Thread(ticket).start();
    }

    public static class Ticket implements Runnable{
        private int count = 10;
        Lock lock = new ReentrantLock();

        @Override
        public  void run() {
            while (true) {
                lock.lock();
                if (count > 0) {
                    System.out.println("正在卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "出票成功,当前票数剩余" + count);
                }
                lock.unlock();
            }
        }
    }
}

总结:显式锁Lock和隐式锁synchronized的区别?

1.获取和释放操作:

Lock需要进行实例,获取操作和释放操作,而synchronized不需要任何获取和释放的操作,只需要加上修饰符即可

2.Lock是通过API来获取的,而synchronized是由JVM来维护

3.Lock可以通过lock和unlock随时管理开始和中断,而synchronized则不能中断

4.Lcok可以通过赋值,true为公平锁,false为非公平锁,而synchronized则固定为非公平锁

值得一提的是,公平锁和非公平锁,举个例子,高中下课所有人跑步到食堂买饭,先到的人买了一份饭,而后面的人在等待,这个时候先到的那个人的同学发来了一条信息,然后先到的同学又买了一份,后面的同学只能再等,这就是非公平,而公平就是买完之后大家重新回到教室再次跑步到食堂买饭,先到先得。

优先使用顺序:

LOCK-同步代码块-同步方法

判断线程是否有安全问题,以及如何解决:

1.先判断是否多线程

2.再判断是否有共享数据

3.是否并发的对共享数据进行操作

4.选择上述三种方法解决线程安全问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值