线程同步
原理:
线程同步就是一个让多线程排队的机制。当多线程访问一个对象,并且还有某些线程想修改这个对象时,这时候就需要线程同步。让多个需要访问此对象的线程进入对象等待次池,形成队列,等待前面线程使用完毕,下一个线程再使用
三大不安全案例:
(1)买票:
package com.heima.syn;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"宏哥").start();
new Thread(buyTicket,"黄牛").start();
new Thread(buyTicket,"美女").start();
}
}
class BuyTicket implements Runnable{
//票数
private int ticket = 10;
//外部停止方式
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if (ticket<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到了第"+ ticket-- +"张票");
}
}
造成有人拿到0,-1的情况就是,在剩下最后一张票时,三个线程的人同时看到了这张票,所以三个人都拿了。
(2)银行取钱:
package com.heima.syn;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(200000,"泡妞基金");
Drawing Me = new Drawing("我",account,100000);
Drawing girlFriend = new Drawing("女朋友",account,150000);
Me.start();
girlFriend.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(String name, Account account, int drawingMoney) {
super(name);//父类Thread的name
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱操作
@Override
public void run() {
//判断钱够不够取
if (account.money - drawingMoney < 0){
System.out.println("取钱失败,余额不足");
return;
}
try {
Thread.sleep(100);//放大问题的发生性
} 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)线程不安全的集合
package com.heima.syn;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {//开启1000条线程,list的每个位置理论上存贮每一个不同的线程的name
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
这里理论上是要有1000,但是因为同时有不同的线程 给的 list 的同一个位置赋值,导致的覆盖,size变小
实现线程同步:synchronized
因为存在以上问题,所以为了数据在方法中被访问的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待。(synchronized出了其作用范围就会解锁)
同步方法:
(1)买票:
package com.heima.syn;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"宏哥").start();
new Thread(buyTicket,"黄牛").start();
new Thread(buyTicket,"美女").start();
}
}
class BuyTicket implements Runnable{
//票数
private int ticket = 10;
//外部停止方式
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法 锁的是this
private synchronized void buy() throws InterruptedException {
if (ticket<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到了第"+ ticket-- +"张票");
}
}
(2)要点:
(1)同步方法默认锁的是this
(2)不需要释放锁,出了作用范围自动释放
(3)如果给一个大的方法加锁,会影响效率,浪费资源,只有方法里面需要修改的内容才需要加锁(所以有了方法块)
同步块:
(1)银行取钱:
package com.heima.syn;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(200000,"泡妞基金");
Drawing Me = new Drawing("我",account,100000);
Drawing girlFriend = new Drawing("女朋友",account,150000);
Me.start();
girlFriend.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(String name, Account account, int drawingMoney) {
super(name);//父类Thread的name
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱操作
//把synchronized放在run,因为默认锁的是this,所以这种情况下会锁住Drawing,但是我们的目的是要锁住account
//所以我们这里要放的位置是正确的,但是要锁的对象不正确
@Override
public void run() {
//锁的对象是变化的量
synchronized (account){
//判断钱够不够取
if (account.money - drawingMoney < 0){
System.out.println("取钱失败,余额不足");
return;
}
try {
Thread.sleep(100);//放大问题的发生性
} 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);
}
}
}
(2)线程不安全的集合安全化:
package com.heima.syn;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {//开启1000条线程,线程里的run是给list的每个位置理论上存贮每一个不同的线程的name
new Thread(()->{ {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}