用synchronized关键字目标一定要对。
上次模拟抢票的案例出现了负数问题,再在分析一下:假如就剩下一张票了(临界值),三个来抢票,假如B线程来了ticketNums=1,不少于等于0,然后延时200毫秒,此时还没有修改ticketNums的数据,还是等于1,然后A进来了,ticketNums的值等于1,也延时毫秒,然后C也进来了,延时200 毫秒,然后延时一过B就才把数据1拿走了,然后就没成了0,A也好了只能拿0,C就只能拿-1。
还有出现了数据相同的情况:每一线程都有自己的工作空间,它们和主存打交道的,假如主存是个10,A线程先把10拷贝过来,还没来的急修改,然后B线程也把这个10拷贝过来了,所以就出现了两10。
案例:同步方法
package com.cb.thread.day03;
/*
* 线程安全:在并发时保证数据的正确性、效率尽可能高
* synchronized
* 1.同步方法
* 2.同步块
*/
public class SynTest01 {
public static void main(String[] args) {
//一份资源
SafeWeb12306 web = new SafeWeb12306();
System.out.println(Thread.currentThread().getName());
//三个代理
new Thread(web,"码畜").start(); //加上名字,区分线程
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();
}
}
class SafeWeb12306 implements Runnable{
private int ticketNums = 10; //99张票
private boolean flag = true;
@Override
public void run() {
while(flag){
test();
}
}
public synchronized void test(){
if(ticketNums<=0){
flag= false;
return;
}
try {
Thread.sleep(200);//模拟延时
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread().getName()谁运行run就是代表谁
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
运行结果:
上次还有个案例,账户里面有100万,两人同时取钱,最后账户里面的钱出现了负数,什么原因了,当A取80万,然后在判断账户里面的钱是否有80万,然后判断够取80万,然后正取的时候,B取90万也判断,注意这时数据还来的急修改还是100万,一判断也够也进来了,这时A取出了80万还剩20万,然后B也取了90万,变成了-70万
接下来我们来改一下上次的代码
package com.cb.thread.day03;
public class SynTest02 {
public static void main(String[] args) {
//账户
Account account = new Account(100, "结婚礼金");
SafeDrawing you = new SafeDrawing(account, 70, "男方");
SafeDrawing wife = new SafeDrawing(account, 80, "女方");
you.start();
wife.start();
}
}
class SafeDrawing extends Thread{
Account account; //取钱的账户
int drawingMoney;//取的钱数
int packetTotal;//口袋里面的钱
public SafeDrawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
test();
}
//目标锁定account
public void test(){
//提高性能,如果账户没钱还来访问等待性能很低。
if (account.money<=0) {
return;
}
//同步块
synchronized(account){
if (account.money-drawingMoney<0) {
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);
}
}
}
class Account{
int money;//金额
String name; //名称
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
运行结果:
从结果上分析只有一个人成功的取到了钱,另外一人没有取到钱,因为账户里面的钱不够了。还有就是如果是锁方法就是this,在这里要锁的是账户,所以就要用到方法块,当这个A先进去了,先账户里面的钱够不够取,如果够了话,就进去取,这时B进去了访问锁,必须等A把钱取出来,B才能进去取钱。
上次还有一个容器数据不正确,往里面存10000,结果不够数,下面就来改正它
package com.cb.thread.day03;
import java.util.ArrayList;
import java.util.List;
public class SynTest03 implements Runnable{
public static List<String> list = new ArrayList<String>();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
SynTest03 u03 =new SynTest03();
Thread t = new Thread(u03);
t.start();
}
Thread.sleep(10000);
System.out.println(list.size());
}
@Override
public void run() {
synchronized(list){
list.add(Thread.currentThread().getName());
}
}
}
运行结果:每存进去一个,出来后在存进去一下个,就不会出现覆盖的情况
将第一个案例用同步块
package com.cb.thread.day03;
public class SynTest04 {
public static void main(String[] args) {
//一份资源
SafeWeb123061 web = new SafeWeb123061();
System.out.println(Thread.currentThread().getName());
//三个代理
new Thread(web,"码畜").start(); //加上名字,区分线程
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();
}
}
class SafeWeb123061 implements Runnable{
private int ticketNums = 10; //99张票
private boolean flag = true;
@Override
public void run() {
while(flag){
test5();
}
}
//线程同步方法
public synchronized void test1(){
if(ticketNums<=0){
flag= false;
return;
}
try {
Thread.sleep(200);//模拟延时
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread().getName()谁运行run就是代表谁
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
//同步块 范围太大 ,性能低下
public void test2(){
synchronized(this){
if(ticketNums<=0){
flag= false;
return;
}
try {
Thread.sleep(200);//模拟延时
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread().getName()谁运行run就是代表谁
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程不安全 ticketNums对象在变,要锁一个地址不变的
public void test3(){
synchronized((Integer)ticketNums){
if(ticketNums<=0){
flag= false;
return;
}
try {
Thread.sleep(200);//模拟延时
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread().getName()谁运行run就是代表谁
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
// 线程不安全范围太小锁不住
public void test4(){
synchronized(this){
if(ticketNums<=0){
flag= false;
return;
}
}
try {
Thread.sleep(200);//模拟延时
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread().getName()谁运行run就是代表谁
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
//
public void test5(){
if(ticketNums<=0){ //考虑的是没有票的情况
flag= false;
return;
}
synchronized(this){
if(ticketNums<=0){ //考虑最后的1张票
flag= false;
return;
}
try {
Thread.sleep(200);//模拟延时
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread().getName()谁运行run就是代表谁
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
}
分析test5:如果不在一进去的时候判断的话,那么当没有票了,B进去了操作,然后其他线程还要等B线程出来,然后再进去,其他线程也一样在再等着,然后已经票还要进行这一系列操作。如果加了话,没有票就直接可以出来。这叫多重检查,后面多用块,少用方法,这样性能高。