可以扫描下面二维码访问我的小程序来打开,随时随地通过微信访问。

1. 开启线程的三种方式?

(1)继承Thread类,重写该类的run方法,调用线程对象的start()方法来启动
(2)实现Runnable接口,重写该类的run方法,调用线程对象的start()方法来启动
(3)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
通过new Thread传入FutureTask,执行Thread的start方法开始执行Callable线程。执行完成后通过FutureTask的get方法获得返回值,Callable具体代码如下:

public class TestCallableThread implements Callable<String> {
    public static void main(String[] args) throws Exception{
        TestCallableThread tct = new TestCallableThread();
        FutureTask<String> ft = new FutureTask<>(tct);
        new Thread(ft).start();
        while(true){
            if(ft.isDone()){
                System.out.println(ft.get());
                break;
            }else{
                System.out.println("waiting");
                Thread.sleep(1000);
            }
        }
    }
    @Override
    public String call() throws Exception {
        Thread.sleep(10000);
        return "Very Good";
    }
}

2. 说说进程,线程,协程之间的区别

(1)进程是系统进行资源分配和调度的独立单位
(2)线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位
(3)协程是一种用户态的轻量级线程,协程的调度完全由用户控制
(4)进程与线程的区别
线程是指进程内的一个执行单元,也是进程内的可调度实体。其主要区别:
<1>地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,他们共享进程的地址空间,而进程有自己独立的地址空间。
<2>资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
<3>线程是CPU处理器调度的基本单位,但是进程不是。
<4>二者均可并发执行(共同点)
<5>每一个独立的线程有一个程序运行的入口,顺序执行序列和程序出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程控制。
(5)协程与线程的区别
<1>一个线程可以有多个协程,一个进程也可以单独拥有多个协程。
<2>进程线程都是同步机制,而协程则是异步
<3>协程能够保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用状态
(6)协程的优点
<1>协程执行的效率非常高。
<2>协程不需要多线程的锁机制

3. 线程之间是如何通信的?

(1)volatile:线程会将内存中的数据,拷贝到各自的本地内存中,当某个变量被 volatile 修饰并且发生改变时,volatile 变量底层会通过lock前缀的指令,将该变量写会主存,同时使其他线程的本地变量的数据无效,从而再次直接从主存读取数据。
(2)等待/通知机制:先来看看wait的原理图
微信截图_20211119164401.png

public class WaitDemo {
    private static Object lock = new Object();
    private static  boolean flag = true;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    while (flag){
                        try {
                            System.out.println("wait start .......");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("wait end ....... ");
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (flag){
                    synchronized (lock){
                        if (flag){
                            lock.notify();
                            System.out.println("notify .......");
                            flag = false;
                        }
                    }
                }
            }
        }).start();
    }
}

(3)join方式:这个比较特殊,下面代码的意思是当前的DemoThread的线程要等待上一个线程执行完毕后才能继续执行,这样就涉及了等待,通知的操作。

public class TestJoin {
    public static void main(String[] args) {
        Thread previorThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new DemoThread(previorThread));
            thread.start();
            previorThread = thread;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " main terminal.");
    }
    static class DemoThread implements Runnable {
        private Thread thread;
        public DemoThread(Thread thread){
            this.thread = thread;
        }
        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " DemoThread terminal.");
        }
    }
}
//输出,很显然,join之后,后边的需要等待前面的线程完事
main main terminal.
Thread-0 DemoThread terminal.
Thread-1 DemoThread terminal.
Thread-2 DemoThread terminal.
Thread-3 DemoThread terminal.
Thread-4 DemoThread terminal.
Thread-5 DemoThread terminal.
Thread-6 DemoThread terminal.
Thread-7 DemoThread terminal.
Thread-8 DemoThread terminal.
Thread-9 DemoThread terminal.

(4)threadLocal方式:实际上线程内部的通信,将当前线程和一个map绑定,在当前线程内可以任意存取数据,减省了方法调用间参数的传递。

4. 什么是Daemon线程?它有什么意义?

Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
守护线程指在程序运行的时候在后台提供一种通用服务的线程,垃圾回收就是一种守护线程。
当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。
守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
守护线程,通过Thread.setDaemon(true)设置,需要在start之前设置。

public class TestJoin {
    public static void main(String[] args) {
        Thread previorThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new DemoThread(previorThread));
            thread.start();
            previorThread = thread;
        }
        for (int i = 0; i < 10; i++) {
            Thread daemonDemoThread = new Thread(new DaemonDemoThread(previorThread));
            daemonDemoThread.setDaemon(true);
            daemonDemoThread.start();
            previorThread = daemonDemoThread;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " main terminal.");
    }
    static class DemoThread implements Runnable {
        private Thread thread;
        public DemoThread(Thread thread){
            this.thread = thread;
        }
        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " DemoThread terminal.");
        }
    }
    static class DaemonDemoThread implements Runnable {
        private Thread thread;
        public DaemonDemoThread(Thread thread){
            this.thread = thread;
        }
        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " DaemonDemoThread terminal.");
        }
    }
}
//输出,很显然,守护线程没完事就被干掉了
main main terminal.
Thread-0 DemoThread terminal.
Thread-1 DemoThread terminal.
Thread-2 DemoThread terminal.
Thread-3 DemoThread terminal.
Thread-4 DemoThread terminal.
Thread-5 DemoThread terminal.
Thread-6 DemoThread terminal.
Thread-7 DemoThread terminal.
Thread-8 DemoThread terminal.
Thread-9 DemoThread terminal.
Thread-10 DaemonDemoThread terminal.
Thread-11 DaemonDemoThread terminal.
Thread-12 DaemonDemoThread terminal.
Thread-13 DaemonDemoThread terminal.
Thread-14 DaemonDemoThread terminal.

5. 在java中守护线程和本地线程区别?

上面似乎已经讲了,守护线程在所有本地线程执行完成就会被干掉。另外也有人说是守护线程是jvm创建的,本地线程是程序创建的,但是感觉似乎不准确。其实就是守护线程实际上是辅助本地线程进程操作的,帮助回收内存,一些监听输入输出啊之类的。

6. 为什么要有线程,而不是仅仅用进程?

(1)进程只能在一个时间干一件事,如果想同时干两件事或多件事(其实开俩进程干?但是开销比较大),进程就无能为力了。
(2)进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
(3)每个进程需要单独的空间内存地址等很多用户资源,损耗大,但是多线程是虚拟内存共享,线程间切换容易,线程等待时,不占用cpu。

7. 什么是可重入锁(ReentrantLock)?

(1)ReentrantLock和synchronized都是独占锁
(2)synchronized加锁解锁的过程是隐式的,ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
(3)new ReentrantLock(true)可以实现公平锁(按照等待时间越长越优先获得锁权限),如果传入false表示非公平锁(性能更好)。
(4)当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,但是ReentrantLock可以通过lockInterruptibly()响应中断。tryLock(),可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。

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

