synchronized保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。当多个并发线程访问同一个对象object中的同步代码块时,一个时间内只有一个线程能够得到执行,另一个线程必须等到当前线程执行完这个代码块之后才能执行,但是其他的线程仍然能够访问该object中的非synchronized同步代码块。
java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
当synchronized作用于实例方法时,所示其实例对象。
在下面的例子中定义了一个静态变量num,定义了一个incr()方法对num进行+1的操作,在run方法中是循环调用incr方法5次。最后在main方法中是创建了两个线程对num进行操作。在该方法中synchronized修饰的是实例方法incr,此时线程的锁便是实例对象instance。
一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他的线程就无法获取该对象的锁,但是其他的对象还是可以访问该实例对象的其他非synchronized方法。
public class Test1 implements Runnable{
public static int num = 0;
public synchronized void incr() {
num++;
System.out.println("Thread-Id:"+Thread.currentThread().getId()+" num="+num);
}
@Override
public void run() {
for(int i = 0; i< 5; i++) {
incr();
}
}
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
Thread t1 = new Thread(test1);
Thread t2 = new Thread(test1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("num="+num); /**num=10*/
}
}
在上面的例子中是两个线程共同操作一个对象,在下面的例子中可以看到两个线程操作的是不同的对象,这时候不同的对象有不同的锁,所以都会获取到各自的锁,这时候获取到的结果就不一定是10了,也许是比10小的数。这种情况下是不满足线程安全的。
public class Test1 implements Runnable{
public static int num = 0;
public synchronized void incr() {
num++;
System.out.println("Thread-Id:"+Thread.currentThread().getId()+" num="+num);
}
@Override
public void run() {
for(int i = 0; i< 5; i++) {
incr();
}
}
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
Test1 test2 = new Test1();
Thread t1 = new Thread(test1);
Thread t2 = new Thread(test2);
t1.start();
t2.start();
//t1.join();
//t2.join();
//System.out.println("num="+num); /**num=10??*/
}
}
当synchronized作用与静态方法时,锁是当前类的class对象。
由于静态方法是类成员,因此通过class对象锁可以控制静态成员的并发操作。如果一个线程A调用一个实例对象的非静态同步方法,线程B调用这个实例对象所属的类的静态同步方法,他们之间是不存在互斥的,因此两个线程占用的锁是不一样的。但是如果两个方法共同操作同一个静态变量num,那么还是需要考虑线程安全的问题。
public class Test1 implements Runnable{
public static int num = 0;
/**
* 非静态同步方法,占用的锁是当前实例对象的锁
*/
public synchronized void incr1() {
num++;
}
/**
* 静态同步方法,占用的锁是当前类的锁
*/
public static synchronized void incr() {
num++;
System.out.println("Thread-Id:"+Thread.currentThread().getId()+" num="+num);
}
@Override
public void run() {
for(int i = 0; i< 5; i++) {
incr();
}
}
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
Test1 test2 = new Test1();
Thread t1 = new Thread(test1);
Thread t2 = new Thread(test2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("num="+num); /**num=10*/
}
}
从下面的例子中可以看出,是将synchronized作用于一个给定的实例对象instance,当前实例对象就是锁对象,每次线程进入synchronized的代码块时就要求当前线程必须拿到instance实例的对象所,其他的线程就必须等待。
public class Test1 implements Runnable{
public static int num = 0;
static Test1 instance = new Test1();
@Override
public void run() {
synchronized(instance) {
for(int i = 0; i< 5; i++) {
num++;
System.out.println("Thread-Id:"+Thread.currentThread().getId()+" num="+num);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("num="+num); /**num=10*/
}
}
volatile是一个类型修饰符,他主要用于修饰被不同的线程访问和修改的变量,他是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
可以确保将变量的更新操作通知到其他的线程,当把变量声明为volatile类型,编译与运行时都会注意到这个变量是共享的,因此该变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会执行线程阻塞,他是一种比synchronized关键字更轻量级的同步机制。
class volatileTest implements Runnable {
private volatile boolean flag = false;
public void run() {
for (int i = 0; i < 5; i++) {
try {
System.out.println("Thread-name:"+Thread.currentThread().getName()+" i:"+i+" flag="+flag);
//线程名为test1,则将flag值改为true,同时打印出新旧flag值
if("test1".equals(Thread.currentThread().getName())) {
boolean oldflag = flag;
flag = true;
System.out.println("test1 oldflag="+oldflag+" flag="+flag);
}else if("test2".equals(Thread.currentThread().getName())) {
//如果线程名为test2,则将flag值修改为false,同时打印出新旧flag值
boolean oldflag = flag;
flag = false;
System.out.println("test2 oldflag="+oldflag+" flag="+flag);
}
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
volatileTest test = new volatileTest();
Thread t1 = new Thread(test, "test1");
Thread t2 = new Thread(test, "test2");
t1.start();
t2.start();
}
}
在上面的例子中可以看到,当两个线程同时操作volatile修饰的变量flag时,两个线程每次拿到的都是flag的最新的值,但是这个时候对于获取到flag的值两个线程并没有一个互斥的操作,并不能保证操作的原子性。
(1)volatile的可见性:当多个线程访问同一个变量时,一个线程修改了这个变量,其他得线程能够立即看的到值得改变,这一个特性对于volatile是满足的。
(2)volatile的原子性:不满足,对于下面的例子,正常期望的结果应该是每次运行都能得到值为10000,但是在实际的运行结果中发现每次得到的结果都不一样,因此volatile虽然保证了可见性,确保每次拿到的值都是最新的,但是不能确保操作的原子性。
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
1.synchronized关键字一次只允许一个线程持有某个特定的锁,因此可以使用该特性实现对共享数据的访问,一次只有一个线程能够使用该共享数据。
2.volatile主要是使变量的值在发生改变时能尽快的让其他的线程知道,使线程获取到数据是最新的。
3.volatile本质是告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他的线程被阻塞住。
4.volatile仅能够用在变量级别,synchronized则可以使用在变量和方法中。
5.volatile仅能够实现变量修改的可见性,而synchronized可以保证变量修改的可见性和原子性。
6.volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。