多线程详解第 4 讲:线程同步(重点)
import java.util.concurrent.ThreadLocalRandom;
//不安全的取钱
//两个人去银行取钱,账户
public clas
s UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing girlFriend = new Drawing(account,100,"girlFriend");
you.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(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断有没有钱
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不下");
return;
}
try {
Thread.sleep(1000);
} 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.kaung.syn;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
运行截图
我们发现集合中的元素并没有到达 10000,少了 2 个。这是因为多个线程抢占同一个资源,就会出现多个线程添加到了同一个位置。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
4、同步方法
同步方法
由于我们可以通过 private 关键字来保证数据对象只能被方法访问 , 所以我们只需要针对方法提出一套机制 , 这套机制就是 synchronized 关键字 , 它包括两种用法 : synchronized 方法 和 synchronized 块 .
同步方法 : public synchronized void method(int args) {}
synchronized 方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 , 方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行。
缺陷 : 若将一个大的方法申明为 synchronized 将会影响效率
同步方法弊端
5、同步块
同步块
同步块 :
synchronized (Obj ) { }
Obj 称之为 同步监视器
Obj 可以是任何对象 , 但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器 , 因为同步方法的同步监视器就是 this , 就是 这个对象本身 , 或者是 class [ 反射中讲解 ]
同步监视器的执行过程
第一个线程访问 , 锁定同步监视器 , 执行其中代码 .
第二个线程访问 , 发现同步监视器被锁定 , 无法访问
第一个线程访问完毕 , 解锁同步监视器
第二个线程访问, 发现同步监视器没有锁 , 然后锁定并访问
对三大不安全案例的改造
1、安全的买票
package com.kaung.syn;
//不安全的买票
public class UnsafeBuyTicket {
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 ticketNum = 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(ticketNum <= 0){
flag = false;
return ;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
}
}
运行截图
2、安全的取钱
package com.kaung.syn;
import java.util.concurrent.ThreadLocalRandom;
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing girlFriend = new Drawing(account,100,"girlFriend");
you.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(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
//synchronized 默认锁的是 this
@Override
public void run() {
//锁的对象是变化的量,需要增删改的对象
synchronized ( account){
//判断有没有钱
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不下");
return;
}
try {
Thread.sleep(1000);
} 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.kaung.syn;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
//休眠主线程,避免主线程执行过快,run 线程还未执行完毕就打印结果。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
运行截图
总结
锁的对象是变化的量,需要增删改的对象。锁住大家都操作的共同资源,避免共同资源在同一时间被多个对象访问。
6、CopyOnWriteArrayList
线程安全的集合
package com.kaung.syn;
import java.util.concurrent.CopyOnWriteArrayList;
//测试 JUC 安全类型的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
7、死锁
死锁
多个线程各自占有一些共享资源 , 并且互相等待其他线程占有的资源才能运行 , 而导致两个或者多个线程都在等待对方释放资源 , 都停止执行的情形 . 某一个同步块同时拥有 “ 两个以上对象的锁 ” 时 , 就可能会发生 “ 死锁 ” 的问题 。
死锁避免方法
产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生。
代码模拟死锁
package com.kuang.thread;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"灰姑娘");
Makeup g2 = new Makeup(1,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread {
//需要的资源只有一份,用 static 来保证只要一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String girlName; //使用化妆品的人
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) { //获得口红的锁
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror) {//一秒钟后想获得镜子
System.out.println(this.girlName + "获得镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick) {//一秒钟后想获得口红
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
程序运行卡死,互相僵持。
评论