线程同步
- 当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数 据安全。
- 为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制。当一个线程获得对象的排它锁,其它线程必须等待,使用后释放锁即可。假如锁机制后会产生新问题:一个线程锁会导致其它所有需要此锁的线程挂起;在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题;如果一个高优先级的线程等待一个低优先级的线程释放锁会导致优先级倒置,引起性能问题。
- synchronized关键字实现线程同步。synchronized方法控制对“成员变量|类变量”对象的访问:每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。如果将一个大的方法声明为synchronized将会大大影响效率。
- synchronized两种使用方法,一种是同步代码块:synchronized (obj){},另一种是同步方法: private synchronized void Function(int amt) {}。
synchronized同步方法
code1:
- 为方法添加synchronized,即同一时间,只允许一个线程访问此方法
- 只有实例对象才会调用sell()方法,synchronized锁的不是sell()方法,而是调用此方法的对象
/**
* 测试synchronized修饰方法
*
* @author dxt
*
*/
public class SynTest01 {
public static void main(String[] args){
synWeb12306 w = new synWeb12306();
new Thread(w, "aa").start();
new Thread(w, "bb").start();
new Thread(w, "cc").start();
}
}
/**
* 模拟12306取票过程
* @author dxt
*
*/
class synWeb12306 implements Runnable{
//票数
private int tickets = 10;
//是否有票
private boolean flag = true;
public void run(){
while(flag){
//每一次卖票都有时间间隔
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票
sell();
}
}
/**
* 如果票数>0, 则卖票
* 为方法添加synchronized,即同一时间,只允许一个线程访问此方法
* 只有实例对象才会调用sell()方法,synchronized锁的不是sell()方法,而是调用此方法的对象
*/
public synchronized void sell(){
if(tickets <= 0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买了第 " + tickets + " 张票");
tickets--;
}
}
结果:
code2:
对方法添加锁,但锁定的对象并不应该是this对象,而是this对象中的Account2对象,因此依旧会产生线程安全问题,
/**
* 测试synchronized修饰方法,而实际是为对象实例上锁
* @author dxt
*
*/
public class SynTest02 {
public static void main(String[] args){
Account2 account = new Account2(100, "dxt");
Drawing2 me = new Drawing2(account, 80);
Drawing2 me2 = new Drawing2(account, 80);
new Thread(me).start();
new Thread(me2).start();
}
}
/**
* 账户
* @author dxt
*
*/
class Account2{
int money; //账户金额
String name;
public Account2(){}
public Account2(int money, String name){
super();
this.money = money;
this.name = name;
}
}
/**
* 模拟取款
* @author dxt
*
*/
class Drawing2 implements Runnable{
Account2 account; //取钱的账户
int getMoney; //所取得钱数
int getTotalMoney; //取钱总数
int packetMoney; //口袋中的钱
public Drawing2(Account2 a, int getMoney){
super();
this.account = a;
this.getMoney = getMoney;
}
/**
* 实现run()方法
*/
public void run(){
drawMoney();
}
/**
* 用synchronized修饰
* 目标锁定account
*/
public synchronized void drawMoney(){
//对账户金钱进行控制(账户余额不能小于0),实际对于多线程没有作用
if(account.money - getMoney < 0){
return;
}
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
account.money -= getMoney;
packetMoney += getMoney;
System.out.println(Thread.currentThread().getName() + "账户余额:" + this.account.money);
System.out.println(Thread.currentThread().getName() + "身上现金:" + this.packetMoney);
}
}
结果:
synchronized同步代码块
方法说明
synchronized (obj){}
obj称之为同步监视器(需要加锁的数据),它可以是任何对象(不变的),但推荐使用共享资源作为同步监视器。
对比于上述的同步方法,同步方法中的同步监视器不需指定,就是this即该对象本身。
同步监视器执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问。
- 第一个线程访问完毕,解锁同步监视器。
- 第二个线程访问,发现同步监视器未锁,锁定并访问。
测试
code:
/**
* 测试synchronized修饰代码块
* @author dxt
*
*/
public class SynTest02 {
public static void main(String[] args){
Account2 account = new Account2(100, "dxt");
synDrawing me = new synDrawing(account, 80);
synDrawing me2 = new synDrawing(account, 80);
new Thread(me).start();
new Thread(me2).start();
}
}
/**
* 账户
* @author dxt
*
*/
class Account2{
int money; //账户金额
String name;
public Account2(){}
public Account2(int money, String name){
super();
this.money = money;
this.name = name;
}
}
/**
* 模拟取款
* @author dxt
*
*/
class synDrawing implements Runnable{
Account2 account; //取钱的账户
int getMoney; //所取得钱数
int getTotalMoney; //取钱总数
int packetMoney; //口袋中的钱
public synDrawing(Account2 a, int getMoney){
super();
this.account = a;
this.getMoney = getMoney;
}
/**
* 实现run()方法
*/
public void run(){
drawMoney();
}
/**
* 用synchronized修饰
* 目标锁定account
*/
public synchronized void drawMoney(){
if(account.money <= 0){
return;
}
synchronized(account){
if(account.money - getMoney < 0){
return;
}
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
account.money -= getMoney;
packetMoney += getMoney;
System.out.println(Thread.currentThread().getName() + "账户余额:" + this.account.money);
System.out.println(Thread.currentThread().getName() + "身上现金:" + this.packetMoney);
}
}
}
code2:
import java.util.ArrayList;
import java.util.List;
public class synTest03 {
public static void main(String[] args) throws InterruptedException{
final List<String> list = new ArrayList<String>();
//容器中装10000个对象
for(int i=0; i<10000; i++){
//匿名内部类
new Thread(new Runnable(){
public void run(){
//为list加锁
synchronized(list){
list.add(Thread.currentThread().getName());
}
}
}).start();
}
Thread.sleep(1000); //等一秒
System.out.println(list.size()); //硬挨为10000,实际要小于10000
}
}