线程组(ThreadGroup)就是由线程组成的管理线程的类。
线程组ThreadGroup对象中的stop,resume,suspend会导致安全问题,主要是死锁问题,已经被官方废弃,多以价值已经大不如以前。
线程组ThreadGroup不是线程安全的,在使用过程中不能及时获取安全的信息。

9. 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

(1)悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制, 比如行锁,表锁等,读锁, 写锁等,都是在做操作之前先上锁。再比如Java 里面的同步原语synchronized 关键字的实现也是悲观锁。
(2)乐观锁:顾名思义,就是很乐观, 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition 机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS 实现的。
(3)读取频繁使用乐观锁,写入频繁使用悲观锁。

10. Java中用到的线程调度算法是什么?

(1)分时调度模型和抢占式调度模型。
(2)分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。
(3)java 虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程(或者按照饥饿状态顺序判断),使其占用 CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

11. 同步方法和同步块,哪个是更好的选择?

(1)同步方法:即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
(2)同步块:即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
(3)同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

12. run()和start()方法区别

(1)run()是实现Runable接口和继承Thread必须重写的,多线程实现的主方法,写的是具体的多线程执行的编码内容。
(2)start()方法是启动执行多线程的方法,表示真正开始运行多线程方法。

13. 如何控制某个方法允许并发访问线程的个数?

Semaphore创建时设定最多同时访问个数,acquire()申请新请求计数+1,达到设定数值后,内部会执行Thread的挂起操作,并把超过指定个数的线程放入到队列中等待唤醒。当release()后会唤醒队列里的前几个线程执行。

import java.util.concurrent.Semaphore;
public class SemaphoreTest {
    static Semaphore semaphore = new Semaphore(2, true);
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test();
                }
            }).start();
        }
    }
    public static void test() {
        try {
            //这块可以运行时可以看到实际时先打印出来,说明已经进入了方法了
            System.out.println("waiting"+Thread.currentThread().getName());
            //申请一个新的请求
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"进来了");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"走了");
        //释放一个请求
        semaphore.release();
    }
}

14. 在Java中wait和seelp方法的不同;

(1)wait()方法,是属于Object类,wait()出发后,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态
(2)sleep()方法,是属于Thread类的,sleep()方法的执行过程中,线程不会释放对象锁

15. Thread类中的yield方法有什么作用?

Thread.yield()可以暂停当前正在执行的线程对象,让其他有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

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

对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变, 例如String、基本类型的包装类(Long,Double等)、BigInteger和BigDecimal等。主要是线程安全,在多线程情况下不可改变,天生线程安全。

17. 谈谈wait/notify关键字的理解

都是继承自Object的方法,wait()方法用来将当前线程置入休眠状态,notify()用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁。

18. 为什么wait, notify 和 notifyAll这些方法不在thread类里面?

简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象。
那么复杂的说呢?因为锁是在对象头中的markWorld的中标记的,wait和notify直接理解为get和set方法,实际上就是对锁的操作,因此理解来说,这个应该放到Object类中,而不是在Thread类中。

19. 什么导致线程阻塞?

(1)Thread.sleep方法休眠一段时间,线程放弃cpu,一段时间后在恢复运行
(2)线程执行wait()方法,进入阻塞状态,直到notify或者notifyAll方法唤醒
(3)等待相关资源:线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如线程执行System.in.read()时,如果用户没有输入,则一直等待
(4)调用了yield方法后,同样会进入阻塞状态。
(5)suspend() 是让线程进入阻塞状态,没有resume()是不会恢复的。
(6)join()方法,被join的Thread要等待join的Thread执行完毕才能继续执行。

20. 讲一下java中的同步的方法

(1)当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,不被其他线程的调用,从而保证了该变量的唯一性和准确性。
(2)同步方法有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
(3)synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
(4)synchronized代码块同步关键代码
(5)使用特殊域变量(volatile)实现线程同步,但是不能完全避免同步问题,因为其不能保证原子操作,它只是在取得该变量值的时候是从内存中读取的而不是存缓存中读取。
(6)使用重入锁实现线程同步,通过lock和unlock来控制方法的执行锁。
(7)ThreadLocal的方式,由于每个线程都是独立的ThreadLocal,所以其实并不存在什么同步,以及冲突,每个线程都在自己的空间内执行。

public class Bank {
    //实际上这个是每个线程独立的ThreadLocal,并不共享,因此多个线程调用addMoney
    //或者subMoney的时候,都是在自己的独立线程内的,并不共享,不存在冲突
    private static ThreadLocal<Integer> count = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    // 存钱
    public void addMoney(int money) {
        count.set(count.get() + money);
        System.out.println(System.currentTimeMillis() + "存进:" + money);

    }
    //取钱
    public void subMoney(int money) {
        if (count.get() - money < 0) {
            System.out.println("余额不足");
            return;
        }
        count.set(count.get() - money);
        System.out.println(+System.currentTimeMillis() + "取出:" + money);
    }
    //查询
    public void lookMoney() {
        System.out.println("账户余额:" + count.get());
    }
}

21. 谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解

(1)当synchronized作用于普通方法是,锁对象是this
(2)当synchronized作用于静态方法是,锁对象是当前类的Class对象
(3)当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj
(4)类锁:synchronized修饰静态的方法或代码块
(5)方法锁:用synchronized修饰非静态方法
(6)重入锁:以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。synchronized和ReentrantLock 都是可重入锁。

