线程和一些关于线程的面试题

1.什么是进程

        正在运行的程序,是系统的进行资源分配的基本单位,目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分,单核cpu在同一时刻,只能有一个进程,宏观并行,微观串行

2.什么是线程

        线程,又称轻量级进程,进程中的一条执行路径,也是cpu的基本调度单位。一个进程由一个或多个线程组成,彼此间完成不同的工作,同时进行,成为多线程

        例子:java虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并行执行

3.进程和线程的区别

        1.进程是操作系统资源分配的基本单位,而线程是cpu的基本调度单位---进程运行调用cpu里面的线程

        2.一个程序运行后至少有一个线程

        3.一个进程可以包换多个线程,但是至少需要有一个线程,否则这个进程是没有意义

        4.进程间不能共享数据段地址,但是同进程的线程之间可以

4.线程的特点

4.1.线程抢占式执行

        效率高,可防止单一线长时间独占cpu

4.2.在单核cpu中,宏观上同时执行,微观上顺序执行

5.java如何创建线程  

5.1.继承Thread类

public class Mythred extends Thread {
    //重写run方法
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("haha"+i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建线程对象
        Mythred m1=new Mythred();
        //开启线程
        m1.start();

        for (int i = 0; i < 20; i--) {
            System.out.println("主线程"+i);
        }
    }
}

                                 获取线程的名字

             //1.通过Thread对象中getName();--只能在Thread子类中获取
            System.out.println(this.getName()+"haha"+i);
            //2.在Thread类中存在一个静态方法获取当前线程对象,在通过该对象调用的getName()
            System.out.println(Thread.currentThread().getName()+i);

                                    设置线程的名字

          //设置线程的名称
        m1.setName("哈哈");

5.2.实现Runnable接口

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-16  09:50:56
 * 实现接口
 */
public class MyRunnable implements Runnable {
    private int ticket = 100;//定义总票数为100张
    //重写方法
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket < 0) {
                    break;
                }
                try {
                    Thread.sleep(100);//模拟售票需要一定的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            }
        }
    }
    }

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-16  09:56:34
 */
public class MyRunnableTest {
    public static void main(String[] args) {
        //创建线对象
        MyRunnable m1=new MyRunnable();

        Thread t1=new Thread(m1,"m1");
        Thread t2=new Thread(m1,"m2");
        Thread t3=new Thread(m1,"m3");
        Thread t4=new Thread(m1,"m4");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

5.3.实现Callable接口

实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出。

public class Test2 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //1-100的和
        int sum=0;
        for (int i =1; i <=100; i++) {
            sum+=i;
        }
        return sum;
   
    }
}
package guan.demo4;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-19  10:30:26
 */
public class Test {
    public static void main(String[] args) throws Exception{
        //创建对象
        Test2 test2=new Test2();
        //创建定长的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 提交任务给线程池中线程对象
        Future<Integer> future = executorService.submit(test2);
        Integer sum = future.get();//--需要等线程执行完毕后,才会把结果输出给该变量
        //main中的方法
        System.out.println(sum);
        //关闭线程池--执行玩任务后关闭
        executorService.shutdown();
    }

}

6.线程中常见的方法

6.1.休眠

package guan;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-17  20:37:56
 */
public class Demo1 {
    //sleep  当前主线程休眠多少毫秒--静态方法
    public static void main(String[] args) {
        Sleep sleep=new Sleep();
        Thread t=new Thread(sleep);
        //开启线程
        t.start();


    }
    static class Sleep implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(3000);//当前cpu主动休眠多少毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println("休息=============="+i);
            }
        }
    }
}

6.2.放弃

package guan;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-17  21:17:39
 */
public class Demo2 {
    //yield--当前线程主动放弃时间片,回叙到就绪状态,竞争下一次时间片--静态方法---y1  y2的交替变得多了
    public static void main(String[] args) {
        Yield y1=new Yield();
        Thread t1=new Thread(y1,"hello");
        t1.start();

        Thread t2=new Thread(y1,"world");
        t2.start();
    }
    static class Yield implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 30; i++) {
                Thread.yield();
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    }
}

6.3.加入

package guan;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-17  21:36:40
 */
public class Demo3 {
    //join,允许其他线程添加到当前线程中,直到其他线程执行完成后,才执行--在start()之后添加
    public static void main(String[] args) throws InterruptedException {
        Join join=new Join();
        Thread t1=new Thread(join,"你好");

        t1.start();
        t1.join();

        JoinTest joinTest=new JoinTest();
        Thread t2=new Thread(join,"吃饭吗");
        t2.start();


    }
    static class Join implements Runnable{

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    }
    static class JoinTest implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    }
}

6.4.守护线程

                线程与两类:用户线程(前台线程)和守护线程(后台线程)

                如果线程中所有的前台进程都执行完毕了,后台进程也会自动结束,垃圾回收线程属于守护线程

package guan;

import com.sun.org.apache.xml.internal.security.signature.reference.ReferenceNodeSetData;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-17  21:57:06
 */
