Java基础知识-线程
1、在 Java 中要想实现多线程代码有几种手段?
1. 一种是继承 Thread 类
2. 另一种就是实现 Runnable 接口
3. 最后一种就是实现 Callable 接口
4. 第四种也是实现 callable 接口,只不过有返回值而已
2、Thread 类中的 start() 和 run() 方法有什么区别?
start()方法被用来启动新创建的线程,而且 start()内部调用了 run()方法,这和直接调用 run()方法的效果不一样。当你调用 run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
3、Java 中 notify 和 notifyAll 有什么区别?
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它 才有用武之地。而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一 个线程能继续运行。
4、Java 多线程中调用 wait() 和 sleep()方法有什么不同?
Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放 CPU 资源或者让当前线程停止执行一段时间,但不会释放锁。
5、什么是线程安全
多个线程同时运行一段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。同一个实例对象在被多个线程使用的情况下也不会出现计算失误,也是线程安全的,反之则是线程不安全的。
6、Java中的 volatile 变量是什么?
Volatile: 一个共享变量(类的成员变量、类的静态成员量)被volatile修饰之后,那么就具备了两层语义:
● a.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
● b.禁止进行指令重排序。但是它并不能保证操作的原子性。
应用场景:在只涉及可见性,针对变量的操作只是简单的读写(保证操作的原子性)的情况下可以使用volatile来解决高并发问题,如果这时针对变量的操作是非原子的操作,这时如果只是简单的i++式的操作,可以使用原子类atomic类来保证操作的原子性(采用CAS实现),如果是复杂的业务操作,那么舍弃volatile,采用锁来解决并发问题(synchronized或者Lock)。
7、线程的状态?
实线程一般具有五种状态,即创建、就绪、运行、阻塞、终止。
1. 在这里插入图片描述新建( new ):新创建了一个线程对象。
2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象的start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ),执行程序代码。
4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:
● a.等待阻塞:运行( running )的线程执行 o.wait ()方法,JVM 会把该线程放 入等待队列( waitting queue )中。
● b.同步阻塞:运行( running )的线程在获取对象的同步锁时, 若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
● c.其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t.join ()方法,或者发出了 I / O 请求时,JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
1. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
8、实现线程同步有三种方式?
1. 同步代码块:在代码块上加上“synchronized”关键字的话,则此代码块就称为同步代 码块。
//同步代码块格式:
synchronized(监视对象){
//需要同步的代码 ;
}
解释:监视对象有三种:对象、String、.class 文件(只要是不变的对象都可以做监 视对象)
1. 同步方法
//同步方法定义格式:
synchronized 方法返回值 方法名称(参数列表){
}
//在方法上加 synchronized,是把当前对象做为监视器
1. 同步锁
Lock lock = new ReentrantLock();//(可以在类中直接 new)
lock.lock(); //中间的代码块进行加锁 lock.unlock();
9、Java中的锁有几种方式?
1. Synchronized
2. Lock
Synchronized的局限性:
● a. 如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能等待。(不能主动释放锁)
● b.当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。(不分情况,一律锁死)
10、Lock的几个实现类?
● ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。
● ReadWriteLock,顾名思义,是读写锁。它维护了一对相关的锁 ——“读取锁”和“写入锁”,一个用于读取操作,另一个用于写入操作。他的两个实现类读锁readerLock和写锁writerLock。
11、线程间通信的几种实现方式?
1. 使用 volatile 关键字。基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式。
2. 使用Object类的wait() 和 notify() 方法。Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
12、synchronized 和 Lock 的区别和应用场景?
1. Lock 是接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
5. Lock 可以提高多个线程进行读操作的效率。
6. Lock 能完成 Synchronized 所实现的所有功能在性能上来说,如果竞争资源不激烈,Synchronized 要优于 Lock,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
13、为什么要用线程池?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数 有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
从JDK1.5 开始,JavaAPI 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个 任务;数目固定的线程池或者是缓存线程池。
14、如何创建线程池?
1. 线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。 Executors:线程池创建工厂类
2. 自己根据创建线程池的需求来 new 对象(使用) 注意:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。 2)CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
建议自己通过 new 关键字创建 newThreadPoolExecutor