java 线程安全解决方案-java线程池安全停止
什么是线程安全
当使用多线程编程时,线程安全是计算机程序代码中的一个概念。 在一个由多个线程并行执行并共享数据的程序中,线程安全的代码会通过同步机制保证每个线程都能正常正确执行,不会出现数据污染等事故。 (作用:保证各线程正常正确执行)
2、线程和进程有什么区别?
进程和线程之间的主要区别在于它们是管理操作系统资源的不同方式。 一个进程有独立的地址空间。 一个进程崩溃后,不会影响保护模式下的其他进程,线程只是一个进程中不同的执行路径。 线程有自己的栈和局部变量,但线程之间没有单独的地址空间。 一个线程的死亡意味着整个进程的死亡,所以多进程程序比多线程程序更健壮,但是在进程间切换时,消耗的资源更大,效率更低。但是,对于一些并发操作,需要同时执行和共享某些变量,只能使用线程,不能使用进程。
3、Java中线程的实现方式有哪些?
1)继承Thread类实现多线程
2)实现Runnable接口实现多线程
3)使用ExecutorService、Callable、Future实现多线程返回结果
4、启动线程方法start()和run()有什么区别?
只有在调用start()方法时,才会显示出多线程的特性,不同线程的run()方法中的代码会交替执行。 如果只是调用run()方法,代码还是同步执行的。 您必须等待一个线程的 run() 方法中的所有代码执行完毕,然后另一个线程才能执行其 run() 方法中的代码。
5.如何终止一个线程?如何优雅地终止线程
方法一:中断停止线程休眠时中断
方法二:带易失性标志位的停止方法
线程终止的方式主要有两种,一种是interrupt,一种是volatile。 两个相似的地方都是通过标记实现的,但是interrupt是中断信号传输,是基于系统级别的,不受blocking的影响。 对于 volatile ,我们利用它的可见性来标记一个标量,但是当出现阻塞等情况时,我们无法及时通知。
线程的生命周期有哪些状态? 它们之间如何流动?
NEW:毫无疑问,表示线程刚刚创建,还没有启动。
RUNNABLE:表示线程已经触发start()方法调用,线程正式启动,线程处于运行状态。
BLOCKED:表示线程被阻塞,等待获取锁。 如果synchronized、lock等关键字占据了临界区,那么一旦获取到锁,就会继续以RUNNABLE状态运行。
WAITING:表示线程处于无限等待状态,等待特殊事件再次唤醒,比如通过wait()方法等待的线程等待notify()或notifyAll()方法,线程等待通过 join() 方法 目标线程运行后被唤醒。 一旦线程被相关事件唤醒,线程进入RUNNABLE状态,继续运行。
TIMED_WAITING:表示线程进入了限时等待,比如sleep(3000),等待3秒后,线程会再次以RUNNABLE状态继续运行。
TERMINATED:表示线程执行完毕后,将被终止。需要注意的是,线程一旦被start方法启动,就不能再回到初始的NEW状态,线程终止后,也不能再返回到 RUNNABLE 状态。
线程中的 wait() 和 sleep() 方法有什么区别?
sleep 方法和 wait 方法都可以用来让出 CPU 一段时间。 不同的是,如果线程持有对象的监听,sleep方法不会放弃对象的监听,wait方法会放弃对象的监听。
多线程同步的方法有哪些?
synchronized关键字、Lock锁实现、分布式锁等。
什么是死锁? 如何避免死锁?
死锁是指两个线程正在等待对方释放对象锁。
多线程之间如何通信?
等待/通知
线程如何获取返回结果呢?
实现可调用接口
暴力关键字有什么作用?
理解volatile关键字作用的前提是理解Java内存模型。 volatile关键字主要有两个作用: 1)多线程主要围绕可见性和原子性这两个特性展开。 用volatile关键字修饰的变量,保证了其在多个线程间的可见性,即每次读取volatile变量,都必须是最新的数据 2)底层代码执行并不像我们看到的高级语言那么简单——Java程序,它的执行是Java代码-->字节码-->根据字节代码执行相应的C/C++代码-->C/C++代码被编译成汇编语言-->与硬件电路交互。 实际上,为了获得更好的性能,JVM 可能会重新排序指令。 可能会出现一些意想不到的问题。 使用 volatile 将禁止语义重新排序。 当然,这也在一定程度上降低了代码执行效率。 从实用的角度来看,volatile的一个重要作用就是与CAS结合保证原子性。 详情请参考java.util。 concurrent.atomic 包下的类,比如 AtomicInteger
新建三个线程T1、T2、T3,如何保证它们按顺序执行?
使用连接方法。
如何控制只有3个线程同时运行?
使用信号量。
为什么要使用线程池?
如果不使用线程池,每个线程都要通过new Thread(xxRunnable)创建并运行一个线程。 并且该方案达到了最佳效率。 当线程数达到一定数量后,系统的CPU和内存资源就会被耗尽,从而导致GC的频繁回收和停顿,因为每一次线程的创建和销毁都会消耗系统资源。 如果为每个任务都创建线程,无疑是一个很大的性能瓶颈。因此,线程池中线程的多路复用,大大节省了系统资源。 当一个线程在一段时间内不再有任务需要处理时,它会自动销毁,而不是留在内存中
几种常用的线程池并说说它们的工作原理。
什么是线程池?
线程池是一个有线程的池。 我们可以把要执行的多线程交给线程池去处理。 和连接池的概念一样,通过维护一定数量的线程池,可以复用多个线程。
线程池的好处?
我们知道,如果不使用线程池,每个线程都要通过new Thread(xxRunnable)来创建并运行一个线程。 当系统和程序达到最佳效率时,当线程数达到一定数量时,系统的CPU和内存资源就会被耗尽,频繁的收集和停顿也会由GC引起,因为每一个线程创建和销毁,都会消耗系统资源。 如果为每个任务创建线程,这无疑是一个很大的性能瓶颈。 因此,线程池中线程的多路复用大大节省了系统资源。 当一个线程在一段时间内不再有任务需要处理时,它会自动销毁,而不是留在内存中。
线程池核心类?
在java.util.concurrent包中,我们可以找到线程池的定义java 线程安全解决方案,其中ThreadPoolExecutor是我们线程池的核心类。 首先我们看一下线程池类的主要参数。 如何提交一个线程 比如可以先定义一个固定大小的线程池ExecutorService es = Executors.newFixedThreadPool(3); 提交线程 es.submit(xxRunnble); es.execute(xxRunnble); 提交和执行有什么区别? execute没有返回值,如果在不知道线程结果的情况下使用execute方法,性能会好很多。 submit返回一个Future对象,如果想知道线程的结果,使用submit提交,在主线程中可以通过Future的get方法捕获线程中的异常。 如何关闭线程池 es.shutdown(); 不会接受新的任务,之前提交的任务在执行完成之前不会关闭线程池。 es.shutdownNow(); 不再接受新的任务,尝试停止池中的任务然后关闭线程池,并返回所有未处理线程的列表
线程池启动线程submit()和execute()方法有什么区别?
execute没有返回值,如果在不知道线程结果的情况下使用execute方法,性能会好很多。 submit返回一个Future对象,如果想知道线程的结果,使用submit提交,在主线程中可以通过Future的get方法捕获线程中的异常。
CyclicBarrier 和 CountDownLatch 的区别?
两个看起来很像的类,都在java.util.concurrent下,可以用来表示代码运行到某个点。 两者的区别在于:
1、CyclicBarrier的一个线程运行到某个点后,该线程停止运行,直到所有线程都运行到这个点,然后所有线程重新运行; CountDownLatch不是,一个线程运行到某个点后,给一个值-1,线程继续运行
2.CyclicBarrier只能调用一个任务,CountDownLatch可以调用多个任务
3.CyclicBarrier可以复用,CountDownLatch不能复用。 如果计数值为 0,则不能再使用 CountDownLatch
什么是活锁、饥饿、无锁、死锁?
死锁、活锁、饥饿与多线程活动时出现的运行阻塞障碍问题有关。 如果一个线程出现这三种情况,即该线程不再活跃,不能继续正常执行。
僵局
死锁是多线程中最糟糕的情况。 多个线程互相占用对方的资源锁,互相等待对方释放锁。 僵局。 比如,A同学抢了B同学的笔,B同学抢了A同学的书。 两人都拿了对方的东西,让对方先还给自己。 就这样继续吵着等着对方回来。 但他们无法解决。 老师让他们知道事情后还给对方,让他们在外力的介入下解决。 当然,这只是一个例子。 他们可以在没有老师的情况下解决它。 计算机不像人。 在这种情况下,如果没有外部干预,它将继续被阻止。
活锁
livelock这个概念大家应该很少听说或者理解,但是在多线程中确实存在。 活锁与死锁正好相反。 死锁是指大家不能拿到资源,互相占用对方的资源,而活锁是指拿到了资源却互相释放,不执行。 多线程发生相互谦让的时候,主动释放资源给其他线程使用,使得这个资源在多个线程之间跳来跳去却无法执行。 这是活锁。
饥饿
我们知道在多线程执行中有线程优先级这样的东西。 优先级高的线程可以跳入队列先执行。 这样,如果高优先级的线程不断抢占低优先级线程的资源,低优先级的线程就无法执行。 这是饥饿。 当然,也有饿死的情况。 一个线程一直在占用资源,其他线程无法执行。 与死锁不同,饥饿在未来的一段时间内仍然可以执行,比如占用资源的线程。 完成并释放资源。
没有锁
无锁,即资源上没有锁,即所有线程都可以访问和修改同一个资源,但同时只有一个线程修改成功。 无锁的典型特点是一个修改操作在循环中进行,线程会不断尝试修改共享资源。 如果没有冲突,则修改成功并退出,否则继续进行下一次循环尝试。 因此,如果多个线程修改同一个值,一定有一个线程可以修改成功,而其他修改失败的线程会不断重试,直到修改成功。 在上一篇文章中介绍了JDK的CAS的原理和应用,也就是无锁的实现。可见无锁是一个非常好的设计。 不会造成线程跳转问题。 锁使用不当肯定会导致系统性能出现问题。 虽然lock-free不能完全替代锁,但是lock-free在某些时候是非常高效的
什么是原子性、可见性、有序性?
原子性、可见性和顺序是多线程编程中最重要的知识点。 由于多线程的复杂性,如何让每个线程看到正确的结果就显得非常重要。
原子性
原子性是指一个线程的操作不能被其他线程打断,同时只有一个线程对一个变量进行操作。 在多线程的情况下,每个线程的执行结果不会受到其他线程的干扰。 比如多个线程同时对同一个共享成员变量执行n++100次java 线程安全解决方案,如果n的初始值为0,那么n的最终值应该是100,这样就不会互相干扰了,这就是传说中的原子性。 但是n++不是原子操作,应该使用AtomicInteger来保证原子性。
能见度
可见性是指一个线程是否修改了共享变量的值,其他线程是否可以看到共享变量的修改值。 在单线程中肯定不会出现这样的问题,单线程必须读取最新的15值,但在多线程编程中就不一定了。 每个线程都有自己的工作内存。 线程首先从主内存中读取共享变量的值到工作内存中,形成一个副本。 计算完成后,将副本的值刷回主存,从读取到最后刷入。 主存是一个进程。 当主存还没有被刷新时,此时其他线程是不可见的,所以其他线程从主存中读取的值是修改前的旧值。 比如CPU缓存优化、硬件优化、指令重排和JVM编译器优化等,都会有可见性问题。
井然有序
我们都知道程序是按照代码顺序执行的,在单线程中是这样,但是在多线程的情况下就不是这样了。 为了优化程序执行,提高CPU处理性能,JVM和操作系统都会对指令进行重新排列,这意味着前面的代码不一定在后面的代码之前执行,也就是后面的代码可能被插入到previous code 之前执行过的,只要不影响当前线程的执行结果即可。 因此,指令重排只会保证当前线程的执行结果一致,但指令重排必然会影响多线程的执行结果。重排虽然优化了性能,但也会遵守一些规则,不能随意排序,但是重新排序会影响多线程执行的结果