在java语言中,如果有一个数据存在多个线程共享的情况,会导致这多个线程的操作互相影响。也就会出现如下情况:
package com.lenovo.plm.dms.p2;
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
class MyThread implements Runnable{
Integer num = 100;
String str = "";
@Override
public void run() {
// TODO Auto-generated method stub
print();
}
public void print(){
num = num + 10;
System.out.println(num);
}
}
运行结果如下,每一次运行结果都不同。很明显这个结果并不对。
120
120
130
140
分析其原因,就是多个线程在互相不可见的情况下一起操作了这个变量,导致这个变量的结果不可预期。
为了避免这种情况,java语言级别支持synchronized关键字,其作用就是要让多个线程之间是可见并进行有序操作。我的理解如下:
- synchronized可以修饰方法,代码块。
- synchronized的原理是,给这个方法所在的对象加上一把锁,注意这里是对象,不是类。如果有多个线程来调用这些synchronized方法,则会先获取这把锁,如果这把锁被其他线程占用,则需要等待其他线程释放。因此可以保持可见性。
- 线程调用synchronized方法,会直接将改方法全部执行完成如果代码中没有中断逻辑的话。执行完成后,才会释放锁,让其他线程来执行该方法。
- 如果一个对象中有多个synchronized方法,那么多线程只能一次调用一个synchronized方法。因为锁是对象的锁。
120
130
140
这里出现一个问题。如果上述中的print方法很长,实际上这样效率很低,因为每个线程同步的顺序执行,那么就没有多线程的必要了。因此synchronized关键字使用的粒度越细,也就是说synchronized关键字修饰的代码越少,真正的并发效率才能越高。
我个人理解,这个才是多线程的优势。所以我理解这也是synchronized关键字可以修饰代码块的原因。代码块的一个简单例子如下:
package com.lenovo.plm.dms.p2;
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
class MyThread implements Runnable{
Integer num = 100;
String str = "";
@Override
public void run() {
// TODO Auto-generated method stub
print();
}
public void print(){
synchronized(num){
num = num + 10;
System.out.println(num);
}
}
}
运行结果如下:
120
120
130
140
这里居然是错误的,结果并没有不是预期的110 120 130 140。这里就奇怪了,为什么会不是同步的。我也给num加锁了,也同步了。为什么还是这样。最后我想明白了,我理解的想明白了。原理是这样的:
这里虽然加了num的锁,但是加锁之前,num并没有同步,因此每个线程取到的值有可能不同步。因此又写了如下程序来测试:
package com.lenovo.plm.dms.p2;
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
class MyThread implements Runnable{
Integer num = 100;
String str = "";
@Override
public void run() {
// TODO Auto-generated method stub
print();
}
public void print(){
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "--coming-------->" +num);
synchronized(num){
num = num + 10;
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "---------->" +num);
}
}
}
这里程序输出结果如下:
1455713670969:Thread-0--coming-------->100
1455713670969:Thread-3--coming-------->100
1455713670969:Thread-0---------->110
1455713670969:Thread-2--coming-------->100
1455713670969:Thread-2---------->130
1455713670969:Thread-1--coming-------->100
1455713670969:Thread-1---------->140
1455713670969:Thread-3---------->120
看coming打印出的结果就是各个线程取到并要进行操作num的值,都是100。因此虽然操作同步了,但是基数不同步。所以还是不行。这里我就换了一个对象来进行加锁,同时对num进行操作。程序如下:
package com.lenovo.plm.dms.p2;
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
class MyThread implements Runnable{
Integer num = 100;
String str = "";
@Override
public void run() {
// TODO Auto-generated method stub
print();
}
public void print(){
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "--coming-------->" +num);
synchronized(str){
num = num + 10;
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "---------->" +num);
}
}
}
这个时候结果如下:
1455713935709:Thread-1--coming-------->100
1455713935724:Thread-0--coming-------->100
1455713935724:Thread-3--coming-------->100
1455713935724:Thread-2--coming-------->100
1455713935724:Thread-1---------->110
1455713935724:Thread-2---------->120
1455713935724:Thread-3---------->130
1455713935724:Thread-0---------->140
结果虽然正确了,针对num是同步且可见的操作。但是这里线程操作num对象时,各线程间是可见的。原因这里就想不明白了。但是结论可以确定为,需要使用一个其他对象来做锁定才行。
这里还有一种情况的是如下就是代码块锁定非this对象和synchronized方法都存在时,运行逻辑是怎么样的。
package com.lenovo.plm.dms.p2;
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
class MyThread implements Runnable{
Integer num = 100;
String str = "";
@Override
public void run() {
// TODO Auto-generated method stub
print();
message();
}
public void print(){
synchronized(str){
num = num + 10;
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "---------->" +num);
}
}
synchronized public void message(){
System.out.println(System.currentTimeMillis() + ":"+Thread.currentThread().getName());
}
}
运行结果是正确的,但是线程调用顺序不同:
1455714331869:Thread-0---------->110
1455714331869:Thread-0
1455714331869:Thread-2---------->120
1455714331869:Thread-2
1455714331869:Thread-3---------->130
1455714331869:Thread-1---------->140
1455714331869:Thread-3
1455714331869:Thread-1
所以这里可以说明一点:synchronized(非this)和synchronized方法是异步执行的。这个也是多线程的优势个人理解。
关于synchronized的使用和理解就这么多,写多线程程序的主要矛盾可以理解为尽量使用细粒度的代码同步来达到最高的并发。