//不可冲入锁,明显lock方法,锁了之后,别的都要等待,直到unlock才可以
public class Lock {
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException {
        while (isLocked) {
            wait();
        }
        isLocked = true;
    }
    public synchronized void unlock() {
        isLocked = false;
        notify();
    }
}
//可重入锁,判断非该线程等待,如果仍然是该线程执行,则计数加一
public class Lock {
    boolean isLocked = false;
    Thread lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();
        while (isLocked && lockedBy != thread) {
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;
    }
    public synchronized void unlock() {
        if (Thread.currentThread() == this.lockedBy) {
            lockedCount--;
            if (lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}

22. static synchronized 方法的多线程访问和作用

(1)重点在static上面,也就是这个方法是静态的,所有的线程过来都是执行这个静态方法,因此控制所有的并发都要走这一个方法。
(2)网上有很多解释,基本上都是在,不带static的是只控制到这个类的同一个实例的访问,带static的情况下就包括了所有的实例(即使不是同一个实例的情况)都被控制了访问。

23. 同一个类里面两个synchronized方法,两个线程同时访问的问题

(1)两个方法都没有synchronized修饰,调用时都可进入,方法A和方法B都没有加synchronized关键字时,调用方法A的时候可进入方法B,方法A和方法B都可以并行执行
(2)一个方法有synchronized修饰,另一个方法没有,调用时都可进入,方法A加synchronized关键字而方法B没有加时,调用方法A的时候可以进入方法B,但是方法A只能一个一个执行,方法B可以并行执行
(3)两个方法都加了synchronized修饰,一个方法执行完才能执行另一个,方法A和方法B都加了synchronized关键字时,调用方法A之后,必须等A执行完成才能进入方法B,方法B执行完了才能执行方法A,因此实际上是顺序执行的
(4)两个方法都是静态方法且还加了synchronized修饰,一个方法执行完才能执行另一个,方法A和方法B都是static静态方法,且都加了synchronized关键字,则调用方法A之后,需要等A执行完成才能进入方法B,方法B执行完了才能执行方法A,因此实际上是顺序执行的,在如果是同一个实例执行的情况下和(3)是一致的,但是如果不是同一个实例下,(3)也会并行执行,但是(4)还是调用方法A之后,需要等A执行完成才能进入方法B,方法B执行完了才能执行方法A,实际上是顺序执行的。

//可以自己运行下,去掉两个synchronized,保留一个,都带着,再带着static,各种情况运行下
public class TwoSynchronized {
    public synchronized void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + " method1 started");
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + " method1 end");
        }catch (Exception e){

        }
    }
    public synchronized void method2() {
        try {
            System.out.println(Thread.currentThread().getName()+" method2 started");
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName()+" method2 end");
        }catch (Exception e){

        }
    }
    public static void main(String[] args) throws InterruptedException {
        TwoSynchronized two = new TwoSynchronized();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    two.method1();
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    two.method2();
                }
            }).start();
        }
        Thread.sleep(100000);
    }
}

24. 你如何确保main()方法所在的线程是Java程序最后结束的线程?

可以使用 Thread 类的 join()方法来确保所有程序创建的线程在 main()方法退出前结束。之前讲过了。

25. 谈谈volatile关键字的作用

(1)保证可见性,当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。
(2)保证有序性,Java内存模型允许编译器和处理器对指令重排序以提高运行性能,只会对不存在数据依赖性的指令重排序。因此有在多线程情况下,会导致某些由于指令重排的问题,被声明为volatile可以禁止指令重排序。
(3)内存屏障,在每个volatile写操作的前面插入一个StoreStore屏障。在每个volatile写操作的后面插入一个StoreLoad屏障。在每个volatile读操作的前面插入一个LoadLoad屏障。在每个volatile读操作的后面插入一个LoadStore屏障。

LoadLoadL,Load1;LoadLoad;Load2:保证load1的读取操作在在load2及后续读取操作之前执行
StoreStore,Store1;StoreStore;Store2:在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStore,Load1;LoadStore;Store2:在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoad,Store1;StoreLoad;Load2:保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行

26. 谈谈ThreadLocal关键字的作用

(1)每个线程都会存在自己独立的ThreadLocal
(2)实际上是一个map,可以把一些在线程开始时候放入一些内容到ThreadLocal中,这样在线程的生命周期内,这些内容都可以直接从ThreadLocal中拿到(如果启动了多线程除外)。
(3)很多时候AOP的过程中需要从ThreadLocal中取出来一些东西

27. Java中Semaphore是什么?

Semaphore信号量,可以控制同时执行的线程数,创建时设定最多同时访问个数,acquire()申请新请求计数+1,达到设定数值后,内部会执行Thread的挂起操作,并把超过指定个数的线程放入到队列中等待唤醒。当release()后会唤醒队列里的前几个线程执行。

import java.util.concurrent.Semaphore;
public class SemaphoreTest {
    static Semaphore semaphore = new Semaphore(2, true);
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test();
                }
            }).start();
        }
    }
    public static void test() {
        try {
            //这块可以运行时可以看到实际时先打印出来,说明已经进入了方法了
            System.out.println("waiting"+Thread.currentThread().getName());
            //申请一个新的请求
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"进来了");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"走了");
        //释放一个请求
        semaphore.release();
    }
}

28. 什么是Callable和Future?

(1)Callable和Runnable是一类,但是Callable在线程执行完成,可以返回一些内容。
(2)Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable用于产生结果,Future用于获取结果。
(3)把Callable和Future结合起来有个FutureTask,可以执行Callable并且获取结果。下面来个样例:

public class TestCallableThread implements Callable<String> {
    public static void main(String[] args) throws Exception{
        TestCallableThread tct = new TestCallableThread();
        FutureTask<String> ft = new FutureTask<>(tct);
        new Thread(ft).start();
        while(true){
            if(ft.isDone()){
                System.out.println(ft.get());
                break;
            }else{
                System.out.println("waiting");
                Thread.sleep(1000);
            }
        }
    }
    @Override
    public String call() throws Exception {
        Thread.sleep(10000);
        return "Very Good";
    }
}

29. ThreadLocal、synchronized 和volatile 关键字的区别

(1)ThreadLocal是每个线程独立的一个Map,可以存储一些内容,和另外两个没啥关系
(2)synchronized用来修饰方法或者代码块的,是一种锁机制,增加了synchronized方法可以防止多线程同时执行该方法,如果是两个实例(同一个实例可以避免)的情况下的同一个synchronized方法被执行,实际上是还能够被同时执行的,如果需要控制就再增加上static就可以控制了
(3)volatile用来修饰变量的,在多线程中同步变量。表示如果要使用该内容,需要直接从内存中取值,而不能从缓存中取,避免了某些情况下的线程冲突

30. synchronized与Lock的区别

(1)synchronized是一个关键字,执行完毕释放,无法获得锁状态
(2)synchronized关键字是通过字节码指令来实现的
(3)synchronized关键字编译后会在同步块前后形成monitorenter和monitorexit两个字节码指令
(4)执行monitorenter指令时需要先获得对象的锁(每个对象有一个监视器锁monitor),如果这个对象没被锁或者当前线程已经获得此锁(也就是重入锁),那么锁的计数器+1。如果获取失败,那么当前线程阻塞,直到锁被对另一个线程释放
(5)执行monitorexit指令时,计数器减一,当为0的时候锁释放
(6)Lock是一个类,需要手动锁、解锁,否则就会死锁,可以通过trylock来知道有没有获取锁
(7)Lock是通过同步器AQS(AbstractQueuedSynchronized类)来实现的,AQS根本上是通过一个双向队列来实现的
(8)Lock的线程构造成一个节点,一个线程先尝试获得锁,如果获取锁失败,就将该线程加到队列尾部
(9)Lock:非公平锁的lock方法,调用的sync(NonfairSync和fairSync的父类)的lock方法

31. ReentrantLock 、synchronized和volatile比较

