前言
本篇博文结合的代码涉及以下知识,可查看我之前的文章
java之Thread类详细分析(全)
【操作系统】线程与进程的深入剖析(全)
造成线程安全主要是数据共享,为了解决这种情况
引出synchronized 是 Java 中的关键字,是一种同步锁(对方法或者代码块中存在共享数据的操作)。同步锁可以是任意对象
具体修饰的对象有3种方式
- 修饰代码块
- 修饰方法
- 修饰静态方法
- 被修饰的代码块称为同步语句块,作用的范围是大括号{ }内的内容
- 修饰的是方法,其作用范围为整个方法,作用对象是调用该代码块的对象(虽然可以修饰方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承)
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象(修饰一个类也同理)
1. 实例方法
-
实例方法不是静态方法
-
如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。或者在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步
一个线程对应一个对象 synchronized 实例方法,其他线程不能对该对象的其他 synchronized 方法访问(一个对象只有一把锁)。大概意思是当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,但其他线程可以访问该对象的其他非synchronized方法两个不同对象访问不同个东西,不会产生歧义,但是如果同一个对象访问同一个东西会有歧义而且不加锁
两个线程中同一个对象访问,加了锁机制不会出错
public class aa implements Runnable{{
//共享资源(临界资源)
static int i=0;
/**
* synchronized 修饰实例方法
*/
public synchronized void add(){
i++;
}
@Override
public void run() {
for(int j=0;j<10;j++){
add();
}
}
public static void main(String[] args)
{
aa b=new aa();
Thread m1=new Thread(b);
Thread m2=new Thread(b);
m1.start();
m2.start();
m1.join();
m2.join();
System.out.println(i);
}
}
但如果两个线程两个不同对象访问,加了锁的机制还是会出错
aa b=new aa();
aa c=new aa();
Thread m1=new Thread(b);
Thread m2=new Thread(c);
需要将其方法添加为静态方法即可
因为两个不同对象,访问同一个锁会出错,定义为静态方法,即当前类的对象锁,不会出现歧义对象
2. 静态方法
对静态方法加锁,锁是当前类的class对象锁
public static synchronized void add(){
i++;
}
3. 代码块
class Ticket {
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale() {
//判断:是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" :
"+(number--)+" "+number);
}
} }
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
- 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,但一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到