线程安全是并发程序的根基,若不能保证执行结果的正确性,那么程序并发也没什么意义。
关键字synchronized
关键字 synchronized 的作用是实现线程之间的同步。它的工作是对同步的代码加锁。
关键字 synchronized 有多种用法:
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
指定加锁对象
package com.demo.base;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable, "T1");
Thread t2 = new Thread(myRunnable, "T2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count = " + myRunnable.count);
}
}
class MyRunnable implements Runnable {
private Object o = new Object();
int count = 0;
@Override
public void run() {
for(int i=0; i<100000; i++){
synchronized (this.o) {
count++;
}
}
}
}
直接作用于实例方法
package com.demo.base;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable, "T1");
Thread t2 = new Thread(myRunnable, "T2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count = " + myRunnable.count);
}
}
class MyRunnable implements Runnable {
int count = 0;
@Override
public void run() {
for(int i=0; i<100000; i++){
task();
}
}
private synchronized void task(){
count++;
}
}
直接作用于静态方法
package com.demo.base;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable, "T1");
Thread t2 = new Thread(myRunnable, "T2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count = " + myRunnable.count);
}
}
class MyRunnable implements Runnable {
static int count = 0;
@Override
public void run() {
for(int i=0; i<100000; i++){
task();
}
}
private static synchronized void task(){
count++;
}
}
注意
- 关键字 synchronized 除了用于线程安全,还可以保证线程间的可见性和有序性,在作用上可以完成代替关键字 volatile,但关键字 synchronized 会使代码块进入同步执行即串行执行,会影响程序的执行效率。
- 为了尽量提高程序的执行效率,在使用关键字 synchronized 时,尽量缩小同步代码块的大小。
synchronized锁可重入
synchronized锁可重入指的就是当一个线程持有了某一个对象锁,这时该线程就可以直接进入该对象锁的其他同步代码块,可重入最大的作用是避免死锁。死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable(){
@Override
public void run() {
synchronized (this){
System.out.println(Thread.currentThread().getName() + "线程抢到了锁");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
a();
}
}
public synchronized void a(){
System.out.println(Thread.currentThread().getName() + "线程执行a方法");
}
};
for(int i=0; i<100; i++){
new Thread(runnable).start();
}
}
}
synchronized锁误区
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable(){
private Integer num = 0;
@Override
public void run() {
num++;
synchronized (num){
System.out.println(Thread.currentThread().getName() + "线程抢到了锁");
System.out.println(Thread.currentThread().getName() + "线程释放了锁");
}
}
};
for(int i=0; i<100; i++){
new Thread(runnable).start();
}
}
}
实例变量num是Integer包装类对象,每次num++之后,本质已经与之前的num不是同一个对象了,不同线程进入同步代码块时申请的也就不是同一个对象锁了。