146.synchronized同步方法与块

将之前写的线程不安全的web12306模拟抢票代码,引入线程安全 1的处理办法

package zy.thread;


public class unsafeWeb12306 implements Runnable{
     
	private boolean flag = true;
	private int ticketNums = 10;
	public void run() {
     
		while(flag) {
     
			get();
		}
	}
	
	public void get() {
     
		if(ticketNums <= 0) {
     
			flag = false;
			return;
		}
		try {
     
			Thread.sleep(200);
		} catch (InterruptedException e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
	}
	
	public static void main(String[] args) {
     
		unsafeWeb12306 w1 = new unsafeWeb12306();
		System.out.println(Thread.currentThread().getName()); //main
		
		new Thread(w1, "码蓄").start();
		new Thread(w1, "码农").start();
		new Thread(w1, "码蝗").start();
	}
}

以上代码的执行结果是,同一张票会被多人获得,没有票的时候也能抢到票,是因为对临界资源票的访问没有加锁

146.synchronized同步方法与块_第1张图片

出现负数情况的原因是,最后一张票的时候,多个线程同时跑到抢票的步骤,也就是说控制条件没有限制住。同一张票被多个人抢到的情况是因为每个线程都有自己独立的工作空间,在第一个线程将更改完的数据写回到主存之前,其它线程已经将主存的数据拷贝到自己的工作空间,因此就会出现同一张票被多个线程抢到的情况,总结就是临界资源的访问和线程之间同步的问题。

使用synchronized方法,使抢票代码线程安全 2

package zy.thread;


public class safeWeb12306 implements Runnable{
     
	private boolean flag = true;
	private int ticketNums = 10;
	public void run() {
     
		while(flag) {
     
			get();
		}
	}
	
	public synchronized void get() {
     
		if(ticketNums <= 0) {
     
			flag = false;
			return;
		}
		try {
     
			Thread.sleep(200);
		} catch (InterruptedException e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
	}
	
	public static void main(String[] args) {
     
		safeWeb12306 w1 = new safeWeb12306();
		System.out.println(Thread.currentThread().getName()); //main
		
		new Thread(w1, "码蓄").start();
		new Thread(w1, "码农").start();
		new Thread(w1, "码蝗").start();
	}
}

synchronized修饰的是safeWeb12306类的成员方法,线程执行的时候锁的就是this对象,this的数据成员的修改都是互斥的

线程不安的银行存取款代码

package zy.thread;

public class unsafeAccount {
     
	public static void main(String[] args) {
     
		Account a = new Account(100, "原麦山丘");
		drawing boy = new drawing(a, 60, "boyDrawing");
		drawing girl = new drawing(a, 80, "girlDrawing");
		boy.start();
		girl.start();
	}
}

//账户
class Account{
     
	int money;
	String name;
	public Account(int money, String name) {
     
		super();
		this.money = money;
		this.name = name;
	}
}

//取款的业务类
class drawing extends Thread{
     
	Account account;
	int drawingMoney;
	int packetTotal;
	
	public drawing(Account account, int drawingMoney, String name) {
     
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
     
		get();
	}
	
	public void get() {
     
		if(account.money < drawingMoney)
			return;
		try {
     
			Thread.sleep(1000);
		} catch (InterruptedException e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		account.money -= drawingMoney;
		packetTotal += drawingMoney;
		System.out.println(this.getName() + "账户余额" + account.money);
		System.out.println(this.getName() + "口袋余额" + packetTotal);
	}
}

即使加了如下的判断,在网络延时的情况下,判断无法发挥作用,最终线程不安全,最终账户剩余余额为负数

if(account.money < drawingMoney)
			return;
146.synchronized同步方法与块_第2张图片

对以上银行取款改进

package zy.thread;

public class safeAccount {
     
	public static void main(String[] args) {
     
		synAccount a = new synAccount(100, "原麦山丘");
		syndrawing boy = new syndrawing(a, 60, "boyDrawing");
		syndrawing girl = new syndrawing(a, 80, "girlDrawing");
		boy.start();
		girl.start();
	}
}

//账户
class synAccount{
     
	int money;
	String name;
	public synAccount(int money, String name) {
     
		super();
		this.money = money;
		this.name = name;
	}
}

//取款的业务类
class syndrawing extends Thread{
     
	synAccount account;
	int drawingMoney;
	int packetTotal;
	
	public syndrawing(synAccount account, int drawingMoney, String name) {
     
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
     
		get();
	}
	
	/*
	 * 给get方法用synchronized,实际是给this加锁
	 * 是不能达到线程安全的,锁定的应该是操作的account
	 * 使用同步块就可完成
	 */
	public void get() {
     
		/*同步块:
		 * synchronized(account) {...}
		 * account称之为同步监视器
		 * 同步监视器只能被一个线程锁定
		 * 只有锁定同步监视器的线程才可以执行,不然就处于同步阻塞状态
		 */
		if(account.money < drawingMoney)
			return;
		synchronized(account) {
     
			if(account.money < drawingMoney)
				return;
			try {
     
				Thread.sleep(1000);
			} catch (InterruptedException e) {
     
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			account.money -= drawingMoney;
			packetTotal += drawingMoney;
		}
		System.out.println(this.getName() + "账户余额" + account.money);
		System.out.println(this.getName() + "口袋余额" + packetTotal);
	}
}

同步块语法:

synchronized(对象obj) {...}

obj称之为同步监视器

  1. 同步监视器只能被一个线程锁定

  2. 只有锁定同步监视器的线程才可以执行,不然就处于同步阻塞状态

在进程同步块之前要再加上条件判断,这样可以提高效率,不用阻塞等待锁定同步监视器才判断

if(account.money < drawingMoney)
			return;
synchronized(account) {...}

总结:synchronized的成员方法,锁定的实际时this对象 3
同步块锁的实际是成员方法所能访问的某一个数据成员

操作容器

多线程在操作容器时,如果对容器不加锁就会导致覆盖操作,线程不安全

package zy.thread;

import java.util.List;
import java.util.ArrayList;

public class unsafeList {
     
	public static void main(String[] args) {
     
		List<String> list = new ArrayList<String>();
		for(int i=0; i<10000; ++i) {
     
			new Thread(()->{
     
				list.add(Thread.currentThread().getName());
			}).start();
		}
		
		System.out.println("list元素个数:" + list.size());
	}
}

对操作的容器进行同步监视,达到线程安全

package zy.thread;

import java.util.List;
import java.util.ArrayList;

public class safeList {
     
	public static void main(String[] args) {
     
		List<String> list = new ArrayList<String>();
		for(int i=0; i<10000; ++i) {
     
			new Thread(()->{
     
				synchronized(list) {
     
		        	list.add(Thread.currentThread().getName());
				}
			}).start();
		}
		
		System.out.println("list元素个数:" + list.size());
	}
}

  1. 并发时保证数据的正确性和高性能 ↩︎

  2. 同步 ↩︎

  3. 对this数据成员的访问都需要加锁 ↩︎

你可能感兴趣的:(java)