(1)ReentrantLock是一种锁,ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。new ReentrantLock(true)可以实现公平锁(按照等待时间越长越优先获得锁权限),如果传入false表示非公平锁(性能更好)。ReentrantLock可以通过lockInterruptibly()响应中断。tryLock()可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。
(2)synchronized用来修饰方法或者代码块的,是一种锁机制,增加了synchronized方法可以防止多线程同时执行该方法,如果是两个实例(同一个实例可以避免)的情况下的同一个synchronized方法被执行,实际上是还能够被同时执行的,如果需要控制就再增加上static就可以控制了
(3)volatile用来修饰变量的,在多线程中同步变量。表示如果要使用该内容,需要直接从内存中取值,而不能从缓存中取,避免了某些情况下的线程冲突

32. 在Java中CycliBarriar和CountdownLatch有什么区别?

(1)CountdownLatch: 一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。是并发包中提供的一个可用于控制多个线程同时开始某个动作的类,其采用的方法为减少计数的方式,当计数减至零时位于latch.Await()后的代码才会被执行,CountDownLatch是减计数方式,计数==0时释放所有等待的线程;CountDownLatch当计数到0时,计数无法被重置。
(2)CycliBarriar字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。 即:N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。CyclicBarrier是当await的数量到达了设置的数量的时候,才会继续往下面执行,CyclicBarrier计数达到指定值时,计数置为0重新开始。
(3)对于CountDownLatch来说,重点是那个“一个线程”,是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
(4)CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
CountDownLatch测试代码

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class TestCountDownLatch {
    public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        Executor executor = Executors.newFixedThreadPool(5);
        //测试阻塞其他线程
        new Thread(new MyRunnable(countDownLatch)).start();
        //为了测试效果进行线程休眠
        Thread.sleep(1000);
        for (int i = 1; i <= 5; i++) {
            countDownLatch.countDown();
            System.out.println("第" + i + "调用countDown方法结束");
            //为了测试效果进行线程休眠
            Thread.sleep(1000);
        }
        /*
         *测试阻塞主线程
         */
        for (int i = 1; i <= 5; i++) {
            new Thread(new MyRunnable1(countDownLatch, i + "")).start();
            Thread.sleep(1000);
        }
        try {
            System.out.println("主线程阻塞");
            countDownLatch.await();
            System.out.println("主线程继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class MyRunnable implements Runnable {
        CountDownLatch countDownLatch;
        public MyRunnable(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
        @Override
        public void run() {
            try {
                System.out.println("进入线程,即将进入阻塞状态");
                //调用await进行线程阻塞
                countDownLatch.await();
                System.out.println("线程进行执行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyRunnable1 implements Runnable {
        private CountDownLatch countDownLatch;
        private String mark;
        public MyRunnable1(CountDownLatch countDownLatch, String mark) {
            super();
            this.countDownLatch = countDownLatch;
            this.mark = mark;
        }
        @Override
        public void run() {
            System.out.println(mark + "号线程开始");
            try {
                //使线程休眠,看到更好的测试效果
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(mark + "号线程结束");
                //调用CountDownLatch的countDown方法进行次数减1
                countDownLatch.countDown();
            }
        }
        public CountDownLatch getCountDownLatch() {
            return countDownLatch;
        }
        public void setCountDownLatch(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
        public String getMark() {
            return mark;
        }
        public void setMark(String mark) {
            this.mark = mark;
        }
    }
}
//输出
进入线程,即将进入阻塞状态
第1调用countDown方法结束
第2调用countDown方法结束
第3调用countDown方法结束
第4调用countDown方法结束
第5调用countDown方法结束
线程进行执行...
1号线程开始
2号线程开始
3号线程开始
4号线程开始
5号线程开始
主线程阻塞
主线程继续执行

CyclicBarrier测试代码

import java.util.concurrent.*;

public class TestCyclicBarrier {
    static final Integer NUM = 5;
    public static void main(String[] args) throws InterruptedException {
        //实例CyclicBarrier对象
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM);//实例化一个固定大小线程池
        ExecutorService executor = Executors.newFixedThreadPool(NUM);
        for (int i = 1; i <= NUM; i++) {
            //执行线程
            executor.execute(new MyRunnale2(cyclicBarrier, i + "号"));
            //为了更好的效果,休眠一秒
            Thread.sleep(1000);
        }
        System.out.println("指令通知完成");
        //执行完毕需要关闭,否则主线程还在这卡着不关闭
        executor.shutdown();
    }
    static class MyRunnale2 implements Runnable {
        private CyclicBarrier cyclicBarrier;
        private String mark;
        public MyRunnale2(CyclicBarrier cyclicBarrier, String mark) {
            super();
            this.cyclicBarrier = cyclicBarrier;
            this.mark = mark;
        }
        @Override
        public void run() {
            System.out.println(mark + "进入线程,线程阻塞中...");
            try {
                //barrier的await方法,在所有参与者都已经在此barrier上调用await方法之前,将一直等待。
                cyclicBarrier.await();
                System.out.println(mark + "进入开始执行...");
                Thread.sleep(2000);//为了看到更好的效果,线程阻塞两秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(mark + "线程阻塞结束,继续执行...");
        }
        public CyclicBarrier getCyclicBarrier() {
            return cyclicBarrier;
        }
        public void setCyclicBarrier(CyclicBarrier cyclicBarrier){
            this.cyclicBarrier = cyclicBarrier;
        }
    }
}
//输出
1号进入线程,线程阻塞中...
2号进入线程,线程阻塞中...
3号进入线程,线程阻塞中...
4号进入线程,线程阻塞中...
5号进入线程,线程阻塞中...
5号进入开始执行...
1号进入开始执行...
2号进入开始执行...
3号进入开始执行...
4号进入开始执行...
指令通知完成
3号线程阻塞结束,继续执行...
2号线程阻塞结束,继续执行...
4号线程阻塞结束,继续执行...
5号线程阻塞结束,继续执行...
1号线程阻塞结束,继续执行...

33. CopyOnWriteArrayList可以用于什么应用场景?

(1)ArrayList在同时遍历和修改这个列表时,会抛出 ConcurrentModificationException。
(2)CopyOnWriteArrayList由于是copy出来的,因此不会出现这个错误。
(3)写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致gc。
(4)不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set操作后,读取到数据可能还是旧的,虽然 CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求。
(5)读写分离,读和写分开;最终一致性;使用另外开辟空间的思路,来解决并发冲突

34. Java中invokeAndWait 和 invokeLater有什么区别?

这个似乎没啥用,为啥问这个?
invokeAndWait()方法请求事件派发线程对组件进行相应更新,需要等待执行完成。
invokeLater()方法是异步调用更新组件的。

35. 多线程中的忙循环是什么?

忙循环就是用户循环让一个线程等待,不像传统方法使用wait()、sleep()或yield(),它们都放弃CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
按照自己的理解写了个忙循环

public class TestBusyWait {
    public static int wait = 3;
    public static void main(String[] args) throws Exception{
        new Thread(new MyRunnable()).start();
        for (int i = 0; i < 3; i++) {
            new Thread(new MyRunnable1()).start();
        }
        Thread.sleep(2000);
    }
    static class MyRunnable implements Runnable {
        public MyRunnable() {
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"空循环开始");
            while (wait != 0) {
            }
            System.out.println(Thread.currentThread().getName()+"空循环完成");
        }
    }
    static class MyRunnable1 implements Runnable {
        public MyRunnable1() {
        }
        @Override
        public void run() {
            wait--;
            try {
                System.out.println(Thread.currentThread().getName()+"开始了");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"完成了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

36. 怎么检测一个线程是否拥有锁?

在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。

public class TestHoldsLock {
    static Object o = new Object();
    public static synchronized void method1() {
        System.out.println("before method1 Thread.holdsLock(o)=="+Thread.holdsLock(o));
        synchronized (o) {
            System.out.println("runing method1 Thread.holdsLock(o)=="+Thread.holdsLock(o));
        }
        System.out.println("after method1 Thread.holdsLock(o)=="+Thread.holdsLock(o));
    }
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main Thread.holdsLock(o)=="+Thread.holdsLock(o));
        TestHoldsLock test = new TestHoldsLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();
    }
}
//输出
main Thread.holdsLock(o)==false
before method1 Thread.holdsLock(o)==false
runing method1 Thread.holdsLock(o)==true
after method1 Thread.holdsLock(o)==false

37. 死锁的四个必要条件?

(1)互斥条件:一个资源每次只能被一个进程使用;
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;

38. 什么是线程池,如何使用?

(1)什么是线程池:java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。实际上是对多个线程统一管理,避免了多次创建销毁操作,可以控制同时执行的最大线程数,防止执行过多式服务卡死。
(2)Executors工厂类可以创建多种不同的线程池
(3)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(4)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
(5)newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
(6)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

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

public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        //创建一个可重用固定线程数的线程池
        ExecutorService pool= Executors.newSingleThreadExecutor();
        //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
        Thread t1=new MyThread();
        Thread t2=new MyThread();
        Thread t3=new MyThread();
        Thread t4=new MyThread();
        Thread t5=new MyThread();
        //将线程放到池中执行;
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        //关闭线程池
        pool.shutdown();

    }
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" started");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" end");
        }
    }
}
//输出
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
pool-1-thread-1 started
pool-1-thread-1 end
//如果修改为
ExecutorService pool= Executors.newFixedThreadPool(2);
//可以看到下面输出同时只有2个在执行
pool-1-thread-1 started
pool-1-thread-2 started
pool-1-thread-1 end
pool-1-thread-2 end
pool-1-thread-1 started
pool-1-thread-2 started
pool-1-thread-2 end
pool-1-thread-1 end
pool-1-thread-2 started
pool-1-thread-2 end
//ScheduledThreadPoolExecutor测试类,定时执行任务
public class ScheduledThreadExecutorTest{
    public static void main(String[] args) {
        //下面这样写也可以,也可以自己new
        //ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
        ScheduledThreadPoolExecutor exec =new ScheduledThreadPoolExecutor(2);
        exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间执行
            @Override
            public void run() {
                System.out.println("===================");

            }}, 1000, 5000, TimeUnit.MILLISECONDS);
        exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间打印系统时间,证明两者是互不影响的
            @Override
            public void run() {
                System.out.println(System.nanoTime());
            }}, 1000, 2000, TimeUnit.MILLISECONDS);
    }
}
//输出,一直循环下去
===================
18116537502800
18118526972600
18120536395200
===================
18122534792700
18124535284500
===================

