StringBuilder is mutable, while String is immutable. Immutable means it doesn't change, so your point about it not being synchronized makes no sense - there are no possible changes to be synchronized.
In short, if you don't need StringBuilders extra features (which is common with strings), String is way more efficient. It also has highly optimized, special handling in JVM that other objects in Java don't have.
Most notable example of String-specific optimization, the String pool, makes Strings behave like constants in that the same instance can be shared throughout the application automatically, saving memory and time. This is not automatically possible for other objects, and for mutable objects it is usually not even desired. The String pool, in comparison to custom-made pool, has been shown to allow for more objects with better performance. The process of using only one instance of a String all around for performance benefits is called string interning.
Since you're only using one instance, when comparing, you only need to compare the reference. For each StringBuilder instance, you'd need to evaluate the contents of the object to do that. Stringbuilder uses an Array as a backing object and that Array has to be iterated through to get its contents, which is considerably more costly. This assuming you're interested about the string contents and not the object instance, which is almost always the case with String/StringBuilder/StringBuffer.
Simple performance test comparing for contents of String and StringBuilder:
public class CompTest {
// fastest way to compare StringBuilder contents according to
// http://stackoverflow.com/questions/11007387/what-is-an-efficient-way-to-compare-stringbuilder-objects
static boolean contentEquals(StringBuilder sb1, StringBuilder sb2) {
if (sb1.length() != sb2.length()) return false;
for (int i = 0, len = sb1.length(); i < len; i++) {
if (sb1.charAt(i) != sb2.charAt(i)) return false;
}
return true;
}
public static void main(String args[]) {
StringBuilder fooSb = new StringBuilder("foo");
StringBuilder barSb = new StringBuilder("foo");
String foo = "foo";
String bar = "foo";
System.out.println(foo.equals(bar));
// returns true
System.out.println(fooSb.equals(barSb));
// returns false, so cannot be used to check contents
System.out.println(contentEquals(fooSb,barSb));
// returns true
long time;
time = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
if (foo.equals(bar)) continue;
}
System.out.println("str comparisons took " + (System.currentTimeMillis() - time) + " ms");
time = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
if (contentEquals(fooSb,barSb)) continue;
}
System.out.println("sb comparisons took " + (System.currentTimeMillis() - time) + " ms");
/* repeat the test as we know JVM is warmed up by now */
time = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
if (foo.equals(bar)) continue;
}
System.out.println("str comparisons took " + (System.currentTimeMillis() - time) + " ms");
time = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
if (contentEquals(fooSb,barSb)) continue;
}
System.out.println("sb comparisons took " + (System.currentTimeMillis() - time) + " ms");
}
}
With results:
true
false
true
str comparisons took 1244 ms
sb comparisons took 11530 ms
str comparisons took 1231 ms
sb comparisons took 12098 ms