本帖最后由 黑白客 于 2021-3-8 16:08 编辑
多线程同步,通信知识详解
线程同步及同步块
同步块
lock锁
程通信问题
生产者和消费者模式(这个模式并不是23种设计模式中的)
管程法
信号灯法
线程池
线程同步及同步块
结合上篇发布的文章,线程同步就算是讲完了。因为篇幅过长,所以这块被迫分成了两块
-
同步方法
由于private关键字来保证数据对象只能被方法所访问。所以我们只需要针对方法提出一套机制
synchronized关键字,包括synchronize方法和synchronized块
public synchronized void method(int args){}
对于方法内的只读代码,是不需要锁的。修改内容才需要锁,锁太多了,会影响效率
package com.wang.syn1;
/**
* @author: 王海新
* @Date: 2021/2/28 16:40
* @Description: 修改为安全的买票,
* 添加synchronized 锁 在buy方法上。就等于在buy的对象上设置了锁。
* 因为buy里面有延时,所以会让先进来的线程一直调用buy。如果票少,其它的线程就没有机会
*可以将延时放到run方法中,这样其它在buy执行完
*/
public class SafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"小明").start();
new Thread(buyTicket,"小红").start();
new Thread(buyTicket,"小芳").start();
new Thread(buyTicket,"小蓝").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flage = true;//外部停止方法
@Override
public void run() {
//买票
while (flage) {
try {
Thread.sleep(1000);
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//添加synchronized 锁
private synchronized void buy() throws InterruptedException {
//判断是否有票
if (ticketNums <= 0) {
flage = false;
return;
}
//模拟延时
//Thread.sleep(1000);
//买票
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums -- +"张");
}
}
- 修改为安全取钱(这里的我也理解不了了,求大佬指导吧)
package com.wang.syn1;
/**
* @author: 王海新
* @Date: 2021/2/28 16:59
* @Description: 不安全的取钱,两个人去银行取钱
*
*/
public class SafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "grilFriend");
you.start();
girlFriend.start();
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 17:05 2021/2/28
* @Version: 1.0.0
* @Description: 账户
*/
class Account{
String name;
int money;
public Account( int money,String name) {
this.name = name;
this.money = money;
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 17:05 2021/2/28
* @Version: 1.0.0
* @Description: 银行 模拟取款
*/
class Drawing extends Thread{
//账户
Account account;
//取了多少钱
int DrawingMoney;
//现在手里有多少钱
int nowMoney;
//构造器,将变量初始化
Drawing(Account account,int DrawingMoney,String name){
super(name);
this.account = account;
this.DrawingMoney = DrawingMoney;
}
@Override
public synchronized void run() {
if (account.money - DrawingMoney < 0) {//判断账户中的钱是否够取
System.out.println(this.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);
//Thread.currentThread()就是返回一个Thread对象,所以我们可以用this
System.out.println(this.getName()+ "手里的钱为" + nowMoney);
}
}
在run上加了锁,但是依然没有锁住。还是出现了负数,这里求解答?
同步块
-
同步块:synchronized(Obj){}
-
Obj称为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中,无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class(反射中讲解)
-
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
这里利用同步块,解决银行取钱问题
package com.wang.syn1;
/**
* @author: 王海新
* @Date: 2021/2/28 16:59
* @Description: 安全的取钱,两个人去银行取钱
*
*/
public class SafeBank {
public static void main(String[] args) {
Account account = new Account(1000,"结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "grilFriend");
you.start();
girlFriend.start();
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 17:05 2021/2/28
* @Version: 1.0.0
* @Description: 账户
*/
class Account{
String name;
int money;
public Account( int money,String name) {
this.name = name;
this.money = money;
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 17:05 2021/2/28
* @Version: 1.0.0
* @Description: 银行 模拟取款
*/
class Drawing extends Thread{
//账户
Account account;
//取了多少钱
int DrawingMoney;
//现在手里有多少钱
int nowMoney;
//构造器,将变量初始化
Drawing(Account account,int DrawingMoney,String name){
super(name);
this.account = account;
this.DrawingMoney = DrawingMoney;
}
@Override
public void run() {
synchronized(account){
if (account.money - DrawingMoney < 0) {//判断账户中的钱是否够取
System.out.println(this.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);
//Thread.currentThread()就是返回一个Thread对象,所以我们可以用this
System.out.println(this.getName()+ "手里的钱为" + nowMoney);
}
}
}
课程上说。因为是账户执行的增删改,所以同步监视器要是account对象。(但是我还是不太动,有大佬可以详解一下吗?如果锁的是银行,那一直有一个人在里面取钱,岂不是也不会出错。当然结果是出错了。但是我真的不理解原因啊)
-
总结,锁的对象是要增删改的对象。
-
根据这个总结,我们将集合也该为安全的
package com.wang.syn1;
import java.util.ArrayList;
import java.util.List;
/**
* @author: 王海新
* @Date: 2021/3/3 09:22
* @Description: 线程安全的集合
* 造成数据不是10000个的原因有两个
*因循环已经跑完,程序结束,线程还没有将数据加入的情况。
* 两个线程同时操作同一个位置,造成的数据覆盖。
*/
public class SafeList {
public static void main(String[] args) {
//创建一个集合
List<String> array = new ArrayList<String>();
//利用for循环,创建1000个线程向集合里面添加数据
for (int i = 0; i < 10000; i++) {
new Thread( () -> {
synchronized(array){
array.add(Thread.currentThread().getName());
}
}).start();
}
try {//利用阻塞。去除掉因循环已经跑完,程序结束,线程还没有将数据加入的情况。导致数据不一致的原因
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(array.size());
}
}
package com.wang.syn1;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author: 王海新
* @Date: 2021/3/4 16:11
* @Description: 测试JUC安全类型的集合
*/
public class TestGUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread( () -> {
strings.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(strings.size());
}
}
lock锁
package com.wang.syn2;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: 王海新
* @Date: 2021/3/5 10:21
* @Description: lock 锁
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true){
//一般在加锁的代码块中,如果有异常抛出,就放到try{}finally{}中 在finally中将锁释放
try{
//显示的加锁
reentrantLock.lock();
if (ticketNumber > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNumber --);
}else {
break;
}
}
finally {
//显示的释放锁
reentrantLock.unlock();
}
}
}
}
程通信问题
生产者和消费者模式(这个模式并不是23种设计模式中的)
-
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
-
在这个问题中,synchronized可以阻止并发更新同一个资源
但是不能用来实现不同线程之间的消息传递
-
java提供了几个方法解决线程之间的通信问题
一下均是Object类的方法。都只能在同步方法,或者在同步代码块中使用,否者会抛出异常
- wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
- wait(long timeout) 指定等待的毫秒数
- notify()唤醒一个处于等待状态的线程
- notifyAll()唤醒同一个对象上,所有调用wait方法的线程,优先级别高的线程,优先调用
-
解决方法1 生产者消费者模式--管程法
添加一个缓冲区,生产者生产的商品放到 这里。消费者从这里消费。从而实现协作
-
解决方法2 并发协作模式,信号灯法。可以用一个标志位来控制 如true放行 false等待
管程法
package com.wang.syn2;
/**
* @author: 王海新
* @Date: 2021/3/6 11:36
* @Description: 线程协作,管程法
* 生产者 消费者 产品 缓冲区
*/
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor( container) .start();
new Consumer(container).start();
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:37 2021/3/6
* @Version: 1.0.0
* @Description: 生产者
*/
class Productor extends Thread {
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
//生产
public void run(){
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+ i + "只鸡。");
}
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:38 2021/3/6
* @Version: 1.0.0
* @Description: 消费者
*/
class Consumer extends Thread {
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了--》" + container.pop().id+ "只鸡");
}
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:38 2021/3/6
* @Version: 1.0.0
* @Description: 产品
*/
class Chicken {
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:39 2021/3/6
* @Version: 1.0.0
* @Description: 缓冲区
*/
class SynContainer{
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,就等待消费者消费
if (count == chickens.length){
//通知消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
chickens[count] = chicken;
count ++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费商品
public synchronized Chicken pop(){
//判断能否消费
if (count == 0) {
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count --;
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
这是草图,我自己哪里理清思路的时候随手画的。
信号灯法
package com.wang.syn2;
/**
* @author: 王海新
* @Date: 2021/3/6 11:36
* @Description: 线程协作,信号灯法
* 生产者 消费者 烤鸡店
*/
public class TestPC2 {
public static void main(String[] args) {
Chicken2 chicken2 = new Chicken2();
new Productor2(chicken2) .start();
new Consumer2(chicken2).start();
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:37 2021/3/6
* @Version: 1.0.0
* @Description: 生产者
*/
class Productor2 extends Thread {
Chicken2 chicken;
public Productor2(Chicken2 chicken2 ){
this.chicken = chicken2;
}
//生产
public void run(){
for (int i = 0; i < 10; i++) {
chicken.pro(i);
}
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:38 2021/3/6
* @Version: 1.0.0
* @Description: 消费者
*/
class Consumer2 extends Thread {
Chicken2 chicken;
public Consumer2(Chicken2 chicken2 ){
this.chicken = chicken2;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
chicken.con();
}
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 11:38 2021/3/6
* @Version: 1.0.0
* @Description: 烤鸡店
*/
class Chicken2 {
int id;//产品编号
//生产完成,通知消费 false
//消费完成,通知生产 true
boolean flag = true;
//生产
public synchronized void pro(int id){
//判断是否生产
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产了
System.out.println("生产了第"+ id + "只鸡");
//通知消费
this.notifyAll();
this.id = id;
this.flag = !this.flag;
}
//消费
public synchronized void con(){
//判断是否消费
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费
this.id = id;
System.out.println("消费了"+ this.id + "只鸡。");
this.flag = !this.flag;
this.notifyAll();
}
}
利用一个标志位,达到两个线程协同的效果
线程池
- 经常创建和销毁特别大的资源,比如并发情况下的线程。对性能影响很大。
- 提前创建好多个线程,放入线程池,使用时直接获取。使用完放回池子中。避免频繁的创建和销毁,实现重复利用。
- 优点
- 提高响应速度(减少创建的时间)
- 降低资源消耗
- 便于线程管理
- corePoolSize:核心池大小
- maximumPoolSize :最大线程数
- KeepAliveTime :线程没有任务后,最多保存的少时间后销毁。
package com.wang.syn2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: 王海新
* @Date: 2021/3/8 15:51
* @Description: 线程池
*/
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭
service.shutdown();
}
}
/*********************************************************************************************************************
* @Author: 王海新
* @Date: 15:51 2021/3/8
* @Version: 1.0.0
* @Description:
*/
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
- OK,到这里,多线程的学习文章就发完了。 完结散花
|