39. Java中interrupted 和 isInterrupted方法的区别?

(1)interrupt方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。线程中断仅仅是置线程的中断状态位,不会停止线程。视线程的状态为并做处理。一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
(2)interrupted查询当前线程(一定注意是当前线程)的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
(3)isInterrupted仅仅是查询当前线程的中断状态,并不清除原状态。

public class InterruptThreadTest extends Thread {
    @Override
    public  void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("执行中");
            if(this.isInterrupted()){
                System.out.println("first call outside thread.interrupted(): " + this.interrupted());
                System.out.println("first call outside thread.isInterrupted(): " + this.isInterrupted());
                System.out.println("second call outside thread.interrupted(): " + this.interrupted());
                System.out.println("second call outside thread.isInterrupted(): " + this.isInterrupted());
            }
        }

    }
    public static void main(String[] args) {
        InterruptThreadTest thread = new InterruptThreadTest();
        thread.start();
        thread.interrupt();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("status: " + thread.getState());
        System.out.println("thread is alive : " + thread.isAlive() );
    }
}
//输出
执行中
first call outside thread.interrupted(): true
first call outside thread.isInterrupted(): false
second call outside thread.interrupted(): false
second call outside thread.isInterrupted(): false
执行中
执行中
执行中
执行中
执行中
。。。重复
status: TERMINATED
thread is alive : false

40. Java线程池中submit() 和 execute()方法有什么区别?

(1)两个方法都可以向线程池提交任务,execute()方法的返回类型是 void,它定义在Executor 接口中。
(2) submit()方法可以返回持有计算结果的 Future 对象,它定义在ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。
(3)excute方法会抛出异常。sumbit方法不会抛出异常,调用Future.get()如果有异常可以抛出。
(4)excute入参Runnable,submit入参可以为Callable(可以返回结果的),也可以为Runnable(不能返回结果)。

public class SingleThreadExecutorTest {
    public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<?> runnableFuture = executorService.submit(() -> System.out.println("run"));// submit一个Runnable任务
        Future<?> callableFuture = executorService.submit(() -> "call");// submit一个Callable任务

        executorService.execute(() -> System.out.println("test"));

        System.out.println(runnableFuture.get());// Output null
        System.out.println(callableFuture.get());// Output call
    }
}
//输出
run
null
call
test

41. 对象锁和类锁是否会互相影响?

(1)我前面讲过的,带有synchronized的static方法,必须单独执行,无论是多个实例操作,只要访问统一方法,都需要等待迁移执行完毕
(2)但是当访问的是带synchronized的普通方法(对象锁),这种如果创建多个实例,互相是不干扰的,可以并行执行,但是同一个实例的情况下需要等待前一个执行完毕才能下个执行
(3)多个实体同时有类锁以及对象锁时候,也是互补干扰的,类锁实际锁的是静态的同步方法,如果不调用同步方法,就不会影响,也就是各自锁各自的。

