Java------多线程_并发_同步_队列与锁(八)
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程,进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
锁保证线程安全,实现线程同步。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。
为了保证数据在方法中被访问时的正确性,在访问时加入“锁机制(synchronized)”,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在以下问题:
1.一个线程持有锁会导致其他所有需要此锁的线程挂起。
2.在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
线程同步实现:
已经通过private关键字保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,就是synchronized关键字。有两种用法,synchronized方法和synchronized块。
synchronized方法控制对“成员变量|类变量”对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,知道从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
锁具体的对象
缺点:若果将一个大的方法声明为synchronized将会大大影响效率。
难点在于安全性与性能兼顾。
**案例一:**模拟抢票,此时synchronized写在方法上,成员方法锁的是this对象。
/**
* 线程安全:保证数据的安全性,同时效率尽可能高
* 第一个例子:抢票
* synchronized
*/
public class ThreadSyn1 {
public static void main(String[] args) {
UnSafe1 threadTest01 = new UnSafe1();
//多个代理,加入名称区分
new Thread(threadTest01,"thread01").start();
new Thread(threadTest01,"thread02").start();
new Thread(threadTest01,"thread03").start();
}
}
class UnSafe1 implements Runnable{
private int tickNums = 10;
private Boolean flag = true;
@Override
public void run(){
while (flag){
test();
}
}
//线程同步,synchronized加到方法上
//成员方法锁的this对象的资源
public synchronized void test(){
if (tickNums<0){
flag = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+tickNums--);
}
}
案例二:取钱案例目标不对锁定失败。这里不是锁this,而是应该锁account。
问题出在:account.money-drawingMoney;就是账户的余额减去取的钱。
问题是多个人取到一致账户余额。此时在方法上锁还是会失败。
应该锁account。
public class ThreadSyn021 {
public static void main(String[] args) {
Account1 account = new Account1(100,"家庭基金");
Draw1 draw = new Draw1(account,70,"男人");
Draw1 draw2 = new Draw1(account,70,"女人");
draw.start();
draw2.start();
}
}
//账户 Account1
class Account1{
public int money;
public String name;
public Account1(int money, String name) {
this.money = money;
this.name = name;
}
}
class Draw1 extends Thread{
Account1 account; //账户
int drawingMoney;//取的钱
int packetTotal;//口袋里的钱
public Draw1(Account1 account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
this.test();
}
public synchronized void test(){
//加上判断,当银行余额-取钱小于0时,不让取
if (account.money-drawingMoney<0){
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName()+"---->账户余额"+account.money);
System.out.println(this.getName()+"---->口袋余额"+packetTotal);
}
}
同步块:synchronized(obj){},obj称之为同步监视器
1.obj可以是任何对象,但是推荐使用共享资源作为同步监视器
2.同步方法中无需执行同步监视器,因为同步方法的同步监视器,是this,即该对象本身,或Class,即类的模子。
同步监视器的执行过程:
1.第一个线程访问,锁定同步监视器,执行其中代码。
2.第二个线程访问,发现同步监视器被锁定,无法访问。
3.第一个线程访问完毕,解锁同步监视器。
4.第二个线程访问,发现同步监视器未锁,锁定并访问。
**案例三:**还是取钱例子,添加synchronized关键字,锁account对象
/**
* synchronized
* 1.同步块,目标更明确
*/
public class ThreadSyn022 {
public static void main(String[] args) {
Account2 account = new Account2(100,"家庭基金");
Draw2 draw = new Draw2(account,70,"男人");
Draw2 draw2 = new Draw2(account,70,"女人");
draw.start();
draw2.start();
}
}
//账户 Account1
class Account2{
public int money;
public String name;
public Account2(int money, String name) {
this.money = money;
this.name = name;
}
}
class Draw2 extends Thread{
Account2 account; //账户
int drawingMoney;//取的钱
int packetTotal;//口袋里的钱
public Draw2(Account2 account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
this.test();
}
public synchronized void test(){
//当小于0时,不用直接再去试图获取锁,直接返回,优化
if (account.money<0){
return;
}
synchronized (account){
//加上判断,当银行余额-取钱小于0时,不让取
if (account.money-drawingMoney<0){
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName()+"---->账户余额"+account.money);
System.out.println(this.getName()+"---->口袋余额"+packetTotal);
}
}
}
案例四:容器添加,加synchronized
import java.util.ArrayList;
/**
* 容器不安全例子
* 加synchronized,使得安全
*/
public class ThreadSyn031 {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> arrayList = new ArrayList<String>();
for (int i = 0 ; i<100;i++){
new Thread(()->{
//synchronized块
synchronized (arrayList){
arrayList.add(Thread.currentThread().getName());
}
}).start();
}
//打印太快,如果还没添加就打印,加个延时
Thread.sleep(10000);
System.out.println(arrayList.size());
}
}