概念
关于线程
- 线程就是独立的执行路径
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
- 多个线程对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;(尽管我们可以设置线程的优先级,但是优先级高的线程也只是权重大,并不意味着优先级高的就先执行,线程的执行顺序完全取决于cpu的调度执行)
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
线程同步
- 线程同步,个人理解是对多个线程进行资源同步
- 背景:由于一个进程的多个线程共享同一块存储空间,在带来便利的同时,也带来了访问冲突的问题
- 解决思路:为了保证数据在方法中被访问的正确性,在访问时加入锁
synchronized
机制,当一个线程获得对象的排他锁,独占资源,其他线程等待,该线程使用后释放锁
- 存在的问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致上下文切换和调度延迟,引发性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引发性能问题
Synchronized关键字的两种用法
- 理解:由于我们可以通过
private
关键字来保证数据对象只能被访问,所以我们只需要针对方法提供一套机制,这套机制就是sychronized
关键字,它包括两种用法synchronized方法和synchronized块
synchronized
方法
- 同步方法
public synchronized void method(int args){}
syschronized
方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized
方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞
- 缺陷:若将一个大的方法申名为
synchronized
将会影响效率
- ⚠️同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是
class
[反射]
synchronized
块
- 同步块
synchronized(Obj){}
- Obj称之为同步监视器(个人理解同步监视器就是多个线程争夺的资源对象);Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
线程同步示例
- 背景:夫妻二人有一个账户,名为结婚基金,账户当前余额为100万。今妻子欲取100万,你要取50万,保证线程安全!
public class UnsafeDrawing {
public static void main(String[] args) {
//账户总共100
Account account=new Account(100,"结婚基金");
Persion wife = new Persion(account,100,"妻子"); //妻子要取100
Persion man = new Persion(account,50,"你"); //你要取50
//两个线程的执行顺序取决于cpu的调度
man.start();
wife.start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//操作人
class Persion extends Thread{
Account account; //账户
int drawingMoney; //取出
int nowMoney; //手里现在的钱
public Persion(Account acount, int drawingMoney, String name){
super(name);
this.account = acount;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//锁的对象就是变化的量,需要增删改的对象
synchronized (account) {
if(account.money-drawingMoney<0){ //账户余额不足
System.out.println( "当前账户余额不足,"+Thread.currentThread().getName()+"取不了?");
return;
}
//sleep可以放大线程不安全问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
account.money=account.money - drawingMoney;
System.out.println(account.name+ "当前余额为:"+account.money);
//你手里现在的钱
nowMoney=nowMoney+drawingMoney;
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里现在的钱:"+nowMoney);
}
}
}