对于使用java同学,synchronized是再熟悉不过了。synchronized是实现线程同步的基本手段,然而底层实现还是通过锁机制来保证,对于被synchronized修饰的区域每次只有一个线程可以访问,从而满足线程安全的目的。那么今天就让我们聊一聊synchronized的那些事
1.基本用法
  java内存模型(JMM)围绕原子性,可见性、有序性以及Happen-before原则展开(参考http://www.cnblogs.com/hujunzheng/p/5118256.html),synchronized通过锁机制的实现,满足了原子性,可见性和有序性,是并发编程正确执行的有效保障,而volatile只保证了可见性和有序性(禁止指令重排)。
  synchronized可以修饰范围的包括:方法级别,代码块级别;而实际加锁的目标包括:对象锁(普通变量,静态变量),类锁。
  下面是synchronized的几种常用方法:
public class SynMethod {
private static final Object staticLockObj = new Object();
/**
* 对象锁,代码级别,同一对象争用该锁,this为SynMethod实例,synchronized的锁绑定在this对象上
*/
public void method1() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
} /**
* 对象锁,方法级别,同一对象争用该锁,普通(非静态)方法,synchronized的锁绑定在调用该方法的对象上,与上一个写法含义一致
*/
public synchronized void method2() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
} /**
* 对象锁,代码级别,同一类争用该锁,绑定在staticLockObj上,不同SynMethod实例,拥有同一个staticLockObj对象
*/
public void method3() {
synchronized (staticLockObj) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
} /**
* 类锁,代码级别,同一类争用该锁
*/
public void method4() {
synchronized (SynMethod.class) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
} /**
* 类锁,方法级别,同一类争用该锁,synchronized的锁绑定在SynMethod.class上
*/
public static synchronized void staticMethod() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}

  下面我们来测试一下(由于同步运行结果收到线程调度等各种影响,对于无法达到同步效果的情况,需要多运行几次)

  测试情况1

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method1();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t1.method1();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程运行了同一个对象t1的同一个public方法method1,这个方法在t1对象上同步,所以实现了同步的效果

  测试情况2

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method1();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t1.method2();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程运行同一个对象t1的不同的方法method1和method2方法,但是这两个方法是使用同一个对象t1上进行同步的,所以实现同步的效果,侧面印证了这两种写法的一致性。

  测试情况3:

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
final SynMethod t2 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method3();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t2.method3();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

这次两个线程运行了不同的类对象t1和t2的同一个方法method3,这个方法是在一个静态对象上同步,这个静态变量是在这个类的所有实例上共享的,所以也是达到了同步的效果

  测试情况4:

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
final SynMethod t2 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method2();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t1.method3();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
B synchronized loop 3
A synchronized loop 3
B synchronized loop 4
A synchronized loop 4

这次是两个线程运行了同一个对象t1的method2和method3方法,这个方法分别在t1对象和SynMethod类的静态对象上同步,所以达到同步效果

  测试情况5:

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
final SynMethod t2 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method4();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t2.method4();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程运行了不同对象t1和t2的同一个方法method4,该方法是在SynMethod类上同步,实现了同步效果

  测试情况6:

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
final SynMethod t2 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method4();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
SynMethod.staticMethod();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程分别运行了对象t1的method4和静态方法staticMethod,这个两个方法都在SynMethod类上同步,实现了同步的效果。

  测试情况7:

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
final SynMethod t2 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method4();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t2.method3();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 3
B synchronized loop 4

这次两个线程运行了两个对象的method3和method4发放,这个两个方法分别在SynMethod类和SynMethod类的静态对象上同步,所以没有达到同步效果

  测试情况8:

public class SynTest {
public static void main(String[] args) {
final SynMethod t1 = new SynMethod();
final SynMethod t2 = new SynMethod();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
t1.method4();
}
}, "A");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
t2.method2();
}
}, "B");
ta.start();
tb.start();
}
}

运行结果:

A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
A synchronized loop 3
B synchronized loop 3
A synchronized loop 4
B synchronized loop 4

这次两个线程运行了两个对象的method4和method2方法,这两个方法分别在SynMethod类和对象t2上同步,所以没有达到同步效果。

  使用总结:虽然上面说的情况比较多,但是从同步对象的角度,同步的场景只用三个,一个是SynMethod实例(可以多个),SynMethod的静态对象(共享)和SynMethod类(一个),只要是在同一个对象上同步,这个对象可以是实例对象,可以是静态对象,可以是类对象,那么就可以实现同步效果,否则无法达到同步,这也与synchronized设计的初衷一致。

  

