线程安全性概念:
一个类或程序在多线程环境下,其行为与预期结果一致的特性。
何时会产生线性安全问题?
当多个线程同时访问临界资源时(一个对象、一个文件、一个数据库、对象的属性)会导致程序运行结果与预期结果不同,即出现线性安全问题。
临界资源:在一段时间内只允许一个线程访问的资源
注:不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。
如何解决线性安全问题?
大多数并发模式在解决线性安全问题时,都采用“序列化访问临界资源”(时间片轮询方式),即同一时刻只能一个线程访问临界资源,也称为同步互斥访问。
实现方式:在访问临界资源的代码前面加一个锁(synchronized['sɪŋkrənaɪzd]和Lock),当访问完临界资源后释放锁,让其他线程访问。
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
Synchronized同步方法或同步块
用synchronized标记一个方法或者代码块,当某个线程访问该对象的synchronized方法或者代码块时,这个线程获得了该对象的锁,其他线程暂时无法访问该方法,只能等待这个方法执行完毕或代码块执行完毕,这个线程才会释放该对象的锁,其他线程就可以访问该方法或代码
特点:
1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,
3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
package com.cdd.dao;
import java.util.ArrayList;
import java.util.List;
public class InsertData {
private List<Integer> list = new ArrayList<Integer>();
private Object object = new Object();
//未加锁
public void insert(Thread thread){
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"插入数据"+i);
list.add(i);
}
}
//synchronized同步方法
public synchronized void insert0(Thread thread){
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"插入数据"+i);
list.add(i);
}
}
//另一个synchronized同步方法.当线程在访问一个对象的synchronized方法时,那么其他线程不能访问该对象的其他synchronized方法。(可以访问非synchronized方法)
//因为一个对象只有一个锁,当一个线程获取该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象其他synchronized方法
public synchronized void print(Thread thread){
System.out.println(thread.getName()+"访问");
}
//synchronized同步代码块
public void insert1(Thread thread){
synchronized (this) {
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"插入数据"+i);
list.add(i);
}
}
}
public void insert2(Thread thread){
synchronized (object) {
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"插入数据"+i);
list.add(i);
}
}
}
}
//测试
<pre name="code" class="java">public static void main(String[] args) {
final InsertData insertData = new InsertData();
new Thread(){
public void run(){
insertData.insert0(Thread.currentThread());
}
}.start();
new Thread(){
public void run(){
insertData.print(Thread.currentThread());
}
}.start();
}
每个类也会有一个锁,它可以用来控制对static数据成员的并发访问(例如:单例模式)
Synchronized同步方法或代码块获取锁线程释放锁的两种情况:
1、获取锁的线程执行完毕,释放对锁的占用
2、对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。
互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。