JAVA-多线程理解

本文深入探讨Java多线程,包括线程与进程的关系、创建线程的三种方式、上下文切换、死锁避免、sleep()与wait()区别、synchronized优化、线程池的优势等关键知识点,帮助开发者全面理解Java并发编程。
摘要由CSDN通过智能技术生成

1,线程和进程
2,  上下文切换
3,  死锁?如何避免?
4,sleep()和wait()的区别和共同点?
5,start()和run()方法?
6,synchronized关键字
7,说说JDK16之后的 synchronized关键字底层做了哪些优化?
8,synchronized关键字和 volatile关键字的区别
9,为啥用线程池?
10,Runnable接⼝和Callable接⼝?
11,执行 execute方法和 submit方法的区别是什么呢
12,线程池的创建?


1,线程和进程

       一个进程执行过程中可以有多个线程,同类多个线程共享进程的方法区,独享程序计数器虚拟机栈本地方法栈

:存放对象实例,几乎所有的对象实例都在这里分配内存。
方法区:已被虚拟机加载的(元空间) 类信息、常量、静态变量、编译代码

虚拟机栈:每个方法被执行同时创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息 。
本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。
(二者保护线程中的局部变量不被别的线程所访问,so私有的啦)
程序计数器:字节码解释器通过改变程序计数器实现流程控制、多线程情况下给线程记录位置(so肯定是私有的啦)。也是唯一不会 OutOfMemoryError 的内存区,生命周期随着线程的而创建和销毁。
 

三种方式创建线程

  • 继承Thread类 ------------------实现:new Therad().start();---------------单继承局限性无法多线程;
  • 实现Runnable接口---------------可以多线程灵活方便;*********重点**********
  • 实现Callable接口 -----------------重写call方法,开启关闭服务;可以设置返回值和抛出异常;

 

2,上下文切换

     一个CPU在任意时刻只能被一个线程所使用,为了让每一个线程有效执行就给他们分配了时间片进行轮转。当任务执行完当前时间片在进入下一个时间片之前进行状态保存,以便下次使用加载。这个过程就是上下文切换。(纳秒级,Linux>win)

3,死锁?如何避免?

      多个线程同时被阻塞,都在等待某个资源被释放。

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
        synchronized (resource1) {
            System.out.println(Thread.currentThread() + "getresource1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "waitingget resource2");
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "getresource2");
                }
            }
        }, "线程 1").start();
        
        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "getresource2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "waitingget resource1");
            
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "getresource1");
              }
            }
        }, "线程 2").start();
    }
}

输出结果:

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

两个线程休眠结束都会开始请求对方资源,线程进入互相等待状态。产生锁的四个条件和避免方式如下:

  1. 互斥:资源在任意时刻只能被一个线程调用。(无法破坏,临界资源有互斥访问的必要性)
  2. 请求与保持:进程因请求资源阻塞时,不释放现有资源。(一次性申请所有资源)
  3. 不剥夺:线程获得的资源没用完释放之前不能被剥夺。(申请不到,主动释放自占资源)
  4. 循环等待:几个进程形成头尾相接的循环等待。(按序申请)

4,sleep()和wait()的区别和共同点?

都可以暂停线程,Thread类的方法:sleep(),yield()等Object的方法:wait()和notify()等
sleep没有释放锁,暂停执行。wait释放了锁,用于线程间交互。wait不会自动苏醒,需要别的线程调用同一个对象的notify或者notifyall方法。sleep传入参数自动唤醒。
[ 为什么wait、notify和notifyAll方法要和synchronized关键字一起使用?因为wait方法使一个线程进入等待状态,并释放其所持有的锁对象,notify方法是通知等待该锁对象的线程重新获得锁对象。所以前提是获得锁对象。锁对象就像一个传话的人,他对某个线程说停下来等待,然后对另一个线程说你可以执行了(实质上是被捕获了),这一过程是线程通信。sleep方法是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,运行的主动权是由当前线程来控制(拥有CPU的执行权)]。
sleep是你困了,要睡觉,等你睡醒了再干活。
wait是你现在没事做,先眯会儿吧,什么时候领导提醒你该干活了再干。

5,start()和run()方法?

       新建(new)一个Thread线程,调用start()启动线程进入就绪状态,分配到时间片就运行。start()执行线程相应准备工作然后自动执行run()的内容。 单独直接run()的话就是Thread里面的一个普通方法而已。

6,synchronized关键字

    解决的是多个线程间访问资源的同步性。被其修饰的方法或者代码块在任意时刻只能有一个线程执行。

    修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
    修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized方法占用的锁是当前类的锁,而访问非静态 synchronized方法占用的锁是当前实例对象锁。
    修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

    总结: synchronized关键字加到 static静态方法和 synchronized( class)代码块上都是是给Cass类上锁。 synchronized关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized( string a)因为VM中,字符串常量池具有缓存功能。

7,说说JDK16之后的 synchronized关键字底层做了哪些优化?

         jDK1.6锁的实现引入了,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,随着竞争激烈而逐渐升级。但是不可降级,这种策略是为了提髙获得锁和释放锁的效率。

8,synchronized关键字和 volatile关键字的区别

      volatile关键字是线程同步的轻量级实现,性能稍微好些。但 volatile只修饰变量。
      synchronized关键字后来引入的偏向锁和轻量级锁等优化方式提升了效率,实际开发场景还是较多。
      volatile多线程访问 不会发生阻塞,而 synchronized关键字可能会发生阻塞。
      volatile保证数据可见性,但不保证原子性。synchronized关键字两者都能保证。
      volatile主要用于解决变量在多个线程之间的可见性,而 synchronized解决的是多个线程之间访问资源的同步性

9,为啥用线程池?

       ●降损耗。对已创建的线程重复使用,降低线程创建和销毁消耗。

       ●提速度。任务到达的时候,可以不需要等到线程创建就能立即执行。

       ●提高线程可管理性。线程是稀缺资源,使用线程池可以进行统一的分配,调优和监控。

10,Runnable接⼝和Callable接⼝?

       Runnable较早,不会返回结果或抛出检查异常,后来的 Callable接口可以。工具类 Executors可以实现 Runnable对象和 Callable对象之间的相互转换。

@FunctionalInterface
public interface Runnable{
    public abstract void run();
}

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

11,执行 execute方法和 submit方法的区别是什么呢?

1. execute() 提交不需要返回值的任务,故无法判断任务是否被线程池执行成功与否。

2. submit() 提交需要返回值的任务。线程池会返回一个 Future类型的对象来判断任务是否执行成功。

并且可以通过Future的get() 方法来获取返回值,但是会阻塞当前线程直到任务完成,而使用get( long timeout, Timeunit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

12,线程池的创建?

     “阿里” 规范不让用 Executors去创建,而通过ThreadPoolexecutor。因为前者会产生堆积请求和大量线程导致OOM内存耗尽。
https://blog.csdn.net/hollis_chuang/article/details/83743723   大佬解析

FixedThreadPool(固定线程数的线程池)
ChachedThreadPool(缓存型线程池)
SingleThreadExecutor(单线程线程池)
ScheduledThreadPool(周期性调度线程池)

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mr.Thompson

相互学习,欢迎指正。

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

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

打赏作者

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

抵扣说明:

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

余额充值