synchronized
在并发程序设计中,线程的安全无疑是极为重要的。保证了性能的同时也必须保证安全性。线程安全是并行程序的基础。加入了volatile关键字并不能绝对保证线程安全,他的作用只是让这个变量对于线程来说是变化可见的。上篇提到过。如果要保证线程的安全那么synchronized则是一大利器。
上文我们已经提过了volatile如果遇到多个线程同时修改,那么他并不能保证自己的线程安全。要从根本上解决问题就必须保证多个线程对变量的操作完全同步。当a线程使用变量资源的时候,其他任意资源都不能访问这个变量。
synchronize是对资源的加锁操作,保证线程的安全。因此我们来优化一下上篇的代码。
public class Main implements Runnable{
static Main ins = new Main();
static volatile int j = 0;
public static void main(String[] args) {
Thread t1 = new Thread(ins);
Thread t2 = new Thread(ins);
t1.start();
t2.start();
try {
t1.join();
t2.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(j);
}
public void run() {
for(int i = 0;i < 100000;i++) {
synchronized(ins) {
j++;
}
}
}
}
也就是在run方法里给j++这个操作加锁,使得任意线程进来执行这个方法都需要获取当前这个对象的锁,因此j++这个操作就被同步了。于是就这样保证了线程的安全。
synchronized还可以写成其他形式,我们把上述代码的后段重写一下。
public synchronized void sum() {
j++;
}
public void run() {
for(int i = 0;i < 100000;i++) {
sum();
}
}
关于加锁的方式有很多需要注意的,以下这种是错误的加锁方式。
public class Main implements Runnable{
static Main ins = new Main();
static volatile int j = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new Main());
Thread t2 = new Thread(new Main());
t1.start();
t2.start();
try {
t1.join();
t2.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(j);
}
public synchronized void sum() {
j++;
}
public void run() {
for(int i = 0;i < 100000;i++) {
sum();
}
}
}
乍一看好像一点问题都没有。但问题的关键是在创建线程的地方,传入的参数是新创建的对象。也就是synchronized在加锁的过程中,锁住的是各自新创建出来的对象的锁。也就是这两个锁,锁住了两个不同的对象,自然会导致线程的不安全。
但我们只需要做一些小小的改动。
public static synchronized void sum() {
j++;
}
这样就算两个线程持有不同的锁,但这个方法需要请求当前类的锁,所以线程还是可以实现同步。从可见性上,synchronized可以完全代替volatile。