精华内容
下载资源
问答
  • java多线程编程题目
    千次阅读
    2021-06-22 14:08:45

    1、要求线程a执行完才开始线程b, 线程b执行完才开始线程

    package com.example.javatest.theardTest.MultiThreadAlgorithm;
    
    /**
     * 要求线程a执行完才开始线程b, 线程b执行完才开始线程
     *
     * join()解释:https://blog.csdn.net/qq_18505715/article/details/79795728
     *
     * wait()  和  notify()  解释:https://blog.csdn.net/chaozhi_guo/article/details/50249177
     *
     * join()的作用:主要作用是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
     *
     * public void joinDemo(){
     *    //....
     *    Thread t=new Thread(payService);
     *    t.start();
     *    //....
     *    //其他业务逻辑处理,不需要确定t线程是否执行完
     *    insertData();
     *    //后续的处理,需要依赖t线程的执行结果,可以在这里调用join方法等待t线程执行结束
     *    t.join();
     * }
     *
     */
    public class T1T2T3 {
    
        public static class PrintThread extends Thread{
    
            PrintThread(String name){
                super(name);
            }
    
            @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println(getName() + " : " + i);
                }
            }
        }
    
        public static void main(String[] args){
    
            PrintThread t1 = new PrintThread("a");
            PrintThread t2 = new PrintThread("b");
            PrintThread t3 = new PrintThread("c");
    
            try {
    
                t1.start();
                t1.join();
    
                t2.start();
                t2.join();
    
                t3.start();
                t3.join();
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
        }
    
    }
    
    /**
     * 我对于join方法的理解:
     *
     *    join() 的源码:
     *    public final void join(long millis) throws InterruptedException {
     *         synchronized(lock) {
     *         ...
     *
     *           while (isAlive()) {
     *              lock.wait(0);
     *           }
     *        ...
     *         }
     *    }
     *
     *    其实就是main()线程调用join()后,synchronized(lock)语句块,获得lock的锁,
     *
     *    然后判断如果t1线程isAlive(), 就一直lock.wait(), 让自己(main()线程)阻塞住,
     *
     *    直到t1线程 !isAlive 后才不wait, 等待着被notify(), 然后t1 die后会调用lock.notifyAll()。
     *
     *
     *    注意:这里lock.wait(0)虽然在t1.join()内,但是join()内的代码不是运行在t1线程中,而是运行在main()线程中,
     *          t1线程中运行的是其run()方法内的代码。
     *
     */
    

    2、两个线程轮流打印数字,一直到100

    package com.example.javatest.theardTest.MultiThreadAlgorithm;
    
    /**
     * 两个线程轮流打印数字,一直到100
     * 
     * Java的wait()、notify()学习:
     * https://blog.csdn.net/boling_cavalry/article/details/77995069
     */
    public class TakeTurnsPrint {
    
        public static class TakeTurns {
    
            private static boolean flag = true;
    
            private static int count = 0;
    
            public synchronized void print1() {
                for (int i = 1; i <= 50; i++) {
                    while (!flag) {
                        try {
                            System.out.println("print1: wait before");
                            wait();
                            System.out.println("print1: wait after");
                        } catch (InterruptedException e) {
                        }
                    }
    
                    System.out.println("print1: " + ++count);
                    flag = !flag;
                    notifyAll();
                }
            }
    
            public synchronized void print2() {
                for (int i = 1; i <= 50; i++) {
                    while (flag) {
                        try {
                            System.out.println("print2: wait before");
                            wait();
                            System.out.println("print2: wait after");
                        } catch (InterruptedException e) {
                        }
                    }
    
                    System.out.println("print2: " + ++count);
                    flag = !flag;
                    notifyAll();
                }
            }
        }
    
        public static void main(String[] args){
    
            TakeTurns takeTurns = new TakeTurns();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    takeTurns.print1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    takeTurns.print2();
                }
            }).start();
    
        }
    
    }
    

    3、写两个线程,一个线程打印1~ 52,另一个线程打印A~Z,打印顺序是12A34B…5152Z

    package com.example.javatest.theardTest.MultiThreadAlgorithm;
    
    /**
     * 两线程,一个打印数字从1到52,另一个打印字母从A到Z,输出:12A34B56C...5152Z
     */
    public class TakeTurnsPrint2 {
        private boolean flag;
        private int count;
    
        public synchronized void printNum() {
            for (int i = 0; i < 26; i++) {
                    while (flag) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    flag = !flag;
                    System.out.print(++count);
                    System.out.print(++count);
                    notify();
            }
        }
    
        public synchronized void printLetter() {
            for (int i = 0; i < 26; i++) {
                    while (!flag) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    flag = !flag;
                    System.out.print((char) (65 + i));
                    notify();
            }
        }
    
        public static void main(String[] args) {
    
            TakeTurnsPrint2 turnsPrint2 = new TakeTurnsPrint2();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    turnsPrint2.printNum();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    turnsPrint2.printLetter();
                }
            }).start();
        }
    }
    

    4、编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC…

    package com.example.javatest.theardTest.MultiThreadAlgorithm;
    
    /**
     * 编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC...
     */
    public class ABCABCABC {
    
        private int flag = 0;
    
        public synchronized void printA() {
            for(int i = 0; i < 5; i++) {
                while (flag != 0) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                flag = 1;
                System.out.print("A");
                notifyAll();
            }
        }
    
        public synchronized void printB() {
            for(int i = 0; i < 5; i++) {
                while (flag != 1) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                flag = 2;
                System.out.print("B");
                notifyAll();
            }
        }
    
        public synchronized void printC() {
            for(int i = 0; i < 5; i++) {
                while (flag != 2) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                flag = 0;
                System.out.print("C");
                notifyAll();
            }
        }
    
        public static void main(String[] args) {
    
            ABCABCABC abcabcabc = new ABCABCABC();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    abcabcabc.printA();
                }
            }).start();
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    abcabcabc.printB();
                }
            }).start();
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    abcabcabc.printC();
                }
            }).start();
        }
    }
    

    5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。

    /**
     * 编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加
     */
    public class TenThreadSum {
    
        public static class SumThread extends Thread{
    
            int forct = 0;  int sum = 0;
    
            SumThread(int forct){
                this.forct = forct;
            }
    
            @Override
            public void run() {
                for(int i = 1; i <= 10; i++){
                    sum += i + forct * 10;
                }
                System.out.println(getName() + "  " + sum);
            }
        }
    
        public static void main(String[] args) {
    
            int result = 0;
    
            for(int i = 0; i < 10; i++){
                SumThread sumThread = new SumThread(i);
                sumThread.start();
                try {
                    sumThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                result = result + sumThread.sum;
            }
            System.out.println("result   " + result);
        }
    }
    

    6 、三个窗口同时卖票

    /**
     * 三个窗口同时卖票
     */
    
    /**
     * 票
     */
    class Ticket {
        private int count = 1;
        public void sale() {
            while (true) {
                synchronized (this) {
                    if (count > 200) {
                        System.out.println("票已经卖完啦");
                        break;
                    } else {
                        System.out.println(Thread.currentThread().getName() + "卖的第 " + count++ + " 张票");
                    }
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    /**
     * 售票窗口
     */
    class SaleWindows extends Thread {
        
        private Ticket ticket;
    
        public SaleWindows(String name, Ticket ticket) {
            super(name);
            this.ticket = ticket;
        }
    
        @Override
        public void run() {
            super.run();
            ticket.sale();
        }
    }
    
    public class TicketDemo {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
    
            SaleWindows windows1 = new SaleWindows("窗口1", ticket);
            SaleWindows windows2 = new SaleWindows("窗口2", ticket);
            SaleWindows windows3 = new SaleWindows("窗口3", ticket);
    
            windows1.start();
            windows2.start();
            windows3.start();
        }
    }
    

    7、 生产者消费者

    7.1 synchronized方式

    public class FruitPlateDemo {
    
        private final static String LOCK = "lock";
    
        private int count = 0;
    
        private static final int FULL = 10;
    
        public static void main(String[] args) {
    
            FruitPlateDemo fruitDemo1 = new FruitPlateDemo();
    
            for (int i = 1; i <= 5; i++) {
                new Thread(fruitDemo1.new Producer(), "生产者-" + i).start();
                new Thread(fruitDemo1.new Consumer(), "消费者-" + i).start();
            }
        }
    
        class Producer implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    synchronized (LOCK) {
                        while (count == FULL) {
                            try {
                                LOCK.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
    
                        System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源");
                        LOCK.notifyAll();
                    }
                }
            }
        }
    
        class Consumer implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    synchronized (LOCK) {
                        while (count == 0) {
                            try {
                                LOCK.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源");
                        LOCK.notifyAll();
                    }
                }
            }
        }
    }
    

    7.2 ReentrantLock方式 (可以保证顺序)

    public class Demo1 {
    
        private int count = 0;
    
        private final static int FULL = 10;
    
        private Lock lock;
    
        private Condition notEmptyCondition;
    
        private Condition notFullCondition;
    
        {
            lock = new ReentrantLock();
            notEmptyCondition = lock.newCondition();
            notFullCondition = lock.newCondition();
    
        }
    
        class Producer implements Runnable {
    
            @Override
            public void run() {
    
                for (int i = 0; i < 10; i++) {
                    lock.lock();
                    try {
                        while(count == FULL) {
                            try {
                                notFullCondition.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源");
                        notEmptyCondition.signal();
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
    
        class Consumer implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    lock.lock();
                    try {
                        while(count == 0) {
                            try {
                                notEmptyCondition.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源");
                        notFullCondition.signal();
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            Demo1 demo1 = new Demo1();
            for (int i = 1; i <= 5; i++) {
                new Thread(demo1.new Producer(), "生产者-" + i).start();
                new Thread(demo1.new Consumer(), "消费者-" + i).start();
            }
        }
    }
    

    7.3 BlockingQueue方式

    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    public class Demo2 {
    
        private int count = 0;
    
        private BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
    
        public static void main(String[] args) {
    
            Demo2 demo2 = new Demo2();
            for (int i = 1; i <= 5; i++) {
                new Thread(demo2.new Producer(), "生产者-" + i).start();
                new Thread(demo2.new Consumer(), "消费者-" + i).start();
            }
        }
    
        class Producer implements Runnable {
    
            @Override
            public void run() {
    
                for (int i = 0; i < 10; i++) {
                    try {
                        queue.put(1);
                        System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        class Consumer implements Runnable {
    
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        queue.take();
                        System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    8、交替打印两个数组

    package com.example.javatest.theardTest.MultiThreadAlgorithm;
    
    public class TwoArr {
    
        int[] arr1 = new int[]{1, 3, 5, 7, 9};
        int[] arr2 = new int[]{2, 4, 6, 8, 10};
    
        boolean flag;
    
        public synchronized void print1(){
            for(int i= 0; i < arr1.length; i++){
                while (flag){
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                flag = !flag;
                System.out.println(arr1[i]);
                notifyAll();
            }
        }
    
        public synchronized void print2(){
            for(int i= 0; i < arr2.length; i++){
                while (!flag){
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                flag = !flag;
                System.out.println(arr2[i]);
                notifyAll();
            }
        }
    
        public static void main(String[] args) {
    
            TwoArr twoArr = new TwoArr();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    twoArr.print1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    twoArr.print2();
                }
            }).start();
        }
    
    }
    
    更多相关内容
  • 面试必背 Java 多线程面试题

    千次阅读 2022-04-25 20:34:43
    1.4 什么是多线程多线程的优劣? 2. 线程与进程 2.1 什么是线程与进程 2.2 线程与进程的区别 2.3 用户线程与守护线程 2.4 什么是线程死锁 2.5 形成死锁的四个必要条件 2.6 如何避免死锁 2.7 什么是上下文...

    文章目录

    一、多线程基础基础知识

    1. 并发编程

    1.1 并发编程的优缺点

    1.2 并发编程的三要素

    1.3 并发和并行有和区别

    1.4 什么是多线程,多线程的优劣?

    2. 线程与进程

    2.1 什么是线程与进程

    2.2 线程与进程的区别

    2.3 用户线程与守护线程

    2.4 什么是线程死锁

    2.5 形成死锁的四个必要条件

    2.6 如何避免死锁

    2.7 什么是上下文的切换

    3. 创建线程

    3.1 创建线程的四种方式

    3.2 Runnable接口和Callable接口有何区别

    3.2 run()方法和start()方法有和区别

    3.3 为什么调用start()方法会执行run()方法,为什么不能直接调用run()方法

    3.4 什么是Callable和Future

    3.5 什么是FutureTask

    4. 线程状态和基本操作

    4.1 线程声明周期的6种状态

    4.2 Java用到的线程调度算法是什么?

    4.2 Java线程调度策略

    4.3 什么是线程调度(Thread Scheduler)和时间分片(Time Slicing )

    4.4 Java线程同步和线程调度的相关方法

    4.5 sleep()和wait()有什么区别

    4.6 你是如何调用wait()方法的,使用if还是循环

    4.7 为什么线程通信方法wait(),notify(),notifyAll()要被定义到Object类中

    4.8 为什么线程通信方法wait(),notify(),notifyAll()要在同步代码块或同步方法中被调用?

    4.9 Thread的yiele方法有什么作用?

    4.10 为什么Thread的sleep和yield是静态的?

    4.11 线程sleep和yield方法有什么区别

    4.12 如何停止一个正在运行的线程?

    4.13 如何在两个线程间共享数据?

    4.14 同步代码块和同步方法怎么选?

    4.15 什么是线程安全?Servlet是线程安全吗?

    4.16 线程的构造方法,静态块是被哪个线程类调用的?

    4.17 Java中是如何保证多线程安全的?

    4.18 线程同步和线程互斥的区别

    4.19 你对线程优先级有什么理解?

    4.20 谈谈你对乐观锁和悲观锁的理解?

    一、多线程基础基础知识

    1. 并发编程

    1.1 并发编程的优缺点

    优点:

    充分利用多核CPU的计算能力,通过并发编程的形式将多核CPU的计算能力发挥到极致,性能得到提升。

    方面进行业务的拆分。提高系统并发能力和性能:高并发系统的开发,并发编程会显得尤为重要,利用好多线程机制可以大大提高系统的并发能力及性能;面对复杂的业务模型,并行程序会比串行程序更适应业务需求,而并发编程更适合这种业务拆分。cai

    缺点:

    并发编程的目的是为了提高程序的执行效率,提高程序运行速度,但并发编程并不是总能提高性能,有时还会遇到很多问题,例如:内存泄漏,线程安全,死锁等。

    1.2 并发编程的三要素

    并发编程的三要素:(也是带来线程安全所在)

    1. 原子性:原子是不可再分割的最小单元,原子性是指一个或多个操作要么全部执行成功,要么全部执行失败。
    2. 可见性:一个线程对共享变量的修改,另一个线程能看到(synchronized,volatile)
    3. 有序性:程序的执行顺序按照代码的先后顺序

    线程安全的问题原因有:

    1. 线程切换带来的原子性问题

    2. 缓存导致的可见性问题

    3. 编译优化带来的有序性问题

    解决方案:

    • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
    • synchronized、volatile、LOCK,可以解决可见性问题
    • Happens-Before 规则可以解决有序性问题

    1.3 并发和并行有和区别

    并发:多个任务在同一个CPU上,按照细分的时间片轮流交替执行,由于时间很短,看上去好像是同时进行的。

    并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的同时进行。

    串行:有n个任务,由一个线程按照顺序执行。

    1.4 什么是多线程,多线程的优劣?

    定义:多线程是指程序中包含多个流,即在一个程序中可以同时进行多个不同的线程来执行不同的任务

    优点:

    • 可以提高CPU的利用率,在多线程中,一个线程必须等待的时候,CPU可以运行其它线程而不是等待,这样就大大提高了程序的效率,也就是说单个程序可以创建多个不同的线程来完成各自的任务。

    缺点:

    • 线程也是程序,线程也需要占内存,线程也多内存也占的也多。
    • 多线程需要协调和管理,所以需要CPU跟踪线程。
    • 线程之间共享资源的访问会相互影响,必须解决禁用共享资源的问题。

    2. 线程与进程

    2.1 什么是线程与进程

    进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。

    线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。

    2.2 线程与进程的区别

    根本区别:进程是操作系统资源分配的基本单元,而线程是处理器任务调度的和执行的基本单位。

    资源开销:每个进程都有自己独立的代码和空间(程序上下文),程序之间的切换会有较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    包含关系:如果一个进程内有多个线程,则执行的过程不是一条线的,而是多条线(多个线程),共同完成;线程是进程的一部分,可以把线程看作是轻量级的进程。

    内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

    2.3 用户线程与守护线程

    用户(User)线程:运行在前台,执行具体任务,如程序的主线程,连接网络的子线程都是用户线程。

    守护(Daemon)线程:运行在后台,为其它前台线程服务,也可以说守护线程是JVM非守护线程的”佣人“,一旦所有线程都执行结束,守护线程会随着JVM一起结束运行。

    main函数就是一个用户线程,main函数启动时,同时JVM还启动了好多的守护线程,如垃圾回收线程,比较明显的区别时,用户线程结束,JVM退出,不管这个时候有没有守护线程的运行,都不会影响JVM的退出。

    2.4 什么是线程死锁

    死锁是指两个或两个以上进程(线程)在执行过程中,由于竞争资源或由于彼此通信造成的一种堵塞的现象,若无外力的作用下,都将无法推进,此时的系统处于死锁状态。

    如图,线程A拥有的资源2,线程B拥有的资源1,此时线程A和线程B都试图去拥有资源1和资源2,但是它们的还在,因此就出现了死锁。

    2.5 形成死锁的四个必要条件

    1. 互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。
    2. 请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。
    3. 不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。
    4. 循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。

    2.6 如何避免死锁

    我们只需破坏形参死锁的四个必要条件之一即可。

    破坏互斥条件:无法破坏,我们的本身就是来个线程(进程)来产生互斥

    破坏请求与保持条件:一次申请所有资源

    破坏不剥夺条件:占有部分资源的线程尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。

    破坏循环等待条件:按序来申请资源。

    2.7 什么是上下文的切换

    当前任务执行完,CPU时间片切换到另一个任务之前会保存自己的状态,以便下次再切换会这个任务时可以继续执行下去,任务从保存到再加载执行就是一次上下文切换。

    3. 创建线程

    3.1 创建线程的四种方式

    1. 继承Thread类
    2. 实现Runnable接口
    3. 实现Callable接口
    4. Executors工具类创建线程池

    3.2 Runnable接口和Callable接口有何区别

    相同点:

    1. Runnable和Callable都是接口
    2. 都可以编写多线程程序
    3. 都采用Thread.start()启动线程

    不同点:

    1. Runnable接口run方法无返回值,Callable接口call方法有返回值,是个泛型,和Futrue和FutureTask配合用来获取异步执行结果。
    2. Runable接口run方法只能抛出运行时的异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息。

    注:Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会堵塞主线程继续往下执行,如果不调用就不会堵塞。

    3.2 run()方法和start()方法有和区别

    每个线程都是通过某个特定的Thread对象对于的run()方法来完成其操作的,run方法称为线程体,通过调用Thread类的start方法来启动一个线程。

    start()方法用于启动线程,run()方法用于执行线程的运行代码,run()可以反复调用,而start()方法只能被调用一次。

    start()方法来启动一个线程,真正实现了多线程的运行。调用start()方法无需等待run()方法体代码执行结束,可以直接继续执行其它的代码;调用start()方法线程进入就绪状态,随时等该CPU的调度,然后可以通过Thread调用run()方法来让其进入运行状态,run()方法运行结束,此线程终止,然后CPU再调度其它线程。

    3.3 为什么调用start()方法会执行run()方法,为什么不能直接调用run()方法

    这是一个常问的面试题,new Thread,线程进入了新建的状态,start方法的作用是使线程进入就绪的状态,当分配到时间片后就可以运行了。start方法会执行线程前的相应准备工作,然后在执行run方法运行线程体,这才是真正的多线程工作。

    如果直接执行了run方法,run方法会被当作一个main线程下的普通方法执行,并不会在某个线程中去执行它,所以这并不是多线程工作。

    小结:

    调用start方法启动线程可使线程进入就绪状态,等待运行;run方法只是thread的一个普通方法调用,还是在主线程里执行。

    3.4 什么是Callable和Future

    Callable接口也类似于Runnable接口,但是Runnable不会接收返回值,并且无法抛出返回结果的异常,而Callable功能更强大,被线程执行后,可有返回值,这个返回值可以被Future拿到,也就是说Future可以拿到异步执行任务的返回值。

    Future接口表示异步任务,是一个可能没有完成的异步任务结果,所以说Callable用于产生结果,Future用于接收结果。

    3.5 什么是FutureTask

    FutureTask是一个异步运算的任务,FutureTask里面可以可以传入Callable实现类作为参数,可以对异步运算任务的结果进行等待获取,判断是否已经完成,取消任务等操作。只有当结果完成之后才能取出,如果尚未完成get方法将堵塞。一个Future对象可以调用Callable和Runable的对象进行包装,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

    4. 线程状态和基本操作

    4.1 线程声明周期的6种状态

    很多地方说线程有5种状态,但实际上是6中状态,可以参考Thread类的官方api

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

    如图:

    新创建:又称初始化状态,这个时候Thread才刚刚被new出来,还没有被启动。

    可运行状态:表示已经调用Thread的start方法启动了,随时等待CPU的调度,此状态又被称为就绪状态。

    被终止:死亡状态,表示已经正常执行完线程体run()中的方法了或者因为没有捕获的异常而终止run()方法了。

    计时状态:调用sleep(参数)或wait(参数)后线程进入计时状态,睡眠时间到了或wait时间到了,再或者其它线程调用notify并获取到锁之后开始进入可运行状态。另一种情况,其它线程调用notify没有获取到锁或者wait时间到没有获取到锁时,进入堵塞状态。

    无线等待状态:获取锁对象后,调用wait()方法,释放锁进入无线等待状态

    锁堵塞状态:wait(参数)时间到或者其它线程调用notify后没有获取到锁对象都会进入堵塞状态,只要一获取到锁对象就会进入可运行状态。

    堵塞状态的详解:

    4.2 Java用到的线程调度算法是什么?

    计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获取到CPU的使用权才能执行指令,所谓多线程的并发运行,其实从宏观上看,各线程轮流获取CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待,CPU的调度,JVM有一项任务就是负责CPU的调度,线程调度就是按照特定的机制为多个线程分配CPU的使用权。

    有两种调度模型:分时调度和抢占式调度

    分时调度就是让所有的线程轮流获得CPU的使用权,并且平均分配到各个线程占有CPU的时间片。

    抢占式调度:Java虚拟机采用抢占式调度模型,是指优先让线程池中优先级高的线程首先占用CPU,如果线程池中优先级相同,那么随机选择一个线程,使其占有CPU,处于这个状态的CPU会一直运行,优先级高的分的CPU的时间片相对会多一点。

    4.2 Java线程调度策略

    度策略线程调度优先选择优先级高的运行,但是如果出现一下情况,就会终止运行(不是进入死亡状态):

    1. 线程调用了yield方法让出CPU的使用权,线程进入就绪状态。
    2. 线程调用sleep()方法,使其进入计时状态
    3. 线程由于IO受阻
    4. 另一个更高的优先级线程出现
    5. 在支持的时间片系统中,改线程的时间片用完。

    4.3 什么是线程调度(Thread Scheduler)和时间分片(Time Slicing )

    线程调度是一个操作系统服务,它负责为储在Runnable状态的线程分配CPU时间片,一旦我们创建一个线程并启动它,它的执行便依赖线程调度器的实现。

    时间分片是指CPU可用时间分配给Runnable的过程,分配的时间可以根据线程优先级或线程等待时间。

    4.4 Java线程同步和线程调度的相关方法

    1. wait():调用后线程进入无限等待状态,并释放所持对象的锁
    2. sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,是一个静态方法,需要处理InterruptException异常。
    3. notify():唤醒一个处于等待状态的线程(无线等待或计时等待),如果多个线程在等待,并不能确切的唤醒一个线程,与JVM确定唤醒那个线程,与其优先级有关。
    4. notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。

    4.5 sleep()和wait()有什么区别

    两者都可以使线程进入等待状态

    • 类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法
    • 是否释放锁:sleep()不释放锁,wait()释放锁
    • 用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。
    • 用法不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。

    4.6 你是如何调用wait()方法的,使用if还是循环

    处以等待状态的线程可能会收到错误警告或伪唤醒,如果不在循环中检查等待条件,程序可能会在没有满足条件的时候退出。

    synchronized (monitor) {
        //  判断条件谓词是否得到满足
        while(!locked) {
            //  等待唤醒
            monitor.wait();
        }
        //  处理其他的业务逻辑
    }
    

    4.7 为什么线程通信方法wait(),notify(),notifyAll()要被定义到Object类中

    Java中任何对象都可以被当作锁对象,wait(),notify(),notifyAll()方法用于等待获取唤醒对象去获取锁,Java中没有提供任何对象使用的锁,但是任何对象都继承于Object类,所以定义在Object类中最合适。

    有人会说,既然是线程放弃对象锁,那也可以把wait()放到Thread类中,新定义线程继承Thread类,也无需重新定义wait(),

    然而,这样做有一个很大的问题,因为一个线程可以持有多把锁,你放弃一个线程时,到底要放弃哪把锁,当然了这种设计不能不能实现,只是管理起来比较麻烦。

    综上:wait(),notify(),notifyAll()应该要被定义到Object类中。

    4.8 为什么线程通信方法wait(),notify(),notifyAll()要在同步代码块或同步方法中被调用?

    wait(),notify(),notifyAll()方法都有一个特点,就是对象去调用它们的时候必须持有锁对象。

    如对象调用wait()方法后持有的锁对象就释放出去,等待下一个线程来获取。

    如对象调用notifyAll()要唤醒等待中的线程,也要讲自身用于的锁对象释放,让就绪状态中的线程竞争获取锁。

    由于这些方法都需要线程持有锁对象,这样只能通过同步来实现,所以它们只能在同步块或同步方法中被调用。

    4.9 Thread的yiele方法有什么作用?

    让出CPU的使用权,使当前线程从运行状态进入就绪状态,等待CPU的下次调度。

    4.10 为什么Thread的sleep和yield是静态的?

    Thread类的sleep()和yield()方法将在当前正在运行的线程上工作,所以其它处于等待状态的线程调用它们是没有意义的,所以设置为静态最合适。

    4.11 线程sleep和yield方法有什么区别

    • 线程调用sleep()方法进入堵塞状态,醒来后因为(没有释放锁)后直接进入了就绪状态,运行yield后也没有释放锁,于是进入了就绪状态。
    • sleep()方法使用时需要处理InterruptException异常,而yield没有。
    • sleep()执行后进入堵塞状态(计时等待),醒来后进入就绪状态(可能是堵塞队列),而yield是直接进入就绪状态。

    4.12 如何停止一个正在运行的线程?

    使用stop方法终止,但是这个方法已经过期,不被推荐使用。

    使用interrupt方法终止线程

    run方法执行结束,正常退出

    4.13 如何在两个线程间共享数据?

    两个线程之间共享变量即可实现共享数据。

    一般来说,共享变量要求变量本身是线程安全的,然后在线程中对变量使用。

    4.14 同步代码块和同步方法怎么选?

    同步块是更好的选择,因为它不会锁着整个对象,当然你也可以然它锁住整个对象。同步方法会锁住整个对象,哪怕这个类中有不关联的同步块,这通常会导致停止继续执行,并等待获取这个对象锁。

    同步块扩展性比较好,只需要锁住代码块里面相应的对象即可,可以避免死锁的产生。

    原则:同步范围也小越好。

    4.15 什么是线程安全?Servlet是线程安全吗?

    线程安全是指某个方法在多线程的环境下被调用时,能够正确处理多线程之间的共享变量,能程序能够正确完成。

    Servlet不是线程安全的,它是单实例多线程的,当多个线程同时访问一个方法时,不能保证共享变量是安全的。

    Struts2是多实例多线程的,线程安全,每个请求过来都会new一个新的action分配这个请求,请求完成后销毁。

    springMVC的controller和Servlet一样,属性单实例多线程的,不能保证共享变量是安全的。

    Struts2好处是不用考虑线程安全问题,springMVC和Servlet需要考虑。

    如果想既可以提升性能又可以不能管理多个对象的话建议使用ThreadLocal来处理多线程。

    4.16 线程的构造方法,静态块是被哪个线程类调用的?

    线程的构造方法,静态块是被哪个线程类调用的?

    该线程在哪个类中被new出来,就是在哪个被哪个类调用,而run方法是线程类自身调用的。

    例子:mian函数中new Thread2,Thread2中new Thread1

    thread1线程的构造方法,静态块是thread2线程调用的,run方法是thread1调用的。

    thread2线程的构造方法,静态块是main线程调用的,run方法是thread2调用的。

    4.17 Java中是如何保证多线程安全的?

    使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger

    使用自动锁,synchronized锁

    Lock lock = new ReentrantLock(),使用手动锁lock .lock(),lock.unlock()方法

    4.18 线程同步和线程互斥的区别

    线程同步:当一个线程对共享数据进行操作的时候,在没有完成相关操作时,不允许其它的线程来打断它,否则就会破坏数据的完整性,必然会引起错误信息,这就是线程同步。

    线程互斥:

    而线程互斥是站在共享资源的角度上看问题,例如某个共享资源规定,在某个时刻只能一个线程来访问我,其它线程只能等待,知道占有的资源者释放该资源,线程互斥可以看作是一种特殊的线程同步。

    实现线程同步的方法:

    1. 同步代码块:sychronized(对象){} 块
    2. 同步方法:sychronized修饰的方法
    3. 使用重入锁实现线程同步:reentrantlock类的锁又互斥功能,Lock lock = new ReentrantLock(); Lock对象的ock和unlock为其加锁

    4.19 你对线程优先级有什么理解?

    每个线程都具有优先级的,一般来说,高优先级的在线程调度时会具有优先被调用权。我们可以自定义线程的优先级,但这并不能保证高优先级又在低优先级前被调用,只是说概率有点大。

    线程优先级是1-10,1代表最低,10代表最高。

    Java的线程优先级调度会委托操作系统来完成,所以与具体的操作系统优先级也有关,所以如非特别需要,一般不去修改优先级。

    4.20 谈谈你对乐观锁和悲观锁的理解?

    乐观锁:每个去拿数据的时候都认为别人不会修改,所以不会都不会上锁,但是在更新的时候会判断一下在此期间有没有去更新这个数据。所以乐观锁使用了多读的场合,这样可以提高吞吐量,像数据库提供的类似write_condition机制,都是用的乐观锁,还有那个原子变量类,在
    java.util.concurrent.atomic包下

    悲观锁:总是假设最坏的情况,每次去拿数据的时候都会认为有人会修改,所以每次在拿数据的时候都会上锁。这样别的对象想拿到数据,那就必须堵塞,直到拿到锁。传统的关系型数据库用到了很多这种锁机制,比如读锁,写锁,在操作之前都会先上锁,再比如Java的同步代码块synchronized/方法用的也是悲观锁。

    最后

    感谢阅读,由于篇幅有限以上面经资料博主已经整理打包好了,这些知识点的导图和问题的答案详解的PDF文档都可以免费分享给大家,点赞收藏文章后,私信【资料】免费领取!

    展开全文
  • 主要介绍了15个高级Java多线程面试题及回答,翻译自国外的一篇文章,这些面试题容易混淆、较难回答,需要的朋友可以参考下吧
  • 主要为大家分享了15个顶级Java多线程面试题,考察面试者是否有足够的Java线程与并发知识,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程与守护线程2.4 什么是线程死锁2.5 ...

    能力有限,初级菜🐔,多线程可是一块庞大的知识块,慢慢总结吧!

    文章目录

    一、多线程基础基础知识

    1. 并发编程

    1.1 并发编程的优缺点

    优点

    • 充分利用多核CPU的计算能力,通过并发编程的形式将多核CPU的计算能力发挥到极致,性能得到提升。
    • 方面进行业务的拆分。提高系统并发能力和性能:高并发系统的开发,并发编程会显得尤为重要,利用好多线程机制可以大大提高系统的并发能力及性能;面对复杂的业务模型,并行程序会比串行程序更适应业务需求,而并发编程更适合这种业务拆分。cai

    缺点

    • 并发编程的目的是为了提高程序的执行效率,提高程序运行速度,但并发编程并不是总能提高性能,有时还会遇到很多问题,例如:内存泄漏,线程安全,死锁等。

    1.2 并发编程的三要素

    并发编程的三要素:(也是带来线程安全所在)

    1. 原子性:原子是不可再分割的最小单元,原子性是指一个或多个操作要么全部执行成功,要么全部执行失败。
    2. 可见性:一个线程对共享变量的修改,另一个线程能看到(synchronized,volatile)
    3. 有序性:程序的执行顺序按照代码的先后顺序

    线程安全的问题原因有:

    1. 线程切换带来的原子性问题
    2. 缓存导致的可见性问题
    3. 编译优化带来的有序性问题
    

    解决方案:

    • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
    • synchronized、volatile、LOCK,可以解决可见性问题
    • Happens-Before 规则可以解决有序性问题

    1.3 并发和并行有和区别

    并发:多个任务在同一个CPU上,按照细分的时间片轮流交替执行,由于时间很短,看上去好像是同时进行的。
    并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的同时进行。
    串行:有n个任务,由一个线程按照顺序执行。

    1.4 什么是多线程,多线程的优劣?

    定义:多线程是指程序中包含多个流,即在一个程序中可以同时进行多个不同的线程来执行不同的任务
    优点:

    • 可以提高CPU的利用率,在多线程中,一个线程必须等待的时候,CPU可以运行其它线程而不是等待,这样就大大提高了程序的效率,也就是说单个程序可以创建多个不同的线程来完成各自的任务。
      缺点:
    • 线程也是程序,线程也需要占内存,线程也多内存也占的也多。
    • 多线程需要协调和管理,所以需要CPU跟踪线程。
    • 线程之间共享资源的访问会相互影响,必须解决禁用共享资源的问题。

    2. 线程与进程

    2.1 什么是线程与进程

    进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。
    线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。

    2.2 线程与进程的区别

    根本区别:进程是操作系统资源分配的基本单元,而线程是处理器任务调度的和执行的基本单位。
    资源开销:每个进程都有自己独立的代码和空间(程序上下文),程序之间的切换会有较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
    包含关系:如果一个进程内有多个线程,则执行的过程不是一条线的,而是多条线(多个线程),共同完成;线程是进程的一部分,可以把线程看作是轻量级的进程。
    内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

    2.3 用户线程与守护线程

    用户(User)线程:运行在前台,执行具体任务,如程序的主线程,连接网络的子线程都是用户线程。
    守护(Daemon)线程:运行在后台,为其它前台线程服务,也可以说守护线程是JVM非守护线程的”佣人“,一旦所有线程都执行结束,守护线程会随着JVM一起结束运行。
    main函数就是一个用户线程,main函数启动时,同时JVM还启动了好多的守护线程,如垃圾回收线程,比较明显的区别时,用户线程结束,JVM退出,不管这个时候有没有守护线程的运行,都不会影响JVM的退出。

    2.4 什么是线程死锁

    死锁是指两个或两个以上进程(线程)在执行过程中,由于竞争资源或由于彼此通信造成的一种堵塞的现象,若无外力的作用下,都将无法推进,此时的系统处于死锁状态。
    如图,线程A拥有的资源2,线程B拥有的资源1,此时线程A和线程B都试图去拥有资源1和资源2,但是它们的🔒还在,因此就出现了死锁。
    在这里插入图片描述

    2.5 形成死锁的四个必要条件

    1. 互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。
    2. 请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。
    3. 不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。
    4. 循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。

    2.6 如何避免死锁

    我们只需破坏形参死锁的四个必要条件之一即可。
    破坏互斥条件:无法破坏,我们的🔒本身就是来个线程(进程)来产生互斥
    破坏请求与保持条件:一次申请所有资源
    破坏不剥夺条件:占有部分资源的线程尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。
    破坏循环等待条件:按序来申请资源。

    2.7 什么是上下文的切换

    当前任务执行完,CPU时间片切换到另一个任务之前会保存自己的状态,以便下次再切换会这个任务时可以继续执行下去,任务从保存到再加载执行就是一次上下文切换。

    3. 创建线程

    3.1 创建线程的四种方式

    1. 继承Thread类
    2. 实现Runnable接口
    3. 实现Callable接口
    4. Executors工具类创建线程池

    3.2 Runnable接口和Callable接口有何区别

    相同点

    1. Runnable和Callable都是接口
    2. 都可以编写多线程程序
    3. 都采用Thread.start()启动线程

    不同点

    1. Runnable接口run方法无返回值,Callable接口call方法有返回值,是个泛型,和Futrue和FutureTask配合用来获取异步执行结果。
    2. Runable接口run方法只能抛出运行时的异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息。

    :Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会堵塞主线程继续往下执行,如果不调用就不会堵塞。

    3.2 run()方法和start()方法有和区别

    每个线程都是通过某个特定的Thread对象对于的run()方法来完成其操作的,run方法称为线程体,通过调用Thread类的start方法来启动一个线程。
    start()方法用于启动线程,run()方法用于执行线程的运行代码,run()可以反复调用,而start()方法只能被调用一次。

    start()方法来启动一个线程,真正实现了多线程的运行。调用start()方法无需等待run()方法体代码执行结束,可以直接继续执行其它的代码;调用start()方法线程进入就绪状态,随时等该CPU的调度,然后可以通过Thread调用run()方法来让其进入运行状态,run()方法运行结束,此线程终止,然后CPU再调度其它线程。

    3.3 为什么调用start()方法会执行run()方法,为什么不能直接调用run()方法

    这是一个常问的面试题,new Thread,线程进入了新建的状态,start方法的作用是使线程进入就绪的状态,当分配到时间片后就可以运行了。start方法会执行线程前的相应准备工作,然后在执行run方法运行线程体,这才是真正的多线程工作。
    如果直接执行了run方法,run方法会被当作一个main线程下的普通方法执行,并不会在某个线程中去执行它,所以这并不是多线程工作。
    小结
    调用start方法启动线程可使线程进入就绪状态,等待运行;run方法只是thread的一个普通方法调用,还是在主线程里执行。

    3.4 什么是Callable和Future

    Callable接口也类似于Runnable接口,但是Runnable不会接收返回值,并且无法抛出返回结果的异常,而Callable功能更强大,被线程执行后,可有返回值,这个返回值可以被Future拿到,也就是说Future可以拿到异步执行任务的返回值。
    Future接口表示异步任务,是一个可能没有完成的异步任务结果,所以说Callable用于产生结果,Future用于接收结果。

    3.5 什么是FutureTask

    FutureTask是一个异步运算的任务,FutureTask里面可以可以传入Callable实现类作为参数,可以对异步运算任务的结果进行等待获取,判断是否已经完成,取消任务等操作。只有当结果完成之后才能取出,如果尚未完成get方法将堵塞。一个Future对象可以调用Callable和Runable的对象进行包装,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

    4. 线程状态和基本操作

    4.1 线程声明周期的6种状态

    很多地方说线程有5种状态,但实际上是6中状态,可以参考Thread类的官方api

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
    

    如图:
    在这里插入图片描述
    新创建:又称初始化状态,这个时候Thread才刚刚被new出来,还没有被启动。
    可运行状态:表示已经调用Thread的start方法启动了,随时等待CPU的调度,此状态又被称为就绪状态。
    被终止:死亡状态,表示已经正常执行完线程体run()中的方法了或者因为没有捕获的异常而终止run()方法了。
    计时状态:调用sleep(参数)或wait(参数)后线程进入计时状态,睡眠时间到了或wait时间到了,再或者其它线程调用notify并获取到锁之后开始进入可运行状态。另一种情况,其它线程调用notify没有获取到锁或者wait时间到没有获取到锁时,进入堵塞状态。
    无线等待状态:获取锁对象后,调用wait()方法,释放锁进入无线等待状态
    锁堵塞状态:wait(参数)时间到或者其它线程调用notify后没有获取到锁对象都会进入堵塞状态,只要一获取到锁对象就会进入可运行状态。

    堵塞状态的详解:
    在这里插入图片描述

    4.2 Java用到的线程调度算法是什么?

    计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获取到CPU的使用权才能执行指令,所谓多线程的并发运行,其实从宏观上看,各线程轮流获取CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待,CPU的调度,JVM有一项任务就是负责CPU的调度,线程调度就是按照特定的机制为多个线程分配CPU的使用权。
    有两种调度模型:分时调度和抢占式调度
    分时调度就是让所有的线程轮流获得CPU的使用权,并且平均分配到各个线程占有CPU的时间片。
    抢占式调度:Java虚拟机采用抢占式调度模型,是指优先让线程池中优先级高的线程首先占用CPU,如果线程池中优先级相同,那么随机选择一个线程,使其占有CPU,处于这个状态的CPU会一直运行,优先级高的分的CPU的时间片相对会多一点。

    4.2 Java线程调度策略

    线程调度优先选择优先级高的运行,但是如果出现一下情况,就会终止运行(不是进入死亡状态):

    1. 线程调用了yield方法让出CPU的使用权,线程进入就绪状态。
    2. 线程调用sleep()方法,使其进入计时状态
    3. 线程由于IO受阻
    4. 另一个更高的优先级线程出现
    5. 在支持的时间片系统中,改线程的时间片用完。

    4.3 什么是线程调度(Thread Scheduler)和时间分片(Time Slicing )

    线程调度是一个操作系统服务,它负责为储在Runnable状态的线程分配CPU时间片,一旦我们创建一个线程并启动它,它的执行便依赖线程调度器的实现。
    时间分片是指CPU可用时间分配给Runnable的过程,分配的时间可以根据线程优先级或线程等待时间。

    4.4 Java线程同步和线程调度的相关方法

    1. wait():调用后线程进入无限等待状态,并释放所持对象的锁
    2. sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,是一个静态方法,需要处理InterruptException异常。
    3. notify():唤醒一个处于等待状态的线程(无线等待或计时等待),如果多个线程在等待,并不能确切的唤醒一个线程,与JVM确定唤醒那个线程,与其优先级有关。
    4. notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。

    4.5 sleep()和wait()有什么区别

    两者都可以使线程进入等待状态

    • 类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法
    • 是否释放锁:sleep()不释放锁,wait()释放锁
    • 用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。
    • 用法不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。

    4.6 你是如何调用wait()方法的,使用if还是循环

    处以等待状态的线程可能会收到错误警告或伪唤醒,如果不在循环中检查等待条件,程序可能会在没有满足条件的时候退出。

    synchronized (monitor) {
        //  判断条件谓词是否得到满足
        while(!locked) {
            //  等待唤醒
            monitor.wait();
        }
        //  处理其他的业务逻辑
    }
    
    

    4.7 为什么线程通信方法wait(),notify(),notifyAll()要被定义到Object类中

    Java中任何对象都可以被当作锁对象,wait(),notify(),notifyAll()方法用于等待获取唤醒对象去获取锁,Java中没有提供任何对象使用的锁,但是任何对象都继承于Object类,所以定义在Object类中最合适。

    有人会说,既然是线程放弃对象锁,那也可以把wait()放到Thread类中,新定义线程继承Thread类,也无需重新定义wait(),
    然而,这样做有一个很大的问题,因为一个线程可以持有多把锁,你放弃一个线程时,到底要放弃哪把锁,当然了这种设计不能不能实现,只是管理起来比较麻烦。
    综上:wait(),notify(),notifyAll()应该要被定义到Object类中。

    4.8 为什么线程通信方法wait(),notify(),notifyAll()要在同步代码块或同步方法中被调用?

    wait(),notify(),notifyAll()方法都有一个特点,就是对象去调用它们的时候必须持有锁对象。
    如对象调用wait()方法后持有的锁对象就释放出去,等待下一个线程来获取。
    如对象调用notifyAll()要唤醒等待中的线程,也要讲自身用于的锁对象释放,让就绪状态中的线程竞争获取锁。
    由于这些方法都需要线程持有锁对象,这样只能通过同步来实现,所以它们只能在同步块或同步方法中被调用。

    4.9 Thread的yiele方法有什么作用?

    让出CPU的使用权,使当前线程从运行状态进入就绪状态,等待CPU的下次调度。

    4.10 为什么Thread的sleep和yield是静态的?

    Thread类的sleep()和yield()方法将在当前正在运行的线程上工作,所以其它处于等待状态的线程调用它们是没有意义的,所以设置为静态最合适。

    4.11 线程sleep和yield方法有什么区别

    • 线程调用sleep()方法进入堵塞状态,醒来后因为(没有释放锁)后直接进入了就绪状态,运行yield后也没有释放锁,于是进入了就绪状态。
    • sleep()方法使用时需要处理InterruptException异常,而yield没有。
    • sleep()执行后进入堵塞状态(计时等待),醒来后进入就绪状态(可能是堵塞队列),而yield是直接进入就绪状态。

    4.12 如何停止一个正在运行的线程?

    1. 使用stop方法终止,但是这个方法已经过期,不被推荐使用。
    2. 使用interrupt方法终止线程
    3. run方法执行结束,正常退出

    4.13 如何在两个线程间共享数据?

    两个线程之间共享变量即可实现共享数据。
    一般来说,共享变量要求变量本身是线程安全的,然后在线程中对变量使用。

    4.14 同步代码块和同步方法怎么选?

    同步块是更好的选择,因为它不会锁着整个对象,当然你也可以然它锁住整个对象。同步方法会锁住整个对象,哪怕这个类中有不关联的同步块,这通常会导致停止继续执行,并等待获取这个对象锁。
    同步块扩展性比较好,只需要锁住代码块里面相应的对象即可,可以避免死锁的产生。
    原则:同步范围也小越好。

    4.15 什么是线程安全?Servlet是线程安全吗?

    线程安全是指某个方法在多线程的环境下被调用时,能够正确处理多线程之间的共享变量,能程序能够正确完成。
    Servlet不是线程安全的,它是单实例多线程的,当多个线程同时访问一个方法时,不能保证共享变量是安全的。
    Struts2是多实例多线程的,线程安全,每个请求过来都会new一个新的action分配这个请求,请求完成后销毁。
    springMVC的controller和Servlet一样,属性单实例多线程的,不能保证共享变量是安全的。
    Struts2好处是不用考虑线程安全问题,springMVC和Servlet需要考虑。
    如果想既可以提升性能又可以不能管理多个对象的话建议使用ThreadLocal来处理多线程。

    4.16 线程的构造方法,静态块是被哪个线程类调用的?

    线程的构造方法,静态块是被哪个线程类调用的?
    该线程在哪个类中被new出来,就是在哪个被哪个类调用,而run方法是线程类自身调用的。
    例子:mian函数中new Thread2,Thread2中new Thread1
    在这里插入图片描述
    thread1线程的构造方法,静态块是thread2线程调用的,run方法是thread1调用的。
    thread2线程的构造方法,静态块是main线程调用的,run方法是thread2调用的。

    4.17 Java中是如何保证多线程安全的?

    1. 使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
    2. 使用自动锁,synchronized锁
    3. Lock lock = new ReentrantLock(),使用手动锁lock .lock(),lock.unlock()方法

    4.18 线程同步和线程互斥的区别

    线程同步:当一个线程对共享数据进行操作的时候,在没有完成相关操作时,不允许其它的线程来打断它,否则就会破坏数据的完整性,必然会引起错误信息,这就是线程同步。
    线程互斥
    而线程互斥是站在共享资源的角度上看问题,例如某个共享资源规定,在某个时刻只能一个线程来访问我,其它线程只能等待,知道占有的资源者释放该资源,线程互斥可以看作是一种特殊的线程同步。
    实现线程同步的方法

    1. 同步代码块:sychronized(对象){} 块
    2. 同步方法:sychronized修饰的方法
    3. 使用重入锁实现线程同步:reentrantlock类的锁又互斥功能,Lock lock = new ReentrantLock(); Lock对象的ock和unlock为其加锁

    4.19 你对线程优先级有什么理解?

    每个线程都具有优先级的,一般来说,高优先级的在线程调度时会具有优先被调用权。我们可以自定义线程的优先级,但这并不能保证高优先级又在低优先级前被调用,只是说概率有点大。
    线程优先级是1-10,1代表最低,10代表最高。
    Java的线程优先级调度会委托操作系统来完成,所以与具体的操作系统优先级也有关,所以如非特别需要,一般不去修改优先级。

    4.20 谈谈你对乐观锁和悲观锁的理解?

    乐观锁:每个去拿数据的时候都认为别人不会修改,所以不会都不会上锁,但是在更新的时候会判断一下在此期间有没有去更新这个数据。所以乐观锁使用了多读的场合,这样可以提高吞吐量,像数据库提供的类似write_condition机制,都是用的乐观锁,还有那个原子变量类,在java.util.concurrent.atomic包下
    悲观锁:总是假设最坏的情况,每次去拿数据的时候都会认为有人会修改,所以每次在拿数据的时候都会上锁。这样别的对象想拿到数据,那就必须堵塞,直到拿到锁。传统的关系型数据库用到了很多这种锁机制,比如读锁,写锁,在操作之前都会先上锁,再比如Java的同步代码块synchronized/方法用的也是悲观锁。

    展开全文
  • 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是...

    Java 线程面试问题

    在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的。他们会问面试者很多令人混淆的Java线程问题。面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面。用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的。下面这些是我在不同时间不同地点喜欢问的Java线程问题。我没有提供答案,但只要可能我会给你线索,有些时候这些线索足够回答问题。现在引用Java5并发包关于并发工具和并发集合的问题正在增多。那些问题中ThreadLocal、Blocking Queue、Counting Semaphore和ConcurrentHashMap比较流行。

    15个Java多线程面试题及回答

    1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

    这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。

    2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?

    lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。

    3)在java中wait和sleep方法的不同?

    通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。

    4)用Java实现阻塞队列。

    这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。

    5)用Java写代码来解决生产者——消费者问题。

    与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。

    6)用Java编程一个会导致死锁的程序,你将怎么解决?

    这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。

    7) 什么是原子操作,Java中的原子操作是什么?

    非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。

    8) Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?

    自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性。

    9) 什么是竞争条件?你怎样发现和解决竞争?

    这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Java》。

    10) 你将如何使用thread dump?你将如何分析Thread dump?

    在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。

    11) 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

    这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。阅读我之前写的《start与run方法的区别》这篇文章来获得更多信息。

    12) Java中你怎样唤醒一个阻塞的线程?

    这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。

    13)在Java中CycliBarriar和CountdownLatch有什么区别?

    这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。

    14) 什么是不可变对象,它对写并发应用有什么帮助?

    另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。

    15) 你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?

    多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。

    补充的其它几个问题:

    1) 在java中绿色线程和本地线程区别?

    2) 线程与进程的区别?

    3) 什么是多线程中的上下文切换?

    4)死锁与活锁的区别,死锁与饥饿的区别?

    5) Java中用到的线程调度算法是什么?

    6) 在Java中什么是线程调度?

    7) 在线程中你怎么处理不可捕捉异常?

    8) 什么是线程组,为什么在Java中不推荐使用?

    9) 为什么使用Executor框架比使用应用创建和管理线程好?

    10) 在Java中Executor和Executors的区别?

    11) 如何在Windows和Linux上查找哪个线程使用的CPU时间最长?

    这只是其中的一部分,还有更多的资料以及答案

    需要学习路线视频及其文档来领取学习的小伙伴,可以转发此文关注小编,私信小编【学习】来领取!!

    最后再给大家分享一下大数据的学习路线图,希望本文能够帮助到大家的学习,也希望能够得到大家的喜欢!!

    展开全文
  • Java并发编程问题是面试过程中很容易遇到的问题,提前准备是解决问题的最好办法,将试题总结起来,时常查看会有奇效。 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题...
  • 主要为大家详细介绍了2018版java多线程面试题集合及答案,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Java线程 共享模型之管程 共享模型之内存 共享模型之无锁 共享模型之不可变 共享模型之工具 共享模型之管程 原理之 Monitor(锁) 原理之伪共享 模式篇—正确姿势 同步模式之保护性智停 同步模式之Blking 同步...
  • 这里写目录标题并发编程基础blocked 和 waiting 的区别线程的 run()和 start()有什么区别?为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?说说线程的生命周期及五种基本状态...
  • java多线程编程】三种多线程的实现方式

    万次阅读 多人点赞 2019-01-01 16:20:56
    文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?...在java语言最大的特点是支持多线程的开发(也是为数不多...
  • java多线程和并发编程面试题

    万次阅读 2019-03-21 14:49:42
    多线程和并发编程 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,可以使用多线程对运算密集型任务提速。比如,...
  • 多线程和并发的问题是任何java面试中必不可少的一部分。1)现在有三个线程:T1、T2和T3。如何确保T2在T1完成后执行,T3在T2完成后执行?这个线程问题通常在第一阶段或电话面试阶段询问,以确定您是否熟悉“连接”方法...
  • 面试题Java多线程

    千次阅读 2020-05-27 22:04:54
    Java多线程面试题1、什么是进程,什么是线程?2、请简要描述线程与进程的关系,区别及优缺点?3、为什么程序计数器、虚拟机栈、本地方法栈是线程私有的?4、说说并发与并行的区别?5、为什么要使用多线程呢?多线程会...
  • Java 多线程编程 实验

    千次阅读 2022-03-16 15:43:35
    Java 多线程编程 实验 1.创建键盘操作练习 2.双线程猜数字 3.月亮围绕地球
  • 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是...
  • Java多线程服务器编程

    2015-05-13 13:19:58
    简单的多线程服务器实现,东北大学网络实验之一
  • Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。 如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 ...
  • 架构师:『试试使用多线程优化』 第二天 头发很多的程序员:『师父,我已经使用了多线程,为什么接口还变慢了?』 架构师:『去给我买杯咖啡,我写篇文章告诉你』 ……吭哧吭哧买咖啡去了 在实际工作中,错误...
  • 下面动力节点java学院小编整理了60道最常见的Java多线程面试题及答案,供你学习或者面试参考。1.多线程使用的优缺点?优点:(1)多线程技术使程序的响应速度更快(2)当前没有进行处理的任务可以将处理器时间让给其它...
  • 史上最全Java多线程面试题及答案

    万次阅读 多人点赞 2018-08-20 11:17:08
    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。 这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 100,321
精华内容 40,128
关键字:

java多线程编程题目