Synchronized深度解析
概览:
- 简介:作用、地位、不控制并发的影响
- 用法:对象锁和类锁
- 多线程访问同步方法的7种情况
- 性质:可重入、不可中断
- 原理:加解锁原理、可重入原理、可见性原理
- 缺陷:效率低、不够灵活、无法预判是否成功获取到锁
- 如何选择Lock或Synchronized
- 如何提高性能、JVM如何决定哪个线程获取锁
- 总结
后续会有代码演示,测试环境 JDK8、IDEA
一、简介
1、作用
能够保证在同一时刻最多只有一个线程执行该代码,以保证并发安全的效果。
2、地位
- Synchronized是Java关键字,Java原生支持
- 最基本的互斥同步手段
- 并发编程的元老级别
3、不控制并发的影响
测试:两个线程同时a++,猜一下结果
package cn.jsonshare.java.base.synchronizedtest; /**
* 不使用synchronized,两个线程同时a++
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedTest1 implements Runnable{
static SynchronizedTest1 st = new SynchronizedTest1(); static int a = 0; /**
* 不使用synchronized,两个线程同时a++
*/
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(a);
} @Override
public void run(){
for(int i=0; i<10000; i++){
a++;
}
}
}
预期是20000,但多次执行的结果都小于20000
10108
11526
10736
...
二、用法:对象锁和类锁
1、对象锁
- 代码块形式:手动指定锁对象
- 方法锁形式:synchronized修饰方法,锁对象默认为this
package cn.jsonshare.java.base.synchronizedtest; /**
* 对象锁实例: 代码块形式
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedTest2 implements Runnable{
static SynchronizedTest2 st = new SynchronizedTest2(); public static void main(String[] args) {
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()){ }
System.out.println("run over"); } @Override
public void run(){
synchronized (this){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
}
package cn.jsonshare.java.base.synchronizedtest; /**
* 对象锁实例:synchronized方法
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedTest3 implements Runnable{
static SynchronizedTest3 st = new SynchronizedTest3(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method();
} public synchronized void method(){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
结果:
开始执行:Thread-0
执行结束:Thread-0
开始执行:Thread-1
执行结束:Thread-1
run over
2、类锁
概念:Java类可能有多个对象,但只有一个Class对象
本质:所谓的类锁,不过是Class对象的锁而已
用法和效果:类锁只能在同一时刻被一个对象拥有
形式1:synchronized加载static方法上
形式2:synchronized(*.class)代码块
package cn.jsonshare.java.base.synchronizedtest; /**
* 类锁:synchronized加载static方法上
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedTest4 implements Runnable{ static SynchronizedTest4 st1 = new SynchronizedTest4();
static SynchronizedTest4 st2 = new SynchronizedTest4(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(st1);
Thread t2 = new Thread(st2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method();
} public static synchronized void method(){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
package cn.jsonshare.java.base.synchronizedtest; /**
* 类锁:synchronized(*.class)代码块
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedTest5 implements Runnable{
static SynchronizedTest4 st1 = new SynchronizedTest4();
static SynchronizedTest4 st2 = new SynchronizedTest4(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(st1);
Thread t2 = new Thread(st2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method();
} public void method(){
synchronized(SynchronizedTest5.class){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
}
结果:
开始执行:Thread-0
执行结束:Thread-0
开始执行:Thread-1
执行结束:Thread-1
run over
三、多线程访问同步方法的7种情况
- 两个线程同时访问一个对象的相同的synchronized方法
- 两个线程同时访问两个对象的相同的synchronized方法
- 两个线程同时访问两个对象的相同的static的synchronized方法
- 两个线程同时访问同一对象的synchronized方法与非synchronized方法
- 两个线程访问同一对象的不同的synchronized方法
- 两个线程同时访问同一对象的static的synchronized方法与非static的synchronized方法
- 方法抛出异常后,会释放锁吗
仔细看下面示例代码结果输出的结果,注意输出时间间隔,来预测结论
场景1:
package cn.jsonshare.java.base.synchronizedtest; /**
* 两个线程同时访问一个对象的相同的synchronized方法
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene1 implements Runnable{
static SynchronizedScene1 ss = new SynchronizedScene1(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss);
Thread t2 = new Thread(ss);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method();
} public synchronized void method(){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
场景2:
package cn.jsonshare.java.base.synchronizedtest; /**
* 两个线程同时访问两个对象的相同的synchronized方法
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene2 implements Runnable{
static SynchronizedScene2 ss1 = new SynchronizedScene2();
static SynchronizedScene2 ss2 = new SynchronizedScene2(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss1);
Thread t2 = new Thread(ss2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method();
} public synchronized void method(){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
场景3:
package cn.jsonshare.java.base.synchronizedtest; /**
* 两个线程同时访问两个对象的相同的static的synchronized方法
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene3 implements Runnable{
static SynchronizedScene3 ss1 = new SynchronizedScene3();
static SynchronizedScene3 ss2 = new SynchronizedScene3(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss1);
Thread t2 = new Thread(ss2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method();
} public synchronized static void method(){
System.out.println("开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("执行结束:" + Thread.currentThread().getName());
}
}
场景4:
package cn.jsonshare.java.base.synchronizedtest; /**
* 两个线程同时访问同一对象的synchronized方法与非synchronized方法
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene4 implements Runnable{
static SynchronizedScene4 ss1 = new SynchronizedScene4(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss1);
Thread t2 = new Thread(ss1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
// 模拟两个线程同时访问 synchronized方法与非synchronized方法
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
} public void method1(){
System.out.println("method1开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("method1执行结束:" + Thread.currentThread().getName());
} public synchronized void method2(){
System.out.println("method2开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("method2执行结束:" + Thread.currentThread().getName());
}
}
场景5:
package cn.jsonshare.java.base.synchronizedtest; /**
* 两个线程访问同一对象的不同的synchronized方法
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene5 implements Runnable{
static SynchronizedScene5 ss1 = new SynchronizedScene5(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss1);
Thread t2 = new Thread(ss1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
// 模拟两个线程同时访问不同的synchronized方法
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
} public synchronized void method1(){
System.out.println("method1开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("method1执行结束:" + Thread.currentThread().getName());
} public synchronized void method2(){
System.out.println("method2开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("method2执行结束:" + Thread.currentThread().getName());
}
}
场景6:
package cn.jsonshare.java.base.synchronizedtest; /**
* 两个线程同时访问同一对象的static的synchronized方法与非static的synchronized方法
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene6 implements Runnable{
static SynchronizedScene6 ss1 = new SynchronizedScene6(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss1);
Thread t2 = new Thread(ss1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
// 模拟两个线程同时访问static的synchronized方法与非static的synchronized方法
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
} public static synchronized void method1(){
System.out.println("method1开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("method1执行结束:" + Thread.currentThread().getName());
} public synchronized void method2(){
System.out.println("method2开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("method2执行结束:" + Thread.currentThread().getName());
}
}
场景7:
package cn.jsonshare.java.base.synchronizedtest; /**
* 方法抛出异常后,会释放锁吗
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene7 implements Runnable{
static SynchronizedScene7 ss1 = new SynchronizedScene7(); public static void main(String[] args) throws Exception{
Thread t1 = new Thread(ss1);
Thread t2 = new Thread(ss1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("run over");
} @Override
public void run(){
method1();
} public synchronized void method1(){
System.out.println("method1开始执行:" + Thread.currentThread().getName());
try {
// 模拟执行内容
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
// 模拟异常
throw new RuntimeException();
//System.out.println("method1执行结束:" + Thread.currentThread().getName());
}
}
总结:
1、两个线程同时访问一个对象的相同的synchronized方法
同一实例拥有同一把锁,其他线程必然等待,顺序执行
2、两个线程同时访问两个对象的相同的synchronized方法
不同的实例拥有的锁是不同的,所以不影响,并行执行
3、两个线程同时访问两个对象的相同的static的synchronized方法
静态同步方法,是类锁,所有实例是同一把锁,其他线程必然等待,顺序执行
4、两个线程同时访问同一对象的synchronized方法与非synchronized方法
非synchronized方法不受影响,并行执行
5、两个线程访问同一对象的不同的synchronized方法
同一实例拥有同一把锁,所以顺序执行(说明:锁的是this对象==同一把锁)
6、两个线程同时访问同一对象的static的synchronized方法与非static的synchronized方法
static同步方法是类锁,非static是对象锁,原理上是不同的锁,所以不受影响,并行执行
7、方法抛出异常后,会释放锁吗
会自动释放锁,这里区别Lock,Lock需要显示的释放锁
3个核心思想:
1、一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应1、5的情景)
2、每个实例都对应有自己的一把锁,不同的实例之间互不影响;
例外:锁对象是*.class以及synchronized被static修饰的时候,所有对象共用同一把锁(对应2、3、4、6情景)
3、无论是方法正常执行完毕还是方法抛出异常,都会释放锁(对应7情景)
补充:
问题:目前进入到被synchronized修饰的方法,这个方法里边调用了非synchronized方法,是线程安全的吗?
package cn.jsonshare.java.base.synchronizedtest; /**
* 目前进入到被synchronized修饰的方法,这个方法里边调用了非synchronized方法,是线程安全的吗?
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedScene8 {
public static void main(String[] args) {
new Thread(() -> {
method1();
}).start(); new Thread(() -> {
method1();
}).start();
} public static synchronized void method1() {
method2();
} private static void method2() {
System.out.println(Thread.currentThread().getName() + "进入非Synchronized方法");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + "结束非Synchronized方法");
}
}
结论:这样是线程安全的
四、性质
1、可重入
指的是同一线程的外层函数获取锁之后,内层函数可以直接再次获取该锁
Java典型的可重入锁:synchronized、ReentrantLock
好处:避免死锁,提升封装性
粒度:线程而非调用
情况1:证明同一方法是可重入的
情况2:证明可重入不要求是同一方法
情况3:证明可重入不要求是同一类中的
2、不可中断
一旦这个锁被别的线程获取了,如果我现在想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁,如果别的线程永远不释放锁,那么我只能永远的等待下去。
相比之下,Lock类可以拥有中断的能力,第一点:如果我觉得我等待的时间太长了,有权中断现在已经获取到锁的线程执行;第二点:如果我觉得我等待的时间太长了不想再等了,也可以退出。
五、原理
1、加解锁原理(现象、时机、深入JVM看字节码)
现象:每一个类的实例对应一把锁,每一个synchronized方法都必须首先获得调用该方法的类的实例的锁,方能执行,否则就会阻塞,方法执行完成或者抛出异常,锁被释放,被阻塞线程才能获取到该锁,执行。
获取和释放锁的时机:内置锁或监视器锁
package cn.jsonshare.java.base.synchronizedtest; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* method1 等价于 method2
*
* @author JSON
* @date 2019-08-29
*/
public class SynchronizedToLock1 {
Lock lock = new ReentrantLock(); public synchronized void method1(){
System.out.println("执行method1");
} public void method2(){
lock.lock();
try {
System.out.println("执行method2");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
} public static void main(String[] args) {
SynchronizedToLock1 sl = new SynchronizedToLock1(); // method1 等价于 method2
sl.method1();
sl.method2();
}
}
深入JVM看字节码:
...
monitorenter指令
...
monitorexit指令
...
关于这两条指令的作用,我们直接参考JVM规范中描述:
monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
这段话的大概意思为:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
这段话的大概意思为:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
https://www.cnblogs.com/lycroseup/p/7486860.html
2、可重入原理(加锁次数计数器)
JVM负责跟踪对象被加锁的次数
线程第一次给对象加锁的时候,计数变为1,每当这个相同的线程在此对象上再次获得锁时,计数会递增
每当任务离开时,计数递减,当计数为0的时候,锁被完全释放
3、可见性原理(内存模型)
Java内存模型

线程A向线程B发送数据的过程(JMM控制)

synchronized关键字实现可见性:
被synchronized修饰,那么执行完成后,对对象所做的任何修改都要在释放锁之前,都要从线程内存写入到主内存,所以主内存中的数据是最新的。
六、缺陷
1、效率低
1)、锁的释放情况少(线程执行完成或者异常情况释放)
2)、试图获得锁时不能设定超时(只能等待)
3)、不能中断一个正在试图获得锁的线程(不能中断)
2、不够灵活
加锁和释放的时机比较单一,每个锁仅有单一的条件(某个对象),可能是不够的
比如:读写锁更灵活
3、无法预判是否成功获取到锁
七、常见问题
1、synchronized关键字注意点:
- 锁对象不能为空
- 作用域不宜过大
- 避免死锁
2、如何选择Lock和synchronized关键字?
总结建议(优先避免出错的原则):
- 如果可以的话,尽量优先使用java.util.concurrent各种类(不需要考虑同步工作,不容易出错)
- 优先使用synchronized,这样可以减少编写代码的量,从而可以减少出错率
- 若用到Lock或Condition独有的特性,才使用Lock或Condition
八、总结
一句话总结synchronized:
JVM会自动通过使用monitor来加锁和解锁,保证了同一时刻只有一个线程可以执行指定的代码,从而保证线程安全,同时具有可重入和不可中断的特性。
Synchronized深度解析的更多相关文章
- Java之戳中痛点 - (8)synchronized深度解析
概览: 简介:作用.地位.不控制并发的影响 用法:对象锁和类锁 多线程访问同步方法的7种情况 性质:可重入.不可中断 原理:加解锁原理.可重入原理.可见性原理 缺陷:效率低.不够灵活.无法预判是否成功 ...
- Java并发之synchronized关键字深度解析(二)
前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实 ...
- java内存分配和String类型的深度解析
[尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...
- 深度解析Java8 – AbstractQueuedSynchronizer的实现分析(上)
本文首发在infoQ :www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer 前言: Java中的FutureTask作为可异步执行任 ...
- SpringMVC 源码深度解析<context:component-scan>(扫描和注冊的注解Bean)
我们在SpringMVC开发项目中,有的用注解和XML配置Bean,这两种都各有自己的优势,数据源配置比較经经常使用XML配置.控制层依赖的service比較经经常使用注解等(在部署时比較不会改变的) ...
- String深度解析
文章出处:http://my.oschina.net/xiaohui249/blog/170013 一.引题 String类型是比较特殊的一种类型,同时也是面试经常被问到的一个知识点,本文结合java ...
- mybatis 3.x源码深度解析与最佳实践(最完整原创)
mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...
- 【转】java内存分配和String类型的深度解析
一.引题 在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题.下面是本 ...
- Hibernate 3 深度解析--苏春波
Hibernate 3 深度解析 Hibernate 作为 Java ORM 模式的优秀开源实现, 当下已经成为一种标准,为饱受 JDBC 折磨的 Java 开发者带来了“福音.快速的版本更新,想 ...
随机推荐
- flower 指定app
flower 指定app # tasks.py from celery import Celery celery = Celery(broker="amqp://", backen ...
- FZU ICPC 2020 寒假训练 1
B - Sum Problem In this problem, your task is to calculate SUM(n) = 1 + 2 + 3 + ... + n. Input The i ...
- vue自定义指令实例使用(实例说明自定义指令的作用)
在写vue项目的时候,我们经常需要对后台返回的数据进行大量的渲染操作,其中就包含了大量的对特殊数据的进一步处理,比如说时间戳.图片地址.特殊数据显示等等特殊数据处理改进. 其实遇到这种情况,通过Vue ...
- 菜鸡的Java笔记 - java 反射机制
反射机制 1.观察 Class 类的使用 2.利用反射改善工程设计模式 3.反射操作类结构 content (内容) 1. ...
- 【linux系统】命令学习(六)awk sed grep 与管道的使用
程序运行环境输入与输出 标准输入0 read a;echo $a 标准输出1 echo cesh 错误输出 ls notr 管道重定向 管道与管道之间可以重定向 管道与文件之间可以重定向 用于写入 将 ...
- Swift-技巧(六)设置按钮状态并更改
摘要 按钮是一个宝藏控件,可以在设置的时候就对不同的状态添加图片.文本,甚至更改背景.在不同的展示场景中更改到不同的状态显示就好.恰恰是如何更改状态着实让我懵了一阵,所以记录一下过程.如果没有兴趣了解 ...
- [luogu7092]计数题
由于$\mu(i)$,因此每一个素数最多存在1次,当$k=0$答案必然为0 根据莫比乌斯和欧拉函数的积性,答案与对素数的划分无关,仅与每一个素数是否出现有关,换言之枚举素数出现的集合$P'$,答案即为 ...
- [luogu5162]WD与积木
设$g_{n}$表示$n$个积木放的方案数,枚举最后一层所放的积木,则有$g_{n}=\sum_{i=1}^{n}c(n,i)g_{n-i}$(因为积木有编号的所以要选出$i$个) 将组合数展开并化简 ...
- Assassin暗杀者-自用短小精悍的webshell管理工具分享
Assassin Assassin是一款精简的基于命令行的webshell管理工具,它有着多种payload发送方式和编码方式,以及精简的payload代码,使得它成为隐蔽的暗杀者,难以被很好的防御. ...
- 链式调用Builder
使用Lombok实现链式调用 1.静态调用 User对象: 对象中必须有一个值不为空staticname作为指定的参数并调用对象 @Accessors(chain = true) @Getter @S ...