目录
一、线程不安全的三个示例
1、买票问题:该情况下很容易出现同一张票被重复购买,是线程不安全的。
package syn;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"我").start();
new Thread(station,"你们").start();
new Thread(station,"黄牛党").start();
}
}
class BuyTicket implements Runnable{
private int ticketNum = 10;
boolean flag = true;
@Override
public void run(){
while(flag){
buy();
}
}
private synchronized void buy(){
if (ticketNum <= 0){
flag=false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到第"+ticketNum--+"张");
}
}
以上代码运行后,发现第6张、第2张票被重复购买,但是现实生活中,是不能存在这样的问题的。
2、银行取钱:同一张卡,不同的两个人去取,一个想取50,一个想取100,大家看卡里有100,都是满足的,但是两个人同时去取,就会出现,明明两个人要取得钱加起来卡里是不够的,但是两个人却都取到了。
package syn;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"存款");
Drawing me = new Drawing(account,50,"小红");
Drawing you = new Drawing(account,100,"大黑");
me.start();
you.start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run(){
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money-drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
System.out.println(this.getName()+"手里的钱有:"+nowMoney);
}
}
运行以上代码,有如下结果:
3、不安全的集合:创建一个ArrayList,把每次运行的线程的名称加入到ArrayList里面,总共运行10000次。
package syn;
import java.util.ArrayList;
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
执行代码后,发现最后打印出的list大小是不足10000的,所以ArrayList是线程不安全的
二、synchronized关键字
synchronized关键字包括两种用法:synchronized方法和synchronized块,也就是同步方法与同步代码块。
1. synchronized方法:public synchronized void fun ( ) { }
控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法要获得调用该方法的对象的锁才鞥执行,否则就线程阻塞,方法一旦被执行,就应当独占锁,直到方法返回才释放锁资源,被阻塞的线程获得锁后继续执行,但是线程安全的同时意味着效率的降低。
2.synchronized块:synchronized ( Obj ) { }
Obj 称为同步监视器,可以是任意对象,但一般为共享资源作为同步监视器,同步方法中不用指定同步监视器, 因为其对象本身,也就是this就是同步监视器,或者是class(反射)也可以。
同步监视器执行过程:
(1)第一个线程访问,锁定同步监视器,并执行其中代码;
(2)第二个线程访问,发现同步监视器被锁,无法访问;
(3)第一个线程访问执行完毕,释放同步监视器;
(4)第二个线程访问,锁定同步监视器,并执行其中代码。
三、解决线程不安全
1. 针对买票问题,我们可以对买票这个公共方法加上synchronized关键字,让买票变成线程安全的操作,其中synchronized为同步方法,所以它锁的是this,如下:
package syn;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"我").start();
new Thread(station,"你们").start();
new Thread(station,"黄牛党").start();
}
}
class BuyTicket implements Runnable{
private int ticketNum = 10;
boolean flag = true;
@Override
public void run(){
while(flag){
buy();
}
}
private synchronized void buy(){
if (ticketNum <= 0){
flag=false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到第"+ticketNum--+"张");
}
}
2. 针对取钱问题,首先对run()方法加锁,但是发现还是线程不安全,可以发现run()锁的是this,也就是银行,但是我们要从操作的是账户,所以要使用同步代码块进行加锁,锁住的是银行的账户,修改如下:
package syn;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"存款");
Drawing me = new Drawing(account,50,"小红");
Drawing you = new Drawing(account,100,"大黑");
me.start();
you.start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run(){
synchronized (account){
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money-drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
System.out.println(this.getName()+"手里的钱有:"+nowMoney);
}
}
}
3.针对不安全的集合,一直在改变的是list,所以我们要对list对象进行加锁。
package syn;
import java.util.ArrayList;
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
四、总结
1.针对同步方法,锁的对象是this。
2.锁的对象是变化的量,即需要进行增删改的对象。