同步方法:只能锁类本身
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法synchronzed方法和synchronized块
同步方法:public synchronzed void method(int args){}
synchronzed方法控制对”对象“的访问,每个对象对应一把锁,每个synchronzed方法都必须获得调用该方法对象的锁才能执行,否则线程会阻塞方法,方法一旦执行,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺点:若将一个大的方法申明为synchronzed将会影响效率
同步块:当一个大方法中,只有一小部代码需要用同步加锁时,我们可以使用同步块,如:当方法中包含的代码有一部代码只是让你去读取的代码,另一部份代码是用来修改东西的,这只是有修改东西的那个代码需要同步,如果不同步当多人同时修改一个内容时,就会出现问题,那这个时候,我们只需要把同步代码块写到对应代码只即可。
同步块:synchronized((Obj){}:可以锁任何对象
Obj称之为同步监视器:Obj的对象一定是需要增删改的对象
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器,也就是需要修改的那个对象作为监视器
同步方法中无需指定同步监视器,因为方法的同步监视器就是this,就是这个对象本身,或者class
同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无方访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器没有锁,然后锁定并访问
//安全的买票:使用同步方法
//使用同步方法,让他安全
public class Main{
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 ticketNums=10;
//创建一个循环停止的属性,外部停止方式
boolean flag=true;
public void run(){
//买票
while(flag){
try {//下面抛出异常上面就要处理
buy();
} catch(Exception e) {
System.out.println("异常");
}
}
}
//买票的方法 在买票的方法中加入同步方法关键字,让他变成同步方法即可
private synchronized void buy()throws InterruptedException{
//判断是否有票
if (ticketNums<=0){
flag=false;
return;
}
//模拟延时
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
//安全的取钱:使用同步块
//两个人去银行取钱,账户
//sleep():可以放大问题的发生性
public class Main {
public static void main(String[] args) {
//账户
Account acc=new Account(1000,"理财基金");
//创建银行对象,构造器中,传入账户及取钱金额及名字
Drawing you=new Drawing(acc,50,"我");
Drawing gir=new Drawing(acc,100,"他");
you.start();
gir.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(){}
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);//获取线程的名字,因为继承了Thread是可以用父类方法来获取的
this.account=account;
this.drawingMoney=drawingMoney;
}
//取钱:重写run,注意:如果我们直接用synchronized 锁run方法,那依旧会有负数,因为run的this类是银行类,但是我们增删改查的操作银行是针对账户的,所以去锁银行,是锁不到的,所以这个方法,我们不能用同步方法去写,而是用同步块
//synchronized默认锁的对象是:this,方法也就是他本身
public void run(){
synchronized(account){//使用同步块,为什么account,可以当做监视器使用呢,我们要锁的是会变化的量,需要增删改的对象
//判断有没有钱
if (account.money-drawingMoney<0){//如果账户里的钱减去要取的钱小于0,就是不够
System.out.println(Thread.currentThread().getName()+":你的余额不足");
return;
}
try {
//当如果账户有钱,我们进来后,都停一秒钟,这样就会让账户出现负数 //为什么出现问题呢,因为这两个账户的内存都不一致的,当他们看到100时,是用时看到的,所以以为都能取完,这就导致了线程的不安全,只有让一个线程先执行完,再让其他线程进行执行,这样才会安全
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("有异常");
}
//卡内余额=余额-你取的钱
account.money=account.money-drawingMoney;
//你手里的线
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"的余额:"+account.money);
//Thread.currentThread().getName()==this.getName),也是获取线程名称
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
}
//线程不安全的集合
import java.util.ArrayList;
import java.util.list;
public class Main {
public static void main(String[] args) {
Listlist=new ArrayList();
for (int i=0;i<1000 ;i++ ){
new Thread(()->{//创建1000个线程
//使用同步块锁变化的量
synchronized(list){
list.add(Thread.currentThread().getName());//把线程的名字添加到集合中
}
}).start();
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("no");
}
System.out.println(list.sixe());//输出一下集合的大小,但是输出正常是不够,因为有时候会出来两个元素放在一个位置然后会覆盖掉
}
}