目录
Synchronized 概述
1、编写一个类、方法、代码块时,如果其中的代码可能运行于多线程环境下,那么就要考虑同步的问题.
"线程安全"问题存在于"实例变量"中,如果是方法内部的私有变量,则不存在"线程安全"问题。
如果多个线程共同访问1个对象中的实例变量,则有可能出现"线程安全"问题。
2、Java 中内置了语言级的同步关键字-synchronized,这大大简化了Java 中多线程同步的使用
3、synchronized 是 Java 中的关键字,是一种同步锁,主要用于修饰方法、代码块
4、例如用 Account 实体类表示一个银行账户信息,主程序中生成 100 个子线程,每一个子线程都对此账户存 100 元,然后又取出 100 元,最终账户的余额应该还是 1000 元。如果线程不同步,则运行的结果极可能超出我们的想像,而且是操作的线程越多,错误出现的概率就越大。
注意事项
1)synchronized 可以定义在方法上,但 synchronized 并不属于方法定义的一部分,因此 synchronized 关键字不能被继承。
2)如果父类中的某个方法使用了 synchronized 关键字,即使子类中覆盖了这个方法,子类中的这个方法默认情况下也并不是同步的,必须显式地在子类的方法中加上 synchronized 关键字。
3)接口中的方法不能使用 synchronized 关键字定义
4)构造方法不能使用 synchronized 关键字,但可以使用 synchronized 代码块来进行同步
修饰代码块
1、被 synchronized 修饰的代码块 {} 称为同步代码块,其作用的范围是大括号 {xxx} 中的代码。
synchronized this · 对象锁
1、如下所示:synchronized(this) 是对象锁,作用对象是调用这个代码块的对象,可以同步不同线程中的同一个对象,下面能达到同步的目的。
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();//同一个对象
for (int i = 0; i < 3; i++) {
new Thread(myThread).start();//三个线程使用相同的对象
}
}
}
class MyThread implements Runnable {
private static int count = 9;
@Override
public void run() {
synchronized (this) {//设置对象锁,对不同线程中的同一任务对象有效
try {
for (int i = 0; i < 3; i++) {
count--;
System.out.print(Thread.currentThread().getName() + ":" + (count)+"\t");
TimeUnit.MILLISECONDS.sleep(500 + new Random().nextInt(1000));//随机休眠 0.5-1.5秒
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
控制台输出如下:
Thread-0:8 Thread-0:7 Thread-0:6 Thread-2:5 Thread-2:4 Thread-2:3 Thread-1:2 Thread-1:1 Thread-1:0
new Thread(new MyThread()).start();//如果上面改成这样,此时三个线程使用不同的对象,synchronized (this)对象锁无法同步
//如果使用线程池的方式也是同理,对象锁只对同一个对象有效
public static void main(String[] args) {
MyThread myThread = new MyThread();//同一个对象
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.execute(myThread);//多个线程中是同一个对象,对象锁可以同步
executorService.execute(new MyThread());//多个线程中已经不是同一个对象,所以对象锁无用
}
}
synchronized class · 类锁
1、作用的范围是 synchronized(Class class){xxx} 后面{}括起来的部分,作用的对象是这个类的所有对象——类锁。
2、对不同线程中的所有本类的对象有效
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
//三个线程使用不同的对象,此时synchronized (this) 对象锁无法同步,必须使用 类锁
new Thread(new MyThread()).start();
}
}
}
class MyThread implements Runnable {
private static int count = 9;
@Override
public void run() {
synchronized (MyThread.class) {//设置类锁,对本类的所有对象有效
try {
for (int i = 0; i < 3; i++) {
count--;
System.out.print(Thread.currentThread().getName() + ":" + (count) + "\t");
TimeUnit.MILLISECONDS.sleep(500 + new Random().nextInt(1000));//随机休眠 0.5-1.5秒
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
控制台输出如下:
Thread-0:8 Thread-0:7 Thread-0:6 Thread-2:5 Thread-2:4 Thread-2:3 Thread-1:2 Thread-1:1 Thread-1:0
修饰普通方法 · 对象锁
1、这里所说的"普通方法"是指 "非静态方法"
2、被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,对不同线程中的同一对象有效——对象锁;
3、一个线程访问一个对象的 synchronized(this){xxx} 代码块、或 synchronized 普通方法时,别的线程可以访问该对象的非synchronized 代码块和方法而不受阻塞。
4、一个线程访问一个对象的 synchronized(this){xxx} 代码块、或 synchronized 普通方法时,别的线程不可以访问该对象的其它synchronized 代码块和方法。
public synchronized void run() {
//写法一
}
//等价于
public void run() {
synchronized (this) {
//写法二
}
}
5、编码与上面的“synchronized this · 对象锁”完全一样,这里就不重复粘贴。
修饰静态方法 · 类锁
1、作用的是整个静态方法,作用的对象是这个类的所有对象——类锁;
2、静态方法是属于类的而不属于对象的,同样 synchronized 修饰的静态方法锁定的是这个类的所有对象。
public class Test {
public synchronized static void todo() {
//写法一
}
//等价于
public void info() {
synchronized (Test.class) {
//写法二
}
}
}
3、静态方法随着类的加载而加载,所以对象锁升级为类锁,同一时刻只能有一个线程的对象能访问。
4、编码与上面的 “synchronized Class · 类锁” 完全一样,这里不再累述。