public class TwoSynchronized {
    public static synchronized void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + " method1 started");
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + " method1 end");
        }catch (Exception e){

        }
    }
    public synchronized void method2() {
        try {
            System.out.println(Thread.currentThread().getName()+" method2 started");
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName()+" method2 end");
        }catch (Exception e){

        }
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            TwoSynchronized one = new TwoSynchronized();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    one.method1();
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            TwoSynchronized two = new TwoSynchronized();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    two.method2();
                }
            }).start();
        }
        Thread.sleep(100000);
    }
}
//输出,可以看出method1和method2同时执行了
//说明了类锁存在的情况下,对象锁还可以同时执行
Thread-0 method1 started
Thread-10 method2 started
Thread-11 method2 started
Thread-14 method2 started
Thread-15 method2 started
Thread-18 method2 started
Thread-19 method2 started
Thread-12 method2 started
Thread-13 method2 started
Thread-16 method2 started
Thread-17 method2 started
Thread-13 method2 end
Thread-11 method2 end
Thread-0 method1 end
Thread-12 method2 end
Thread-14 method2 end
Thread-15 method2 end
Thread-18 method2 end
Thread-19 method2 end
Thread-16 method2 end
Thread-17 method2 end
Thread-9 method1 started
Thread-10 method2 end
Thread-9 method1 end
Thread-7 method1 started
Thread-7 method1 end
Thread-6 method1 started
Thread-6 method1 end
Thread-3 method1 started
Thread-3 method1 end
Thread-2 method1 started
Thread-2 method1 end
Thread-8 method1 started
Thread-8 method1 end
Thread-5 method1 started
Thread-5 method1 end
Thread-4 method1 started
Thread-4 method1 end
Thread-1 method1 started
Thread-1 method1 end

42. Java中ConcurrentHashMap的并发度是什么?

ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16。注意注意,这个仅限于JDK1.7,在JDK1.8不用分段了,用的CAS方式重试去写入,所以不存在并发度了。

43. 什么是Java Timer类?如何创建一个有特定时间间隔的任务?

(1)java.util.Timer 是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer类可以用安排一次性任务或者周期任务。
(2)Timer有几个schedule方法,可以设定指定时间执行,或者指定间隔执行等操作。
(3)schedule(TimerTask task, Date time),其中TimerTask表示需要执行的任务。

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest01 {
    public TimerTest01(int time){
        Timer timer = new Timer();
        timer.schedule(new TimerTaskTest01(), time * 1000);
        System.out.println(time * 1000);
    }
    public static void main(String[] args) {
        System.out.println("timer begin....");
        // 3秒后执行内部具体任务
        new TimerTest01(3);
        System.out.println("timer end....");
    }
    static class TimerTaskTest01 extends TimerTask {
        public void run() {
            // 执行的任务
            System.out.println("Time's up!!!!");
        }
    }
}

44. SimpleDateFormat是线程安全的吗?

(1)SimpleDateFormat底层实现是用的Calendar,其中parse时候调用了CalendarBuilder.establish()方法,
(2)其中先后调用了cal.clear()与cal.set(),这样就把静态实体的值修改了,所以在多线程parse过程中导致了问题。
(3)那么如何避免呢:需要使用局部变量,每个线程种,都单独创建SimpleDateFormat;

                    try {
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH");
                        System.out.println(simpleDateFormat.parse("2020-01-01 15"));
                    } catch (ParseException e) {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }

(4)另外一种为parse的前后,SimpleDateFormat对加锁

                    try {
                        synchronized (simpleDateFormat){
                            simpleDateFormat.parse("2020-01-01");
                        }
                    } catch (ParseException e) {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }

(4)执行前加Lock.lock,执行完成后unlock
(5)通过ThreadLocal,每个线程独立的simpleDateFormat

//在线程run方法同级别创建threadlocal
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
//run方法中实现,每个线程过来,通过threadLocal中取出来,但是这种和上面(3)每个线程创建独立的SimpleDateFormat没有区别吧?
//如果这条有异议可以给我评论,咱们再讨论
                    try {
                        threadLocal.get().parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }

45. 用Java写一个会导致死锁的程序,你将怎么解决?

(1)两个线程分别都要锁定两个变量
(2)两个线程分别都获取锁定了一个变量,各自不同
(3)两个线程分别都等待对方释放另一个变量,结果谁也不会释放,就死锁了

public class TestDeadLock {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        Thread runnableA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("runnable a lock a");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("runnable a waiting b");
                    synchronized (b){
                        System.out.println("runnable a lock b");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        Thread runnableB =  new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (b){
                    System.out.println("runnable b lock b");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("runnable b waiting a");
                    synchronized (a){
                        System.out.println("runnable b lock a");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        runnableA.start();
        runnableB.start();
    }
}
//输出
runnable b lock b
runnable a lock a
runnable a waiting b
runnable b waiting a
//后边就一直互相等待了

(4)所以应该尽量避免多线程执行中,一个线程锁定多个
(5)如果必须要锁定多个,那么尽量要锁定顺序一致
(6)使用定时锁tryLock,一段时间以后timeout会释放锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TestDeadLock {
    static ReentrantLock a = new ReentrantLock();
    static ReentrantLock b = new ReentrantLock();
    public static void main(String[] args) {

        Thread runnableA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    a.tryLock(10, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("runnable a lock a failed");
                    return;
                }
                System.out.println("runnable a lock a");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("runnable a waiting b");
                try {
                    b.tryLock(10, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("runnable a lock b failed");
                    return;
                }
                System.out.println("runnable a lock b");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread runnableB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    b.tryLock(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("runnable b lock b failed");
                    return;
                }
                System.out.println("runnable b lock b");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("runnable b waiting a");
                try {
                    a.tryLock(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("runnable b lock a failed");
                    return;
                }
                System.out.println("runnable b lock a");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        runnableA.start();
        runnableB.start();
    }
}
//输出
runnable a lock a
runnable b lock b
runnable a waiting b
runnable b waiting a
runnable b lock a
runnable a lock b

46. 如何保证多线程读写文件的安全?

(1)读写互斥,写读互斥,写写互斥,只有读读相容(可以异步)
(2)FileInputStream、FileOutputStream、RandomAccessFile均可得到FileChannel对象
(3)FileChannel通过独占锁tryLock()锁定文件,用于写文件。
(4)FileChannel的tryLock(0, Long.MAX_VALUE, true)是非阻塞的,是共享锁,能被多个线程同时持有,它能禁止其他线程获取独占锁(防止写进程进来写文件),可用于读文件。
(5)FileChannel的lock()是阻塞的,在文件被锁定的情况下,会保持阻塞,直到获得该锁为止,实际上这个可以用作喝tryLock一样的情况,用于写文件。

//一般用这种方式,来循环获取锁,获取到锁之后开始写操作。
File file;
try {
    file = FileUtils.createFile(pathFile);
    } catch (IOException e) {
        e.printStackTrace();
        ioListener.onFail("文件创建失败,请检查路径是否合法以及读写权限");
        return;
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel fileChannel = fileOutputStream.getChannel();
//文件锁
FileLock fileLock = null;
while (true) {
     try {
         fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, true);//共享锁
         break;
     } catch (Exception e) {
         System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
     }
}

47. Java中的ReadWriteLock是什么?

(1)ReetrantReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。
(2)读锁使用共享模式;写锁使用独占模式;读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockTest {
    //默认是非公平锁
//    static ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
    //手工设置为公平锁
    static ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock(true);
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
//            new Thread(new RunnableDowngradeLock()).start();
            new Thread(new RunnableUpgradeLock()).start();
        }
    }
    //ReentrantReadWriteLock支持锁降级,但是一定记得显示的把两个锁都释放
    static class RunnableUpgradeLock implements Runnable{
        @Override
        public void run() {
            try {
                //先获取写锁
                rtLock.writeLock().lock();
                System.out.println(Thread.currentThread().getName() + " get writeLock");
                //再获取读锁,这种明显是降级,锁降级
                rtLock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + " get read lock");
                System.out.println(Thread.currentThread().getName() + " isWriteLocked "+ rtLock.isWriteLocked());
                System.out.println(Thread.currentThread().getName() + " isFair "+ rtLock.isFair());
            }finally {
                rtLock.writeLock().unlock();
                rtLock.readLock().unlock();
            }
        }
    }
    //ReentrantReadWriteLock不支持锁升级,下面这个执行会死锁
    static class RunnableDowngradeLock implements Runnable{
        @Override
        public void run() {
            try {
                //先获取读锁
                rtLock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + " get read lock");
                //再获取写锁,这种明显是升级了,锁升级
                rtLock.writeLock().lock();
                System.out.println(Thread.currentThread().getName() + " get writeLock");
            }finally {
                rtLock.writeLock().unlock();
                rtLock.readLock().unlock();
            }
        }
    }
}

