一、并发举例(线程不安全)
1、两个人同时操作一张银行卡,如何保证线程安全。
2、多个人同时购买一张火车票,谁能买到?
二、并发特点
1、同一个对象
2、被多个线程操作
3、同时操作
三、如何保证线程安全:线程同步(队列+锁)
1、使用队列的技术一个一个对资源进行操作,并通过一定算法决定谁先使用,例如一个班级只有一台电脑,那么排队一个一个操作。
2、对象的锁:如果当前资源被占用则锁上。
四、线程同步方法
1、synchronized
关键字包括两种用法:synchronized
方法和synchronized
块
2、synchronized
方法:若将一个大的方法声明为synchronized
将会大大影响效率,比如一个方法内两个属性A和B,A属性提供只读功能,B属性可修改需要锁住,那么如果一个方法都设置为synchronized
则A也无法访问则影响了效率。方法锁
public class Synchronized {
public static void main(String[] args) {
SafeWeb12306 safe = new SafeWeb12306();
new Thread(safe,"bull1").start();
new Thread(safe,"bull2").start();
new Thread(safe,"bull3").start();
}
}
class SafeWeb12306 implements Runnable{
private int ticketNum = 10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
test();
}
}
public synchronized void test() {
if(ticketNum<=0) {
flag = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+(ticketNum--));
}
}
3、synchronized
块可以只锁住一部分。下面的例子直接锁住账户而不是锁住方法,这里的方法就相当于取款机,锁住取款机是没用的,只能锁住账户。
synchronized
块的表示方法:
synchronized(object){}
public class Synchronized2 {
public static void main(String[] args) {
Account account = new Account(1000,"结婚礼金");
Drawing me = new Drawing(account,80,"自己");
Drawing you = new Drawing(account,90,"媳妇儿");
new Thread(me,"自己").start();
new Thread(you,"媳妇儿").start();
}
}
class Account{
int money;
String name;
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
class Drawing implements Runnable{
Account account ;
int drawMoney;
int pocketMoney=0;
@Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
test();
}
public Drawing(Account account,int drawMoney,String name) {
// TODO 自动生成的构造函数存根
this.account = account;
this.drawMoney = drawMoney;
}
public void test() {
//非常重要的一个判断,可以提高效率
if(account.money<=0) {
return;
}
synchronized (account) {
if(account.money-drawMoney<0) {
System.out.println(Thread.currentThread().getName()+"取钱余额不足");
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
account.money -= drawMoney;
pocketMoney += drawMoney;
System.out.println(Thread.currentThread().getName()+"账户里还剩-->"+account.money);
System.out.println(Thread.currentThread().getName()+"口袋里还有-->"+pocketMoney);
}
}
}
五、同步块
1、synchronized (obj){}
:其中obj称为同步监视器。
2、同步方法中无需指定同步监视器,因为同步方法的同步监视器是this
即该对象本身,或class即类的模子。
3、同步监视器的执行过程:
(1)第一个线程访问时,锁定同步监视器,执行其中代码。
(2)第二个线程访问,发现同步监视器被锁定,无法访问。
(3)第一个线程访问完毕,解锁同步监视器。
(4)第二个线程访问发现同步监视器未锁定,锁定并访问。
4、要锁住不变的对象,时刻在变的属性之类的东西不能锁
六、同步块模拟抢票
public class SynWeb12306 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"bull1").start();
new Thread(ticket,"bull2").start();
new Thread(ticket,"bull3").start();
new Thread(ticket,"bull4").start();
new Thread(ticket,"bull5").start();
}
}
class Ticket implements Runnable{
private int ticketNum = 30;
private boolean flag = true;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
while(flag) {
test();
}
}
public void test() {
synchronized (this) { //锁住对象本身,如果只锁定ticketNum不行,因为他一直在变,锁定不变的东西
if(ticketNum<=0) {
flag = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNum--);
}
}
}