多线程的同步是在指定区域形成只允许单线程运行的机制。
- synchronized同步方法
- synchronized同步语句块
- volatile关键字
1.synchronized同步方法
非线程安全问题存在于实例变量中,如果是方法内部的私有变量则不存在非线程安全问题。
如果多个线程共同访问一个对象中的实例变量,则有可能出现非线程安全问题。
关键字synchronized取得的锁都是对象锁,不是对一段代码或者是函数当作锁。多个线程调用同一个对象过程当中,谁先调用有synchronized方法,谁就先获得该对象的锁。只有共享资源的读写才会用到同步处理,其他情况不需要考虑同步问题。参考代码如下:
共享资源的bean:
public class BusinessBean {
private float price=10.00f;
synchronized public void cook() {
System.out.println("I am cooking");
price+=1.00f;
System.out.println("price is "+price);
System.out.println("thread wake up , and thread name is "+Thread.currentThread().getName());
}
}
引用共享资源操作多线程bean定义:
第一种:
public class MyThread extends Thread{
private BusinessBean bb;
public MyThread(BusinessBean bb) {
this.bb=bb;
}
@Override
public void run(){
Thread.currentThread().setName("I am Fisher");
bb.cook();
System.out.println("It's done");
}
}
第二种:
public class RunThread implements Runnable{
private BusinessBean bb;
public RunThread(BusinessBean bb) {
this.bb=bb;
}
@Override
public void run(){
Thread.currentThread().setName("I am Farmer");
bb.cook();
System.out.println("It's done");
}
}
测试同步的main:
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
BusinessBean bb=new BusinessBean();
MyThread mt=new MyThread(bb);
Thread rnth=new Thread(new RunThread(bb));
rnth.start();
mt.start();
}
测试结果:
main
I am cooking
price is 11.0
thread wake up , and thread name is I am Fisher
I am cooking
price is 12.0
thread wake up , and thread name is I am Farmer
It's done
It's done
脏读问题出现在操作同一个示例变量的情况下,不同线程争夺实例变量时出现。特别是对非共享变量采取非同步方法进行操作的时候,很容易出现。synchronized拥有锁的重入功能,当一个线程获得一个对象的锁之后,再次请求改对象的锁时是可以再次获得该对象的锁的。在一个方法中无论嵌套多少次synchronized都只会获得一个对象锁,这个对象锁被多个当前多次调用的synchronied共享。子类可以通过“可重入锁”调用父类的同步方法。当一个线程执行代码出现异常的时候,其占有的锁都会自动释放。
2.synchronized同步语句块
synchronized声明的方法在有些情况下有点麻烦,如果任务执行时间很长,那么等待执行的时间会很长。在synchronized块中的代码是同步执行,不在的是异步执行synchronized(this)代码块锁定的还是当前对象。
将上面线程中共享资源的bean修改如下:
public class BusinessBean {
private float price=10.00f;
public void cook() {
System.out.println("I am cooking");
synchronized(this) {
System.out.println("price is "+price + " thread is " +Thread.currentThread().getName());
price+=1.00f;
System.out.println("price is "+price + " thread is " +Thread.currentThread().getName());
}
System.out.println("thread name is "+Thread.currentThread().getName());
}
}
继续运行测试的结果如下:
main
I am cooking
I am cooking
price is 10.0 thread is I am Farmer
price is 11.0 thread is I am Farmer
price is 11.0 thread is I am Fisher
price is 12.0 thread is I am Fisher
thread name is I am Fisher
It's done
thread name is I am Farmer
It's done
synchronized(非this对象)也是同步的一种,里面 非this对象表示锁对象,在正常的同步过程中,只有相同的所对象才会起到同步的作用。如果是不同的所对象,表示锁的层次不一样,不会体现同步效果。synchronized(class)对静态方法加锁也是一种同步方式,锁定的对象是类,它的锁定范围要比对象锁的范围大很多,类锁相当于对所有的类对象加锁。synchronized(x),x的锁类型不可以是String类型的,x的锁类型是对象类型的。对于多个不同的锁对象来说,持有相同锁的多线程是同步的,持有不同锁的多线程是异步的。
3.volatile关键字
关键字volatile的作用是强制从公共堆栈取得变量的值,而不是从线程私有数据栈取得变量的值。它只能修饰变量,不会发生阻塞,能保证数据可见性,不保证原子性,也主要解决变量在多个线程之间的可见性。synchronized解决的是多个线程访问资源的同步性。线程的安全性包括原子性和可见性两个方面,java同步机制也是围绕这两个方面进行的。
将上面线程的共享资源bean修改如下:
public class BusinessBean {
private float price=10.00f;
private Object obj=new Object();
volatile private int flag;
public void cook() {
System.out.println("I am cooking");
flag=flag+1;
System.out.println("flag is "+flag);
synchronized(obj) {
System.out.println("price is "+price + " thread is " +Thread.currentThread().getName());
price+=1.00f;
System.out.println("price is "+price + " thread is " +Thread.currentThread().getName());
}
System.out.println("thread name is "+Thread.currentThread().getName());
}
}
运行结果如下:
main
I am cooking
I am cooking
flag is 2
flag is 1
price is 10.0 thread is I am Fisher
price is 11.0 thread is I am Fisher
thread name is I am Fisher
It's done
price is 11.0 thread is I am Farmer
price is 12.0 thread is I am Farmer
thread name is I am Farmer
It's done
这个效果是乱序的,同步还是有点问题的。
关键总结信息
使用synchronized关键字做多线程同步的时候,它会同步运行程序块中私有变量和公共内存中的变量,所以使用它做同步的效果是不错的,当然也有JVM不停优化的功劳。
使用volatile关键字做多线程的时候,要保证volatile修饰的变量不会有两个线程同时对它修饰的变量进行修改。这个时候就会体现出volatile的效果了。普通的线程用到它的时候不多,但特别的线程,例如守护线程会有很高的可能性使用它。
使用原子类也可以做同步,但是这个多线程同步范围仅限于对原子类对象的操作是同步的,当多线程的同步范围扩大的时候,容易出现问题。当然原子类的同步强度是超过voiatile修饰的变量。
使用多线程要“外练互斥,内修可见”。多线程的同步是以消耗更多的时间来降低多线程整体的运行速度保证安全性的,同步的要求越高,消耗的时间代价越高。