public class Demo4 {
    //Daemon守护线程
    public static void main(String[] args) {

        Daemon daemon=new Daemon();
        Thread t1=new Thread(daemon,"睡觉");
        //设置t1为守护线程。当前台进程执行完毕,后台也会执行完毕
        //设置谁为守护进程,谁就是后台进程
        t1.setDaemon(true);
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程"+i);
        }
    }
    static class Daemon implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+":副线程"+i);
            }
        }
    }
}

7.线程的状态

当线程对象对创建后,即进入了新建状态( NEW ), ; 当调用线程对象的 start() 方法( t.start(); ),
线程即进入就绪状态( Runnable ); CPU 获取到时间片,进入到运行状态( Running ; 当线程调用
wait() 或者 sleep() , 进入阻塞状态( Blocked ),当休眠时间结束后,或者调用 notify
notifyAll 时会重新进入就绪状态( Runnable , 再重新获取时间片,进入运行状态,线程执行完了或
者因异常退出了 run() 方法,该线程结束生命周期,进入终结状态 (Dead)

 8.线程安全问题

 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致

临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性

原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省

解决方法:加锁

    synchronized (临界资源对象) {

}
//this代表当前对象
 synchronized (this) {

}

注意:括号里面的必须是对象

9.线程死锁

9.1.什么是线程死锁

当A线程拥有A锁的资源,这个时候A锁需要B锁的资源,B线程拥有B锁的资源,这个时候B锁需要A锁的资源,这样会导致线程A等待B线程释放资源B,B线程等待A线程释放A资源,使A线程和B线程处于永久的等待,从而造成死锁

//对象
public class Lock {
    public static Object a=new Object();
    public static Object b=new Object();
}
//a对象
public class Boy extends Thread{
    @Override
    public void run() {
        synchronized (Lock.a){
            System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
            synchronized (Lock.b){
                System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
                System.out.println("可以吃饭了");
            }
      }
    }
}
//b对象
public class Gril extends Thread {
    @Override
    public void run() {
        synchronized (Lock.b){
            System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
            synchronized (Lock.a){
                System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
                System.out.println("可以吃饭了");
            }
        }
    }
}
//测试类
  //获取对象
        Boy boy=new Boy();
        //设置线程的名字
        boy.setName("哈哈");
        Gril gril=new Gril();
        gril.setName("曦曦");
        //开启线程
        boy.start();
        gril.start();

9.2.造成死锁的原因

                锁与锁之间的嵌套导致

9.3.如何解决死锁

                1.尽量减少锁得嵌套

                2.可以使用一些安全类

                3.可以使用Lock中的得枷锁,设置枷锁时间

10.线程通信

10.1.常见的方法

10.1.1.等待

     1.wait() 2.wait(long timeout)

注意:必须在对Object枷锁的同步代码块中,在一个线程中,调用Object.wait()时,此线程会释放其拥有的所有标记。同时此线程阻塞在Object的等待队列中,释放锁,进入等待队列

10.1.2.通知--唤醒等待队列中线程,进入到就绪队列,参与cpu的竞争

      1.notify()  2.notifyAll();

package guan.demo2;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-18  15:19:35
 */
public class Card {
    //定义余额剩余多少
    private double balance;
    //true:表示有钱  false:表示没钱
    private boolean flag=false;
    //get  set方法
    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //定义存钱的方法
    public synchronized void save(double money){
        //判断卡里面有没有钱--如果卡里面有钱的话就不存钱
        if (flag == true) {
            //如果有钱的话,等待,让取钱进行
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没有钱的话,存钱
        this.balance=this.balance+money;
        System.out.println(Thread.currentThread().getName()+"往卡中存入"+money+"现在卡中余额"+balance);
       //存钱完毕,代表有钱
        flag=true;
        //唤醒等待队列中的线程
        this.notify();
    }
    public synchronized void leave(double money) {
        //判断卡中有没有钱
        if (flag==false) {
            try {
                //如果没有钱的话等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //卡中有钱,取钱
        this.balance=this.balance-money;
        System.out.println(Thread.currentThread().getName()+"往卡中取出"+money+"现在卡中余额"+balance);
      //取钱完毕,没有钱了
        flag=false;
        //唤醒等待队列中的线程
        this.notify();
    }
}
public class BoyTest implements Runnable{
    //定义的卡对象
    private Card card;
    //有参
    public BoyTest(Card card) {
        this.card = card;
    }
    //重写Runnable里面的run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //调用卡中的存钱方法
            card.save(1000);
        }
    }
}
public class GrilTest implements Runnable{
    //定义的卡对象
    private Card card;
    //有参


    public GrilTest(Card card) {
        this.card = card;
    }
    //重写Runnable里面的run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //调用卡中的取钱的方法
            card.leave(1000);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建银行卡对象
        Card card=new Card();
        BoyTest boyTest=new BoyTest(card);
        GrilTest grilTest=new GrilTest(card);

        Thread t1=new Thread(boyTest,"曦曦");
        Thread t2=new Thread(grilTest,"哈哈");
        t1.start();
        t2.start();
    }
}

11.sleep和wait的区别?

11.1.所在的类不同

        sleep属于Thread类,wait属于Object类

11.2.使用的地方不同

     sleep可以使用在任何代码块,wait只能在同步代码块内使用

11.3.是否有释放锁资源

                sleep不释放锁资源,wait释放锁资源

11.4.sleep时间到了会自动唤醒,wait必须使用notify或者notifyAll来唤醒

12.notify和notifyAll的区别?

        notifyAll()会唤醒所有的线程,notify()之后唤醒的是一个线程,notify()调用后,会将全部的线程由等待池移到锁池,然后参与锁的竞争,竞争成功之后则继续执行,如果不成功则流在锁池等待锁被释放然后参与下一次竞争,而notify()只会唤醒一个线程,具体唤醒那一个线程由cpu控制

13.线程池

13.1.什么是线程池

                        该池子中预先存储若干个线程对象,整个池子就是线程池

13.2.为什么使用线程池

                        线程是宝贵的内存资源,单个线程约占1MB空间,过分分配容易造成溢出。频繁的创建以及销毁会增加线程虚拟机的回收频率,资源开销,造成功能下降

13.3.线程池的作用

    1.线程容器,可以设定线程分配的数量上限

    2.将预先创建的线程对象存入池中,并重用线程池中的线程对象

    3.避免频繁的创建和销毁

13.4.4.线程池创建的方法有哪些

所有的线程池---封装了一个父接口---java.util.concurrent.Executor.它的实现接口: ExecutorService.

有一个工具类Executors可以帮你创建相应的线程池。

                [1] 创建单一线程池 newSingleThreadExecutor()

                [2] 创建定长线程池。newFixedThreadPool(n);

                [3] 创建可变线程池. newCachedThreadPool()

                [4] 创建延迟线程池 .newScheduledThreadPool(n);

package guan.demo3;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-19  09:44:56
 */
public class Test {
    public static void main(String[] args) {
        // Executor:线程池的根类。里面有一个方法。execute执行线程任务的方法Runnable类型的任务
        // *       ExecutorService: 线程池的子接口
        // *           shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
        // *           shutdownNow(): 立即关闭线程池。
        // *           isTerminated():判断线程池是否终止了。
        // *           submit(任务参数): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。
        //单一线程池: 适应场景:队列要求线程有序执行。
       // ExecutorService executorService = Executors.newCachedThreadPool();
        //创建固定长度的线程池对象--默认3个,超出的线程等待
        //ExecutorService executorService = Executors.newFixedThreadPool(3);
        //创建可变长度的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //延迟任务执行
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);

        for (int i = 0; i < 5; i++) {
            //提交任务给线程池中线程对象
            //schedule定时执行
           scheduledExecutorService.schedule(new Runnable() {
               @Override
               public void run() {
                   System.out.println(Thread.currentThread().getName()+"====");
               }
           },10, TimeUnit.SECONDS);

        }

        //关闭线程池--执行玩任务后关闭
        executorService.shutdown();
    }
}

13.4.5.使用最原始的方式创建线程池

        上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

package guan.demo3;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * TODO
 *
 * @author lenovo
 * @version 1.0
 * @since 2022-07-19  10:11:00
 */
public class Test2 {
    public static void main(String[] args) {
        /**
         * int corePoolSize, 核心线程数--默认允许当开始有几个线程
         *          int maximumPoolSize, 最大线程数
         *          long keepAliveTime, 空闲时间
         *          TimeUnit unit, 时间单位
         *          BlockingQueue<Runnable> workQueue: 堵塞队列,
         *
         *  根据你自己的业务以及服务器配置来设置。
         */
        //LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。
        BlockingQueue blockingQueue=new LinkedBlockingDeque(3);
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);

        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"-------");
                }
            });
        }
        //关闭线程池--执行玩任务后关闭
        threadPoolExecutor.shutdown();
    }
}

14手动锁

Lock它是手动锁的父接口,它下面有很多实现类。

lock()方法。  tryLock()---查看是否获取锁资源

unlock()释放锁资源,放在finally中

    public void run() {
        while (true) {
            //上锁
                s.lock();
                try {
                    
               
                if (ticket < 0) {
                    break;
                }
                try {
                    Thread.sleep(100);//模拟售票需要一定的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //解锁
                    s.unlock();
                }
        }
    }
    }

15.synchronized和Lock有什么区别?        

        1.synchronized可以给类,方法,代码块枷锁,而Lock只能给代码块枷锁。

        2.synchronized不需要手动获取锁和释放锁,使用简单,发生异常或自动解锁,不会造成死锁,而lock需要自己手动释放锁,如果使用不当没有unlock()去释放锁,就会造成死锁

        3.通过Lock可以知道有没有成功获取死锁,ersynchronized却没有办法办到

猜你喜欢

转载自blog.csdn.net/ne_123456/article/details/125842824