认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
synchronized关键字和Lock的实现类都是悲观锁
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。
如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
class Phone {//资源类 public static synchronized void sendEmail() { //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-------sendEmail"); } public synchronized void sendSMS() { System.out.println("-------sendSMS"); } public void hello() { System.out.println("-------hello"); } } /** * @auther zzyy * @create 2021-03-03 15:00 题目:谈谈你对多线程锁的理解。 * 线程 操作 资源类 8锁案例说明 * 1 标准访问有ab两个线程,请问先打印邮件还是短信 * 2 sendEmail方法暂停3秒钟,请问先打印邮件还是短信 * 3 新增一个普通的hello方法,请问先打印邮件还是hello * 4 有两部手机,请问先打印邮件还是短信 * 5 两个静态同步方法,同1部手机,请问先打印邮件还是短信 * 6 两个静态同步方法, 2部手机,请问先打印邮件还是短信 * 7 1个静态同步方法,1个普通同步方法,同1部手机,请问先打印邮件还是短信 * 8 1个静态同步方法,1个普通同步方法,2部手机,请问先打印邮件还是短信 * * * 1-2 * * * 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了, * * * 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法 * * * 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法 * 3-4 * * 加个普通方法后发现和同步锁无关,hello * * 换成两个对象后,不是同一把锁了,情况立刻变化。 * * * * * 5-6 都换成静态同步方法后,情况又变化 * * 三种 synchronized 锁的内容有一些差别: * * 对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁——实例对象本身, * * 对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板 * * 对于同步方法块,锁的是 synchronized 括号内的对象 * * * 7-8 * * 当一个线程试图访问同步代码时它首先必须得到锁,退出或抛出异常时必须释放锁。 * * * * * * 所有的普通同步方法用的都是同一把锁——实例对象本身,就是new出来的具体实例对象本身,本类this * * * 也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。 * * * * * * 所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class * * * 具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的 * * * 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。 * */ public class Lock8Demo { public static void main(String[] args) {//一切程序的入口,主线程 Phone phone = new Phone();//资源类1 Phone phone2 = new Phone();//资源类2 new Thread(() -> { phone.sendEmail(); },"a").start(); //暂停毫秒 try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { //phone.sendSMS(); //phone.hello(); phone2.sendSMS(); },"b").start(); } } /** * * ============================================ * 1-2 * * 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了, * * 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法 * * 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法 * * 3-4 * * 加个普通方法后发现和同步锁无关 * * 换成两个对象后,不是同一把锁了,情况立刻变化。 * * 5-6 都换成静态同步方法后,情况又变化 * 三种 synchronized 锁的内容有一些差别: * 对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁——实例对象本身, * 对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板 * 对于同步方法块,锁的是 synchronized 括号内的对象 * * 7-8 * 当一个线程试图访问同步代码时它首先必须得到锁,退出或抛出异常时必须释放锁。 * * * * 所有的普通同步方法用的都是同一把锁——实例对象本身,就是new出来的具体实例对象本身,本类this * * 也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。 * * * * 所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class * * 具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的 * * 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。 **/
public class LockByteCodeDemo { final Object object = new Object(); public void m1() { synchronized (object){ System.out.println("----------hello sync"); //throw new RuntimeException("----ex"); } } /*public synchronized void m2() { }*/ public static synchronized void m2() { } }
反编译
不一定
管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
C++源码
class Ticket { private int number = 50; //private Lock lock = new ReentrantLock(true); //默认用的是非公平锁,分配的平均一点,=--》公平一点 private Lock lock = new ReentrantLock(false); //默认用的是非公平锁,分配的平均一点,=--》公平一点 public void sale() { lock.lock(); try { if(number > 0) { System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number); } }finally { lock.unlock(); } } /*Object objectLock = new Object(); public void sale() { synchronized (objectLock) { if(number > 0) { System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number); } } }*/ } 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(); } }
非公平锁:
公平锁:
⽣活中,排队讲求先来后到视为公平。程序中的公平性也是符合请求锁的绝对时间的,其实就是 FIFO,否则视为不公平
恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
可: 可以
重: 再次
入: 进入
锁: 同步锁。
进入什么:
一句话:
同步代码块:
public class ReEntryLockDemo{ public static void main(String[] args) { final Object objectLockA = new Object(); new Thread(() -> { synchronized (objectLockA) { System.out.println("-----外层调用"); synchronized (objectLockA) { System.out.println("-----中层调用"); synchronized (objectLockA) { System.out.println("-----内层调用"); } } } },"a").start(); } }
同步方法:
/** * 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远 * 可以得到锁的 */ public class ReEntryLockDemo{ public synchronized void m1() { System.out.println("-----m1"); m2(); } public synchronized void m2() { System.out.println("-----m2"); m3(); } public synchronized void m3() { System.out.println("-----m3"); } public static void main(String[] args) { ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo(); reEntryLockDemo.m1(); } }
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
/** * 指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用并且不发生死锁,这样的锁就叫做可重入锁。 * 简单的来说就是: * 在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的 */ public class ReEntryLockDemo { public static void main(String[] args) { Lock lock = new ReentrantLock(); new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"\t"+"-----外层"); lock.lock(); try { System.out.println(Thread.currentThread().getName()+"\t"+"-----内层"); }finally { //lock.unlock(); } }finally { lock.unlock(); } },"t1").start(); new Thread(() -> { lock.lock(); try { System.out.println("------22222"); }finally { lock.unlock(); } },"t2").start(); } }
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
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(); } }
纯命令:
图形化:
首先
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。
所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此,Java提供了一种用于停止线程的机制——中断。
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,
此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
public class InterruptDemo { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted()); System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted()); System.out.println("111111"); Thread.currentThread().interrupt(); System.out.println("222222"); System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted()); System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted()); } }
方法的注释也清晰的表达了“中断状态将会根据传入的ClearInterrupted参数值确定是否重置”。
所以,
静态方法interrupted将 会清除中断状态(传入的参数ClearInterrupted为true),
实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)。
public class InterruptDemo { private static volatile boolean isStop = false; public static void main(String[] args) { new Thread(() -> { while(true) { if(isStop) { System.out.println(Thread.currentThread().getName()+"线程------isStop = true,自己退出了"); break; } System.out.println("-------hello interrupt"); } },"t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } isStop = true; } }
public class StopThreadDemo { private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true); public static void main(String[] args) { Thread t1 = new Thread(() -> { while(atomicBoolean.get()) { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----hello"); } }, "t1"); t1.start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } atomicBoolean.set(false); } }
public class InterruptDemo { public static void main(String[] args) { Thread t1 = new Thread(() -> { while(true) { if(Thread.currentThread().isInterrupted()) { System.out.println("-----t1 线程被中断了,break,程序结束"); break; } System.out.println("-----hello"); } }, "t1"); t1.start(); System.out.println("**************"+t1.isInterrupted()); //暂停5毫秒 try { TimeUnit.MILLISECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } t1.interrupt(); System.out.println("**************"+t1.isInterrupted()); } }
>具体来说,当对一个线程,调用 interrupt() 时:
① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。
被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,
那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
结论:
线程中断相关的方法:
interrupt()方法是一个实例方法
它通知目标线程中断,也就是设置目标线程的中断标志位为true,中断标志位表示当前线程已经被中断了。
sInterrupted()方法也是一个实例方法i
它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志
Thread类的静态方法interrupted()
返回当前线程的中断状态(boolean类型)且将当前线程的中断状态设为false,此方法调用之后会清除当前线程的中断标志位的状态(将中断标志置为false了),返回当前值并清零置false
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
下面这句话,后面详细说
LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),
permit只有两个值1和零,默认是零。
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
调用LockSupport.park()时
permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,
然后会将permit再次设置为零并返回。
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
public class LockSupportDemo3 { public static void main(String[] args) { //正常使用+不需要锁块 Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName()+" "+"1111111111111"); LockSupport.park(); System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒"); },"t1"); t1.start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+" -----LockSupport.unparrk() invoked over"); } }
public class T1 { public static void main(String[] args) { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---被叫醒"); },"t1"); t1.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---unpark over"); } }