2.实现原理

  首先我们将SynMethod编译一下(命令:javac SynMethod.java),得到.class文件SynMethod.class,再通过反编译命令(javap -c SynMethod.class)

Compiled from "SynMethod.java"
public class concurrent.SynMethod {
public concurrent.SynMethod();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public void method1();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: iconst_0
5: istore_2
6: iload_2
7: iconst_5
8: if_icmpge 51
11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14: new #3 // class java/lang/StringBuilder
17: dup
18: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
21: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
24: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String;
27: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: ldc #8 // String synchronized loop
32: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: iload_2
36: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
42: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: iinc 2, 1
48: goto 6
51: aload_1
52: monitorexit
53: goto 61
56: astore_3
57: aload_1
58: monitorexit
59: aload_3
60: athrow
61: return
Exception table:
from to target type
4 53 56 any
56 59 56 any public synchronized void method2();
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 47
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: new #3 // class java/lang/StringBuilder
13: dup
14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
17: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
20: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String;
23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: ldc #8 // String synchronized loop
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: iload_1
32: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
35: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: iinc 1, 1
44: goto 2
47: return public void method3();
Code:
0: getstatic #12 // Field staticLockObj:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
6: iconst_0
7: istore_2
8: iload_2
9: iconst_5
10: if_icmpge 53
13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
16: new #3 // class java/lang/StringBuilder
19: dup
20: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
23: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
26: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String;
29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: ldc #8 // String synchronized loop
34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37: iload_2
38: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: iinc 2, 1
50: goto 8
53: aload_1
54: monitorexit
55: goto 63
58: astore_3
59: aload_1
60: monitorexit
61: aload_3
62: athrow
63: return
Exception table:
from to target type
6 55 58 any
58 61 58 any public void method4();
Code:
0: ldc_w #13 // class concurrent/SynMethod
3: dup
4: astore_1
5: monitorenter
6: iconst_0
7: istore_2
8: iload_2
9: iconst_5
10: if_icmpge 53
13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
16: new #3 // class java/lang/StringBuilder
19: dup
20: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
23: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
26: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String;
29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: ldc #8 // String synchronized loop
34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37: iload_2
38: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: iinc 2, 1
50: goto 8
53: aload_1
54: monitorexit
55: goto 63
58: astore_3
59: aload_1
60: monitorexit
61: aload_3
62: athrow
63: return
Exception table:
from to target type
6 55 58 any
58 61 58 any public static synchronized void staticMethod();
Code:
0: iconst_0
1: istore_0
2: iload_0
3: iconst_5
4: if_icmpge 47
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: new #3 // class java/lang/StringBuilder
13: dup
14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
17: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
20: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String;
23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: ldc #8 // String synchronized loop
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: iload_0
32: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
35: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: iinc 0, 1
44: goto 2
47: return static {};
Code:
0: new #14 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: putstatic #12 // Field staticLockObj:Ljava/lang/Object;
10: return
}

  通过反编译出来的代码可以看到,方法级别的synchronized同步使用了monitorenter和monitorexit这个同步命令,而monitorexit出现了两次,猜测是由于异常处理的需要

  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的异常的原因。
 
  对于方法级别的同步,在flag标志中多了ACC_SYNCHRONIZED标示符,用于标记整个方法的同步,JVM在执行该方法前会读取该标记符,从而调用monitorentor命令,在退出方法时调用monitorexit命令,从而达到同步的效果(这里反编译的结果并没有看到flag字段,可以参考http://www.cnblogs.com/paddix/p/5367116.html)
  
  JSR 133(Java Memory Model)FAQ(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html)中关于synchronized的问题中提到,synchronized除了保证线程对该代码块的互斥访问外,还有下面这么一段话
  But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.
  大概的意思是,同步不仅仅保证互斥访问,同步还保证当前线程在同步块前和同步块中,对内存的写操作对于其他访问相同同步块(使用了同一个monitor)的线程是可见的。当我们退出了同步块,会释放monitor,并且将缓存数据刷新到内存,这样当前线程的写操作对于其他线程是可见的,当我们进入同步快之前,会获取monitor,并且使得当前处理器的缓存失效,从而读取数据必须从内存中重新加载,这样我们就可以看到其他线程在同步块中写操作
 
3.总结
  Synchronized是java常用的同步手段,正确合理的使用是编写好并发程序的关键,本文主要是从使用和实现原理的角度来说,下个文章将会讲讲关于synchronized的底层锁优化以,偏向锁哪些事
 
参考:
http://www.cnblogs.com/paddix/p/5367116.html
https://blog.csdn.net/sum_rain/article/details/39892219
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

关于java的Synchronized,你可能需要知道这些(上)的更多相关文章

  1. Java中Synchronized的用法

    原文:http://blog.csdn.net/luoweifu/article/details/46613015 作者:luoweifu 转载请标名出处 <编程思想之多线程与多进程(1)——以 ...

  2. Java 多线程 —— synchronized关键字

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  3. Java的synchronized关键字:同步机制总结

    JAVA中synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块.搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程 ...

  4. JAVA多线程synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 当两个并发线程访问同一个对象object中的这个synchronized(this)同 ...

  5. java中synchronized的用法详解

    记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...

  6. 转:Java同步synchronized使用

    原文链接 作者:Jakob Jenkov Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(s ...

  7. JAVA关键词synchronized的作用

    记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...

  8. java中synchronized的使用方法与具体解释

    Java语言的keyword.当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多仅仅有一个线程运行该段代码. 一.当两个并发线程訪问同一个对象object中的这个synchronized ...

  9. java同步synchronized

    java同步synchronized volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性. 看下面的这段代码: /** * * @author InJavaWeTrust * */ ...

  10. Java 多线程 - Synchronized关键字

    目录 1-Synchronized 关键字概述 2- Synchronized关键字作用域 3- Synchronized 原理(反编译指令解释) 正文 1-Synchronized 关键字概述 由于 ...

随机推荐

  1. 路由测试-lee

    //get 路由 Route::get('/', 'WelcomeController@index'); Route::get('home', 'HomeController@index'); //路 ...

  2. python——模块与包2

    模块与包2 1 什么是包 包是一种通过使用.'模块名'来组织python模块名称空间的方式. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都 ...

  3. scrapy中response.body 与 response.text区别

    scrapy中response.body 与 response.text区别 body http响应正文, byte类型 text 文本形式的http正文,str类型,它是response.body经 ...

  4. Docker----起步

    最近学习了一下的docker相关的东西,下面介绍一下我个人的学习总结和体会.关于docker的详细介绍和优势,在网上随便都可以找得到,就不做介绍了.这个部分的内容比较简单,有Docker基础的朋友可以 ...

  5. Java集合框架之四大接口、常用实现类

    Java集合框架 <Java集合框架的四大接口> Collection:存储无序的.不唯一的数据:其下有List和Set两大接口. List:存储有序的.不唯一的数据: Set:存储无序的 ...

  6. WPF 自定义TreeView控件样式,仿QQ联系人列表

    一.前言 TreeView控件在项目中使用比较频繁,普通的TreeView并不能满足我们的需求.因此我们需要滴对TreeView进行改造.下面的内容将介绍仿QQ联系人TreeView样式及TreeVi ...

  7. 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- Demo分析

    如何创建工程 下载最新的Unity发布插件包. 打开Unity,新建一个项目 将插件包导入 在菜单中点击ASRuntime/Create ActionScript3 FlashDevelop HotF ...

  8. Unity3D UGUI 自动调节大小

    可添加以下组件 组件包含的两个枚举参数,可以自行设定适应方式. 例如一个Text UI元素,当文字过多的时候他不会自动增加高度而导致文字不能完全显示,这时候就可以挂载这个组件,如上图设置参数,就可以自 ...

  9. [ZJOI 2014]力

    Description 给出n个数qi,给出Fj的定义如下: $$F_j = \sum_{i<j}\frac{q_i q_j}{(i-j)^2 }-\sum_{i>j}\frac{q_i ...

  10. [NOI 2011]道路修建

    Description 在 W 星球上有 n 个国家.为了各自国家的经济发展,他们决定在各个国家 之间建设双向道路使得国家之间连通.但是每个国家的国王都很吝啬,他们只愿 意修建恰好 n – 1条双向道 ...