1.公平&非公平
1.1卖票案例(非公平)
class Ticket
{
private int number = 50;
private Lock lock = new ReentrantLock();
public void sale()
{
lock.lock();
try
{
if(number > 0)
{
System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number);
}
}finally {
lock.unlock();
}
}
}
public class SaleTicketDemo
{
public static void main(String[] args)
{
Ticket ticket = new Ticket();
new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"a").start();
new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"b").start();
new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"c").start();
new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"d").start();
new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"e").start();
}
}
结果:
很容易出现一条线程抢占全部资源,分配不均
2.1三问:
1.1为何会有公平/非公平设计? 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。 1.2为何默认非公平: 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
2.公平锁问题: 公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁, 这就是传说中的 “锁饥饿”
3.何时用公平何时非公平: 要吞吐量就非公平,否则就公平锁。
2.可重入锁:
synchronized和ReenctrantLock都是可重入 概念: 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁。
在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的。
public class ReEntryLockDemo
{
static Lock lock = new ReentrantLock();
public static void main(String[] args)
{
new Thread(() -> {
lock.lock();
try
{
System.out.println("----外层调用lock");
lock.lock();
try
{
System.out.println("----内层调用lock");
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
},"a").start();
new Thread(() -> {
lock.lock();
try
{
System.out.println("b thread----外层调用lock");
}finally {
lock.unlock();
}
},"b").start();
}
}
ReenctrantLock加锁几次就一定要解锁几次,否则当前线程用完了没释放干净,下边的线程就阻塞了。
★2.1死锁:
public class DeadLockDemo
{
static Object lockA = new Object();
static Object lockB = new Object();
public static void main(String[] args)
{
Thread a = new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "\t" + " 自己持有A锁,期待获得B锁");
try {TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "\t 获得B锁成功");
}
}
}, "a");
a.start();
new Thread(() -> {
synchronized (lockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+" 自己持有B锁,期待获得A锁");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lockA)
{
System.out.println(Thread.currentThread().getName()+"\t 获得A锁成功");
}
}
},"b").start();
}
}
排查证明是死锁: jps -l → jstack 类名 或者jconsole排查
3.中断
1.首先 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。 所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
其次 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。 因此,Java提供了一种用于停止线程的机制——中断。
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true; 接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断, 此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
★3.1 中断示例1:volatile
public static void main(String[] args) {
new Thread(()->{
while (true){
if (isStop){
System.out.println("========isStop=true,程序结束");
break;
}
System.out.println("-----hello,");
}
},"t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
isStop=true;
},"t2").start();
}
}
结果:
-----hello,
-----hello,
-----hello,
-----hello,
-----hello,
========isStop=true,程序结束
★3.2优雅停止线程:中断示例2–原子类
static AtomicBoolean atomicBoolean=new AtomicBoolean(false);
public static void main(String[] args) {
new Thread(()->{
while (true){
if (atomicBoolean.get()){
System.out.println("========atomicBoolean=true,程序结束");
break;
}
System.out.println("-----hello,atomicBoolean");
}
},"t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
atomicBoolean.set(true);
},"t2").start();
}
结果:
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
========atomicBoolean=true,程序结束
★3.3Thread自带的API实现
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
//isInterrupted自查中断标志位是否为true
if (Thread.currentThread().isInterrupted()) {
System.out.println("========isInterrupted,程序结束");
break;
}
System.out.println("-----hello,isInterrupted");
}
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
t1.interrupt();
},"t2").start();
}
3.4当前线程的中断标识为true时,并不是立刻停止!
当对一个线程,调用 interrupt() 时: ① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。 被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,★★★需要被调用的线程自己进行配合才行。
② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法, 那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
3.4.1 中断为true后,并不是立刻stop程序案例(被调用的线程不配合):
main线程通知t1线程中断,但是此案例的t1线程并未主动响应main线程的终端通知。t1执行完了线程死了才结束。
public static void main(String[] args) {
// 中断为true后,并不是立刻停止程序.因为此线程没配合isInterrupt()
Thread t1 = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println(("------i: " + i));
}
System.out.println("t1.interrupt调用之后02: " + Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
System.out.println("t1.interrupt()调用之前t1线程中断标志默认值: "+t1.isInterrupted());
try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
// 仅仅设置中断标志位为true,并未真正停止线程
t1.interrupt();
// 活动状态,t1线程还在执行
System.out.println("-----t1.interrupt()调用之后01中断标志默认值: "+t1.isInterrupted());
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
// 非活动状态,t1线程已经结束执行
System.out.println("------t1.interrupt()调用之后03中断标志默认值: "+t1.isInterrupted());
}
结果:建议自己执行一遍观察
★★3.5修改了中断标志,t1线程也主动响应,但t1程序依旧未停止
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("========isInterrupted,程序结束");
break;
}
//t2已经把标志位设为true,但是t1线程sleep被true打断后,标志位又变为false,所以即使抛异常也继续运行
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
// ★★★线程的中断标志位为false,无法停止,需要再次调用interrupt(),将标志位设为true!!
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println("-----hello,isInterrupted");
}
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
t1.interrupt();
},"t2").start();
}
结论:
|