1. 线程同步机制
由于同一进程的做工和线程共享同一存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问中加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文加锁切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的相乘释放锁或导致优先级倒置,引起性能问题。
2. 线程并发不安全案例
- 不安全的买票(多个人操作同一张票)
package Demo05;
/*
1.不安全的买票,会出现:
多个人拿到同一张票
有人那到了负数票
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"anna").start();
new Thread(buyTicket,"kim").start();
new Thread(buyTicket,"jick").start();
}
}
//买票
class BuyTicket implements Runnable{
//票
private int ticketNum = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法,锁的是this
public void buy() throws InterruptedException {
//首先判断是否有票
if(ticketNum<=0){
flag = false;
return;
}
// Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--);
}
}
- 不安全的取款(俩人用同一个账号取款)
package Demo05;
import javax.print.DocFlavor;
/*
2.不安全的取款
两个人信息不同步,同一张银行卡取出超出现额的钱
也是多人操作同一个数据的问题
余额为负数
*/
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, 412725);
WithDraw anna = new WithDraw(account, 50, "anna");
WithDraw kim = new WithDraw(account, 100, "kim");
anna.start();
kim.start();
}
}
//账户
class Account{
int money;//余额
int number;//卡号
public Account(int money,int number) {
this.money = money;
this.number = number;
}
}
//取钱
class WithDraw extends Thread{
Account account;//账户
int drawMoney;//取钱金额
int nowMoney;//手里的钱
int remainderMoney;//余额
public WithDraw(Account account, int drawMoney,String name) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
//判断有没有钱
if(account.money<drawMoney){
System.out.println("余额不足");
return;
}
//sleep放大问题发生性
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果余额足够进行取钱,更新余额
account.money = account.money - drawMoney;
nowMoney = drawMoney+nowMoney;
System.out.println("卡余额为:"+account.money);
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱为:"+nowMoney);
}
}
- 不安全的集合(list)
package Demo05;
/*
3.不安全的集合
最后输出的集合要小于加入的
线程可能出现覆盖
*/
import sun.security.mscapi.CPublicKey;
import java.util.ArrayList;
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 300; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
3. 同步方法及同步块
- 解决以上线程不安全问题的机制是synchronized关键字,它包括两种方法:synchronized方法和synchronized块。
- synchronized方法控制对”对象的访问“,每个对象分配一把锁,每个synchronized方法都必须获得调用该方法的对象锁才能执行,否则会出现线程阻塞,方法一旦执行,就该独占该锁,直到该方法返回才释放,后面被阻塞的线程才能获得这个锁,继续执行。缺陷:若将一个大的方法申明为synchronized将会印象效率
- 此时synchronized块应运而生。同步块:synchronized(Obj){ }。Obj称之为同步监视器,可以是任何对象,推荐共享资源作为同步监视器;同步方法中无需指定发同步监视器,因为同步方法的同步监视器就是this,就是对象本身。
- 同步监视器的执行过程:第一线程访问,锁定同步监视器,执行代码-----》第二个线程访问,同步监视器被锁定,无法访问--------》第一个线程结束,解除锁定------》第二个线程访问,锁定
3.1 不安全的抢票
**只是在buy()添加synchronized
package Demo05;
/*
1.不安全的买票,会出现:
多个人拿到同一张票
有人那到了负数票
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"anna").start();
new Thread(buyTicket,"kim").start();
new Thread(buyTicket,"jick").start();
}
}
//买票
class BuyTicket implements Runnable{
//票
private int ticketNum = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法,锁的是this
public synchronized void buy() throws InterruptedException {
//首先判断是否有票
if(ticketNum<=0){
flag = false;
return;
}
// Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--);
}
}
3.2 不安全账户
采用synchronized块,锁住银行账户,因此把对象设置为银行账户,操作的内容丢进块里边
package Demo05;
import javax.print.DocFlavor;
/*
2.不安全的取款
两个人信息不同步,同一张银行卡取出超出现额的钱
也是多人操作同一个数据的问题
余额为负数
*/
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, 412725);
WithDraw anna = new WithDraw(account, 50, "anna");
WithDraw kim = new WithDraw(account, 100, "kim");
anna.start();
kim.start();
}
}
//账户
class Account{
int money;//余额
int number;//卡号
public Account(int money,int number) {
this.money = money;
this.number = number;
}
}
//取钱
class WithDraw extends Thread{
Account account;//账户
int drawMoney;//取钱金额
int nowMoney;//手里的钱
int remainderMoney;//余额
public WithDraw(Account account, int drawMoney,String name) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
//锁的对象就是变化的量,需要增删改查的对象
synchronized (account){
//判断有没有钱
if(account.money<drawMoney){
System.out.println("余额不足");
return;
}
//sleep放大问题发生性
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果余额足够进行取钱,更新余额
account.money = account.money - drawMoney;
nowMoney = drawMoney+nowMoney;
System.out.println("卡余额为:"+account.money);
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱为:"+nowMoney);
}
}
}
3.3 不安全的集合
依然采用synchronized块,对象是操作的结合对象
package Demo05;
/*
3.不安全的集合
最后输出的集合要小于加入的
线程可能出现覆盖
*/
import sun.security.mscapi.CPublicKey;
import java.util.ArrayList;
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 300; i++) {
synchronized (list){
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
3.4 CopyOnWriteArrayList
除了以上手动锁定之外,还可以用自带安全类操作同一数据
package Demo05;
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC安全类的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
strings.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(strings.size());
}
}