48. lock原理

(1)Lock接口就这么几个方法

//尝试获取锁,获取成功则返回,否则阻塞当前线程
void lock()
//尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常 
void lockInterruptibly()
//尝试获取锁,获取锁成功则返回true,否则返回false 
boolean tryLock();
//尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常 
boolean tryLock(long time, TimeUnit unit)
//释放锁
void unlock()
//返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
Condition newCondition()

(2)一定有一个变量是表示(锁)状态的变量(假设0表示没有线程获取锁,1表示已有线程占有锁),该变量必须声明为voaltile类型(任何线程过来,都要直接从内存读取锁锁状态,不可能从缓存取);
(3)锁操作步骤:1>先获取锁状态,如果状态时0未锁定时候,修改锁状态为1,返回锁定成功;2>如果时1已锁定,进入等待队列,将自身阻塞,等待锁变为0未锁定的唤醒该线程,然后执行第一步;3>修改锁状态时,有可能失败,抢占式的,只有第一个修改的能成功,后边的线程修改都失败,同样进入等待队列,将自身阻塞,等待锁变为0时唤醒该线程,然后执行第一步
(4)解除锁操作步骤:1>修改锁状态,将1变为0,然后通知等待队列的第一个线程(如果是公平锁的话,第一个等待的),释放完成;2>被唤醒的锁,执行上面的所操作步骤。3>如果锁是已经被释放了,还来解除锁操作,这个时候直接抛异常失败。
(5)公平锁实现:锁操作时,判断队列是否存在等待的线程,1>如果有,就把自己加入队列,然后将自身阻塞;2>如果没有等待的线程,执行所操作步骤第一步。3>队列按照先入先出操作,所以唤醒的一定是先进入队列的线程
(6)非公平锁实现:实际上就是是否可以插入到队列前面,第一个被唤醒一定是队列头的等待线程,哪个线程抢占了队列头,谁就是第一个。
(7)Condition接口,实际上就是维护的等待队列,先进先出队列

49. ReentrantLock的内部实现

(1)其实上面Lock已经说的差不多了
(2)内部有抽象类Sync继承了AbstractQueuedSynchronizer。Sync有两个子类,一个FairSync另一个NonfairSync(ReentrantLock默认就是非公平锁)。
(3)这个比Lock多了acquire和tryAcquire的方法,还有tryRelease/getOwner/getHoldCount/isLocked等方法。
(4)tryAcquire方法分为公平锁/非公平锁的,公平锁:如果锁状态为0且等待队列没有等待的线程,则通过cas方式设置锁状态为1,设置拥有锁的线程为自己;如果锁状态大于1,判断拥有锁的是不是自己,是自己则将状态改为加1,返回成功,如果拥有锁的不是自己,返回失败。非公平锁:如果锁状态为0,就通过cas方式抢占锁,抢占成功设置状态,设置拥有锁的线程为自己,如果锁状态不为0,在判断拥有锁的是不是自己,是则+1,返回成功,不是则直接返回抢占失败。
(5)acquire方法实际上在AbstractQueuedSynchronizer(AQS)里,这个会调用(4)的tryAcquire方法,如果失败把自己加入到等待队列中,成功则直接返回成功。
(6)tryRelease就是tryAcquire的反向,为锁状态减去准备release的个数,也就是释放锁的个数。但是这个要判断解锁的是否为当前拥有锁的线程,否则直接失败。

50. 多线程有什么要注意的问题?

(1)需要创建大量多线程的程序,建议使用线程池,避免占用大量资源
(2)控制并发数量,过大可能导致服务器无法支撑,cpu就那么多,大量并发执行反而卡死了
(3)通过一些锁来控制并发导致的共享问题
(4)使用锁的过程中注意开发时候尽量别设计成循环等待的,容易死锁
(5)另外ReentrantLock一类的锁,lock后,一定要记得unlock,否则就死锁了
(6)有些线程任务执行完成一部分后,需要等待其他线程执行完成后再执行,这个时候可以通过sleep或者其他方式使其进入阻塞状态。
(7)有没有优先级?

51. 多线程断点续传原理

(1)多线程断点续传,实际上就是传递文件过程中断了,下次再重新传递的时候,记住中断的点,重新传递文件。
(2)但是如何记录断开的点呢?多线程的断点续传是通过判断客户端的文件长度来来判定传输的位置的,然后服务端设置到已经传输完毕的下一个位置开始继续传输
(3)然后拆分成了多份,多线程同时下载,下载过程中通过临时文件(拆成多少份需要多少个临时文件)记录当前下载完成的长度,多个文件都下载完成就完成了
(4)如果到了哪个位置即使断了,多份文件中记录着每份下载完成长度,重新执行多线程的时候仍然分成多份文件,每份文件独立判断是否完成,完成多少,跳转到未完成的位置开始继续下载

52. 断点续传的实现

这个是我网上找了几个例子,只有这个才是真正的断点续传,其他的帖子都是骗子,只有分份下载,压根没续传。

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

