进程和线程的区别?多线程有什么好处? 进程:正在进行中的程序(直译)。 线程:就是进程中一个负责程序执行的控制单元(执行路径)
编写多线程程序有几种实现方式? 一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。Runnable不是线程,是线程里运行的代码
class Demo implements Runnable// extends Fu
//准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行
{
public void run() {
show();
}
public void show() {
for (int x = 0; x < 20; x++) {
System.out.println(Thread.currentThread().getName() + "....." + x);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
Java并发Concurrent包——Callable/Future/FutureTask解析
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。 省了自己写回调了
public interface Callable<V> {
// 计算结果,如果无法计算结果,则抛出一个异常
V call() throws Exception;
}
FutureTask、RunnableFuture相比runnable有结果的返回
public static class CountTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程开始计算");
Thread.sleep(5000);
System.out.println("子线程结束计算,共用时5秒");
return 100;
}
}
ExecutorService executor = Executors.newCachedThreadPool();
// Future的使用
//Future<Integer> result = executor.submit(new CountTask());
// FutureTask的使用
FutureTask<Integer> futureTask = new FutureTask<>(new CountTask());
executor.submit(futureTask);
executor.shutdown();
// Future获取结果
//Integer i = result.get();
// futureTask获取结果
Integer i = futureTask.get();
ThreadLocal 原理分析
线程的基本状态以及状态之间的关系? 创建并运行线程:
可以通过Thread类的isAlive方法来判断线程是否处于就绪/运行状态:当线程处于就绪/运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于阻塞状态,也可能处于停止状态。
join join(插队):一种特殊的wait,当前运行线程调用另一个线程的join方法,当前线程进入阻塞状态直到另一个线程运行结束等待该线程终止。 注意该方法也需要捕捉异常。
线程的sleep()方法和yield()方法有什么区别?
wait 和 sleep 区别? sleep来自Thread类,和wait来自Object类 调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁 sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒
wait和notify wait:使一个线程处于等待(阻塞/冻结)状态,并且释放所持有的对象的锁,让其他线程可以进入Synchronized数据块,当前线程被放入对象等待池中; notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态; 用法 在未达到目标时 wait() 用 while 循环检查 设置完成后 notifyAll() wait() 和 notify() / notifyAll() 都需要放在同步代码块里
为什么stop()方法被废弃而不被使用呢? 原因是stop()方法太过于暴力,会强行把执行一半的线程终止。这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下。 使用boolean类型的变量,来终止线程 或者使用interrupt
启动一个线程是调用run()还是start()方法? 启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。
一个线程如果出现了运行时异常会怎么样 如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器(锁),那么这个对象监视器会被立即释放
守护进程 t1.start(); t2.setDaemon(true); // setDameon是守护线程,可以理解为后台线程,你停我也停。前台必须手动结束 t2.start();
多次start一个线程会怎么样 会抛出java.lang.IllegalThreadStateException 线程状态非法异常
线程安全问题的本质 在多个线程访问共同的资源时,在某⼀个线程对资源进行写操作的中途(写入已经开始,但还没结束),其他线程对这个写了一半的资源进行行了读操作,或者基于这个写了一半的资源进行了写操作,导致出现数据错误。 锁机制的本质 通过对共享资源进行访问限制,让同一时间只有一个线程可以访问资源,保证了数据的准确性。
不论是线程安全问题,还是针对线程安全问题所衍生出的锁机制,它们的核心都在于共享的资源,而不是某个方法或者某几行代码。
解决思路 就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。 使用锁机制:synchronized 或 lock 对象 同步的好处:解决了线程的安全问题。 同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。 同步的前提:同步中必须有多个线程并使用同一个锁。
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 不能,一个对象的一个synchronized方法只能由一个线程访问。
同步函数和同步代码块的区别 同步函数的锁是固定的this。同步代码块的锁是任意的对象。建议使用同步代码块。 静态方法的同步函数的锁是class类,不是this对象,静态方法不属于某个对象,多个类是共享的 private final Object monitor1 = new Object(); 一般用object当锁就行了
简述synchronized 和Lock的异同?
读写锁
finally 的作用:保证在方法提前结束或出现 Exception 的时候,依然能正常释放锁。
一般并不会只是使用 Lock ,而是会使用更复杂的锁,例如ReadWriteLock
public class ReadWriteLockDemo implements TestDemo {
private int x = 0;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();
private void count() {
writeLock.lock();
try {
x++;
} finally {
writeLock.unlock();
}
}
private void print(int time) {
readLock.lock();
try {
System.out.print(x + " ");
} finally {
readLock.unlock();
}
}
@Override
public void runTest() {
}
}
悲观锁
Java内存模型 Java内存模型定义了一种多线程访问Java内存的规范。 Java内存模型将内存分为了主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 有序性:即程序执行的顺序按照代码的先后顺序执行。 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
volatile关键字的作用 volatile关键字的作用主要有两个:
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。保证了每次读取到volatile变量,一定是最新的数据。
AtomicInteger atomicInteger = new AtomicInteger(0);
...
atomicInteger.getAndIncrement();//++count
volatile为什么不能保证原子性 一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。 线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。 问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。
Synchronized和Volatile的比较
线程池的好处
线程池相关方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
线程池的参数
线程工厂
,如何去创建线程的,可以自定义线程创建的执行者,他们有适当的线程名称、优先级,甚至他们还可以守护进程。
LinkedBlockingQueue 在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列(先进先出)。Java提供的线程安全的Queue可以分为阻塞队列(安全)和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子ConcurrentLinkedQueue(也是安全的,cas)。
LinkedBlockingQueue 是线程安全的队列,通过ReentrantLock保证的。(链表实现的队列) 由于LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。 2 的 31 次方 - 1
Java自己的线程池
自定义线程池ThreadPoolExecutor 自定义ThreadManager类管理多线程,例如
开启线程数一般是cpu的核数* 2+1
Executors弊端 Executors的4个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 Executors的4个功能线程有如下弊端: FixedThreadPool和SingleThreadExecutor:主要问题是堆积的请求处理队列均采用LinkedBlockingQueue,可能会耗费非常大的内存,甚至OOM。 CachedThreadPool和ScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
newFixedThreadPool 创建一个固定线程数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。 应用场景:控制线程最大并发数。
newSingleThreadExecutor 创建一个只有一个线程的线程池,每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待线程处理完再依次处理任务队列中的任务
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
特点:只有1个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。 应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作、文件操作等。
newCachedThreadPool 创建一个可以根据实际情况调整线程池中线程的数量的线程池
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
特点:无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列为不存储元素的阻塞队列。 应用场景:执行大量、耗时少的任务。 newScheduledThreadPool 创建一个可以定时或者周期性执行任务的线程池
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
特点:核心线程数量固定,非核心线程数量无限,执行完闲置10ms后回收,任务队列为延时阻塞队列。 应用场景:执行定时或周期性的任务。
多个线程在处理同一资源,但是任务却不同。 生产者和消费者在同一时间段内共用同一个存储空间,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况: 存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁。
wait()和notify()方法的实现 缓冲区满和为空时都调用wait()方法等待,当生产者生产了一个产品或者消费者消费了一个产品之后会唤醒所有线程。
public class Test1 {
private static Integer count = 0;
private static final Integer FULL = 10;
private static String LOCK = "lock";
public static void main(String[] args) {
Test1 test1 = new Test1();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (LOCK) {
while (count == FULL) {
try {
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "生产者生产,目前总共有" + count);
LOCK.notifyAll();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK) {
while (count == 0) {
try {
LOCK.wait();
} catch (Exception e) {
}
}
count--;
System.out.println(Thread.currentThread().getName() + "消费者消费,目前总共有" + count);
LOCK.notifyAll();
}
}
}
}
}
指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
public class DeadlockTest {
public static void main(String[] args) {
String str1 = new String("资源1");
String str2 = new String("资源2");
new Thread(new Lock(str1, str2), "线程1").start();
new Thread(new Lock(str2, str1), "线程2").start();
}
}
class Lock implements Runnable {
private String str1;
private String str2;
public Lock(String str1, String str2) {
super();
this.str1 = str1;
this.str2 = str2;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "运行");
synchronized (str1) {
System.out.println(Thread.currentThread().getName() + "锁住"+ str1);
Thread.sleep(1000);
synchronized (str2) {
// 执行不到这里
System.out.println(Thread.currentThread().getName()
+ "锁住" + str2);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程1运行
线程1锁住资源1
线程2运行
线程2锁住资源2
线程1运行线程1锁住资源1,线程2运行线程2锁住资源2,两个线程是同时执行的,线程1锁住了资源1,线程2锁住了资源2,线程1企图锁住资源2,但是资源2已经被线程2锁住了,线程2企图锁住资源1,但是资源1已经被线程1锁住了,然后就死锁了。 你的同步(锁)有我的同步,我的同步有你同步
要出现死锁问题需要满足以下条件
解决方法