0.先导的问题代码
下面的代码演示了一个计数器,两个线程同时对i进行累加的操作,各执行1000000次.我们期望的结果肯定是i=2000000.但是我们多次执行以后,会发现i的值永远小于2000000.这是因为,两个线程同时对i进行写入的时候,其中一个线程的结果会覆盖另外一个.
public class AccountingSync implements Runnable { static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
要从根本上解决这个问题,我们必须保证多个线程在对i进行操作的时候,要完全的同步.也就是说到A线程对i进行写入的时候,B线程不仅不可以写入,连读取都不可以.
1.synchronized关键字的作用
关键字synchronized的作用其实就是实现线程间的同步.它的工作就是对同步的代码进行加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性.就像上面的代码中,i++的操作只能同时又一个线程在执行.
2.synchronized关键字的用法
指定对象加锁:对给定的对象进行加锁,进入同步代码块要获得给定对象的锁
直接作用于实例方法:相当于对当前实例加锁,进入同步代码块要获得当前实例的锁(这要求创建Thread的时候,要用同一个Runnable的实例才可以)
直接作用于静态方法:相当于给当前类加锁,进入同步代码块前要获得当前类的锁
2.1指定对象加锁
下面的代码,将synchronized作用于一个给定的对象.这里有一个注意的,给定对象一定要是static的,否则我们每次new一个线程出来,彼此并不共享该对象,加锁的意义也就不存在了.
public class AccountingSync implements Runnable { final static Object OBJECT = new Object(); static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (OBJECT) { increase(); } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }2.2直接作用于实例方法
synchronized关键字作用于实例方法,就是说在进入increase()方法之前,线程必须获得当前实例的锁.这就要求我们,在创建Thread实例的时候,要使用同一个Runnable的对象实例.否则,线程的锁都不在同一个实例上面,无从去谈加锁/同步的问题了.
public class AccountingSync implements Runnable { static int i = 0; public synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
请注意main方法的前三行,说明关键字作用于实例方法上的正确用法.
2.3直接作用于静态方法
将synchronized关键字作用在static方法上,就不用像上面的例子中,两个线程要指向同一个Runnable方法.因为方法块需要请求的是当前类的锁,而不是当前实例,线程间还是可以正确同步的.
public class AccountingSync implements Runnable { static int i = 0; public static synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
3.错误的加锁
从上面的例子里,我们知道,如果我们需要一个计数器应用,为了保证数据的正确性,我们自然会需要对计数器加锁,因此,我们可能会写出下面的代码:
public class BadLockOnInteger implements Runnable { static Integer i = 0; @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (i) { i++; } } } public static void main(String[] args) throws InterruptedException { BadLockOnInteger badLockOnInteger = new BadLockOnInteger(); Thread t1 = new Thread(badLockOnInteger); Thread t2 = new Thread(badLockOnInteger); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
当我们运行上面代码的时候,会发现输出的i很小.这说明线程并没有安全.
要解释这个问题,要从Integer说起:在Java中,Integer属于不变对象,和String一样,对象一旦被创建,就不能被修改了.如果你有一个Integer=1,那么它就永远都是1.如果你想让这个对象=2呢?只能重新创建一个Integer.每次i++之后,相当于调用了Integer的valueOf方法,我们看一下Integer的valueOf方法的源码:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
Integer.valueOf()实际上是一个工厂方法,他会倾向于返回一个新的Integer对象,并把值重新复制给i;
所以,我们就知道问题所在的原因,由于在多个线程之间,由于i++之后,i都指向了一个新的对象,所以线程每次加锁可能都加载了不同的对象实例上面.解决方法很简单,使用上面的3种synchronize的方法之一就可以解决了.
本文向大家介绍JAVA Static关键字的用法,包括了JAVA Static关键字的用法的使用技巧和注意事项,需要的朋友参考一下 static关键字的作用: 用来修饰成员变量和方法,被修饰的成员是属于类的,而不单单是属于某个对象的,也就是说,可以不靠对象来调用。 首先我们来介绍类变量 当static修饰成员变量时,该变量称为类变量,该类的每个对象都共享同一个类变量的值,任何 对象都可以更改该变量
对于可见性,Java 提供了 volatile 关键字来保证可见性和禁止指令重排。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。 从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详
Swift 5.x 关键字 mutating 的作用 1. 在实例方法中修改属性 结构体和枚举是值类型. 默认情况下, 值类型属性不能被自身的实例方法修改. 可以选择在func关键字前加上mutating关键字来指定实例方法内可以修改属性 e.g. struct Point { var x = 0.0, y = 0.0 mutating func moveBy(x de
下面是 Java 里面的关键字。不能使用以下任一作为您的程序标识符。关键字 const 和 goto 语句被保留,即使他们目前尚未使用。true, false, 和 null 似乎是关键字,但它们实际上是字面值;你不能使用它们作为你的程序标识符。 abstract continue for new switch assert*** default goto*
关键字是不能做为变量的。Lua 的关键字不多,就以下几个: and break do else elseif end false for function if in local nil not or repeat return then true until while
模型 21.1 model 视图 21.1 view 处理器方法 21.1 handler method 处理器 21.1 handler 控制器 21.1 controller 命令对象 21.1 command object 表单返回对象 21.1 form-backing object 响应 21.1 response 视图解析器 21.1 view resolver 验证器 21.1.1