目录
1)线程同步机制简介
协调多个线程之间数据访问的机制,java平台提供的线程同步机制包括:锁,volatile关键字,final关键字,static关键字,以及相关的API,如Object.wait()/Object.notify()等
2)锁的概念
线程安全的前提是多个线程并发访问共享数据,只要把多个线程在访问共享数据时的并发访问改成串行访问,就可以解决线程安全问题。JVM 把锁分为内部锁和显示锁两种. 内部锁通过 synchronized 关键字实现; 显示锁通过 java.concurrent.locks.Lock 接口的实现类 实现的
2.1)锁的实现方式
也可以说有N把锁,但是钥匙只有一把,一次只能打开一把锁,不能同时打开
2.2)锁的作用
锁可以实现对共享数据的安全访问,保障线程的原子性、可见性与有序性。
原子性:一个锁只能被一个线程持有,这就保证临界区的代码一次只能被一个线程执行。既具备了原子性
可见性:在java平台中,锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的操作,这就保存数据是最新的
有序性:写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的
注意:
这些线程在访问共享数据时必须使用同一个锁(如都是对数据A的操作,但不是同一个锁,也会导致线程安全问题)
即使用是读取共享数据的线程也需要使用同步锁(如果不使用锁,当某个锁读取数据时,可能有其它线程正在修改数据,就会造成脏读)
2.3)锁相关的概念
1)可重入性:一个线程持有该锁的时候能再次(多次)申请该锁,相当于已经有该锁的钥匙了,下次再次遇到这个锁,还是能打开
在methodA中获取a锁后,b中能再次获取A锁
void methodA(){
申请 a 锁
methodB();
释放 a 锁
}
void methodB(){
申请 a 锁
....
释放 a 锁
}
2)锁的争用与调试
java平台中内部锁属于非公平锁,显示Lock锁既支持公平锁也支持非公平锁(非公平锁就是如果 A线程释放了a锁,再次申请a锁会比其它线程申请成功的概率大)
3)锁的粒度
一个锁可以保护的共享数据的数量大小称为锁的粒度,锁的粒度过粗会导致线程在申请锁时会时行不必要的等待,粒度过细会增加锁调试的开销
内部锁:synchronized关键字
![](https://i-blog.csdnimg.cn/blog_migrate/59fb7f4011fca1b2b2da81b4de10b764.png)
3)内部锁:synchronized关键字
3.1)synchronized同步代码块
![](https://i-blog.csdnimg.cn/blog_migrate/375ad9edb6083624826b427be3123a74.png)
Test03 test03 = new Test03();
Thread r = new Thread(new Runnable() {
@Override
public void run() {
test03.mm();
}
});
r.setName("0");
r.start();
Thread r1 = new Thread(new Runnable() {
@Override
public void run() {
test03.s();
}
});
r1.setName("1");
r1.start();
}
public void mm(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public void s(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
3.2)同步方法
同步方法默认锁是this,与synchronized(this){}代码块是一个锁
、
ps:虽然执行的方法不同,但是同步锁都是this(test03),所以先执行完mm()后在执行ss1()
Test04 test03 = new Test04();
Thread r = new Thread(new Runnable() {
@Override
public void run() {
test03.mm();
}
});
r.setName("0");
r.start();
Thread r1 = new Thread(new Runnable() {
@Override
public void run() {
test03.ss1();
}
});
r1.setName("1");
r1.start();
}
public synchronized void ss1(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public void mm(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
3.3)同步静态方法
静态方法默认的锁是调用它类的类对象,也称为类锁
bb1()与s()锁对象都是调用它们的类锁Test04.class
Test04 test03 = new Test04();
Thread r = new Thread(new Runnable() {
@Override
public void run() {
test03.bb1();
}
});
r.setName("0");
r.start();
Thread r1 = new Thread(new Runnable() {
@Override
public void run() {
test03.s();
}
});
r1.setName("1");
r1.start();
}
public synchronized static void bb1(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public void s(){
synchronized (Test04.class){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
4)死锁
解决死锁可以采用获取锁顺序一致的方法解决(既线程a与线程b都是先获取key1锁,在获取key2锁)
下边就是一个典型的死锁:
a线程获取Key1锁
b线程获取Key2锁
a线程获取key2锁,由于key2在b线程,等待b线程释放锁
b线程获取key1锁,由于key1在a线程,等待a线程释放锁
a线程获取key2锁,由于key2在b线程,等待b线程释放锁
b线程获取key1锁,由于key1在a线程,等待a线程释放锁
.........
public class Test05 {
public static void main(String[] args) {
Ru ru = new Ru();
ru.setName("a");
ru.start();
Ru ru1 = new Ru();
ru1.setName("b");
ru1.start();
}
static class Ru extends Thread{
private static final Integer key1 = new Integer(1);
private static final Integer key2 = new Integer(1);
@Override
public void run() {
if(this.getName().equals("a")){
synchronized (key1){
System.out.println("线程a获的了key1锁,还要获取key2锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (key2){
System.out.println("线程a获的了key2锁");
}
}
}
if(this.getName().equals("b")){
synchronized (key2){
System.out.println("线程b获的了key2锁,还要获取key1锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (key1){
System.out.println("线程b获的了key1锁");
}
}
}
}
}
}