public class MutilDownload {
    private static String path = "https://mirrors.cnnic.cn/apache/tomcat/tomcat-8/v8.5.73/bin/apache-tomcat-8.5.73-deployer.tar.gz";
    private static final int threadCount = 3;
    private static int runningThread; //标识 正在运行的线程数量
    public static void main(String[] args) {
        try {
            URL url = new URL(path);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                int contentLength = conn.getContentLength();
                runningThread = threadCount;
                System.out.println("length" + contentLength);
                RandomAccessFile rafAccessFile = new RandomAccessFile("D://WUDownloadCache//apache-tomcat-8.5.73-deployer.tar.gz", "rw");
                rafAccessFile.setLength(contentLength);
                int blockSize = contentLength / threadCount;
                for (int i = 0; i < threadCount; i++) {
                    int startIndex = i * blockSize; //每个现成下载的开始位置
                    int endIndex = (i + 1) * blockSize - 1;// 每个线程的结束位置
                    if (i == threadCount - 1) {
                        //最后一个线程
                        endIndex = contentLength - 1;
                    }
                    new DownloadThread(startIndex, endIndex, i).start();
                }
            }
        } catch (Exception e) {
        }
    }

    private static class DownloadThread extends Thread {
        private int startIndex;
        private int endIndex;
        private int threadId;
        public DownloadThread(int startIndex, int endIndex, int threadId) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.threadId = threadId;
        }
        @Override
        public void run() {
            try {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                File file = new File("D://WUDownloadCache//"+threadId + ".txt");
                if(file.exists() && file.length() > 0) {
                    FileInputStream fis = new FileInputStream(file);
                    BufferedReader buff = new BufferedReader(new InputStreamReader(fis));
                    String lastPosition = buff.readLine();// 读取出来的内容就是上次下载的位置
                    int lastPos = Integer.parseInt(lastPosition);
                    System.out.println("线程id:" + threadId + "当前线程下载的位置:-----"+ lastPos);
                    startIndex = lastPos;
                    fis.close();
                    buff.close();
                }
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); //固定写法,请求部分资源
                int responseCode = conn.getResponseCode();  // 206表示请求部分资源
                if (responseCode == 206) {
                    RandomAccessFile rafAccessFile = new RandomAccessFile("D://WUDownloadCache//apache-tomcat-8.5.73-deployer.tar.gz", "rw");
                    rafAccessFile.seek(startIndex);
                    InputStream is = conn.getInputStream();
                    int len = -1;
                    byte[] buffer = new byte[1024];
                    int total = 0; // 代表当前线程下载的大小
                    while ((len = is.read(buffer)) != -1) {
                        rafAccessFile.write(buffer, 0, len);
                        total += len;
                        //断点续传, 保存当前线程下载的位置
                        int currentThreadPosition = startIndex + total; //当前线程下载的位置
                        // 存储当线程的下载五位置
                        RandomAccessFile raff = new RandomAccessFile("D://WUDownloadCache//"+threadId +".txt", "rwd");
                        raff.write(String.valueOf(currentThreadPosition).getBytes());
                        raff.close();
                    }
                    rafAccessFile.close();
                    System.out.println("线程" + threadId + "下载完成");
                    //删除临时文件
//                    File tempFile = new File(threadId + ".txt");
//                    if(tempFile.exists()) {
//                        file.delete();
//                    }
                    synchronized (MutilDownload.class) {
                        runningThread--;
                        if(runningThread == 0) {
                            for (int i = 0; i < threadCount; i++) {
                                File deleteFile = new File(i + ".txt");
                                deleteFile.delete();
                            }
                        }
                    }
                }
            } catch (Exception e) {
            }
        }
    }
}

53. 实现生产者消费者模式

简易版本,只有发送和消费,中间用queue实现。


import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;

import static java.lang.Thread.*;

public class ProducerAndConsumer {
    public static final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(20);
    public static void main(String[] args) {
        new Thread(new Producer()).start();
        try {
            sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Consumer()).start();
    }
    static class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 25; i++) {
                try {
                    queue.add(i + "");
                }catch(Exception e){
                    e.printStackTrace();
                    if(e.getClass().equals(IllegalStateException.class)&&e.getMessage().equals("Queue full")){
                        waitTest();
                    }
                }
            }
        }
    }
    static class Consumer implements Runnable {
        @Override
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String result = queue.poll();
                System.out.println(result);
                if(Objects.isNull(result)){
                    notifyTest();
                }
            }
        }
    }
    public static synchronized void notifyTest() {
        ProducerAndConsumer.class.notify();
    }
    public static synchronized void waitTest() {
        try {
            ProducerAndConsumer.class.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

54. 用Java实现阻塞队列

(1)什么是阻塞队列:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
(2)主要就是存入和取出的处理,存入时候需要判断是否队列已经满了,满了需要等待队列有数据被取出才能放入,如果存入数据要通知等待取数据的队列;取出时候,要判断队列是否为空,如果空了就要等待队列有数据再取出,如果取出数据了要通知等待存入数据的队列。
(3)另外注意一定要在存/取的时候加锁,避免线程并发存储或者取出数据导致的问题


import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

public class BlockingQueueTest<E> {
    public static void main(String[] args) {
        BlockingQueueTest<String> test = new BlockingQueueTest<String>(10);
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 15; i++) {
                    test.add(i+"");
                    System.out.println("add "+ i );
                }
            }
        }).start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("get started");
                while (true) {
                    String get = test.get();
                    System.out.println("get "+ get );
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private LinkedList<E> list = new LinkedList<>();
    private static int MAX_SIZE;
    private final static Object lock = new Object();
    public BlockingQueueTest(int size) {
        this.MAX_SIZE = size;
    }
    //原子性操作数字的加减
    private AtomicInteger count = new AtomicInteger(0);
    public synchronized void waitTest() {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void add(E e) {
        synchronized (lock) {
            //满了
            while(MAX_SIZE == count.get()){
                waitTest();
            }
            list.add(e);
            //count++
            count.incrementAndGet();
            lock.notify();
        }
    }
    public E get(){
        synchronized (lock) {
            //满了
            while(0 == count.get()){
                waitTest();
            }
            E e = list.removeFirst();
            //count--
            count.decrementAndGet();
            lock.notify();
            return e;
        }
    }
}

55. BlockingQueue介绍:

(1)上边54代码讲的够详细了,下面主要讲讲他的方法吧
(2)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)      
(3)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败
(4)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续
(5)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
(6)poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
(7)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
(8)drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

56. Java中的同步集合与并发集合有什么区别?

(1)同步集合,就是集合的方法都有synchronized的集合,线程安全,效率不高。
(2)并发集合,Concurrent下面的集合,比如ConcurrentHashMap通过分段的方式控制多线程并发冲突,也是线程安全,效率较高。

56. 谈谈NIO的理解


汤太咸
3 声望2 粉丝

程序员一枚,也爱读书,也爱理财,还喜欢把学到的读到的总结出来写点东西,写的不难,希望大家喜欢。