《JAVA300集》多线程_并发基础案例-day21
程序员文章站
2022-07-10 17:06:24
...
目录
一、并发与线程同步
并发:同一个对象的多个线程同时操作。
并发有可能引发线程不安全的问题。
线程同步是一种等待机制,用于解决线程不安全问题。
当多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候就可以通过线程同步方法。多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
二、锁机制
为了保证数据在多线程访问中的正确性,在访问时加入了锁机制(synchronized),当一个线程获得对象的排它锁,就可以独占资源,其他线程必须等待,这个线程使用完成后释放锁。
使用锁机制有以下问题:(锁会导致性能下降)
使用方法: synchronized方法和synchronized块。
每个对象对应一把锁
难点:如何合适的用锁,使得锁不多也不少。同时兼顾安全和性能。
抢票案例
package Thread;
/*
*线程安全:在并发时保证数据的正确性,效率尽可能高
* synchronized
* 同步方法
* */
public class SynTest01 {
public static void main(String[] args) {
SafeWeb12306 web = new SafeWeb12306();
new Thread(web,"小黄").start();
new Thread(web,"小王").start();
new Thread(web,"小蒋").start();
}
}
class SafeWeb12306 implements Runnable{
private int ticketNums = 10;
private boolean flag = true;
@Override
public void run() {
while(flag){
// 模拟抢票延时
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
test();
}
}
// 锁方法里面跟对象相关的资源,使得只能同时被1个线程访问
public synchronized void test(){
if(ticketNums<=0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了"+ticketNums--+"号票");
}
}
运行结果:
取钱案例
// 账户
public class Account {
int money;
String name;
public Account(int money,String name){
this.money = money;
this.name = name;
}
}
错误示例-
//模拟取钱(错误示例)
public class SynTest02 {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚礼金");
SafeDrawing you = new SafeDrawing(account,80,"bad的你");
SafeDrawing wife = new SafeDrawing(account,90,"happy的小蒋");
you.start();
wife.start();
}
}
class SafeDrawing extends Thread{
Account account; //取钱的账户
int drawingMoney; //取出的钱
int packetTotal; //口袋里的钱
public SafeDrawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
test();
}
//锁这个方法没有用,这个方法类似于取款机,应该去锁账户,因为是账户的对象
public synchronized void test(){
if(account.money-drawingMoney<0){
return;
}
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName()+"--账户 "+account.money);
System.out.println(this.getName()+"--口袋的钱 "+packetTotal);
}
}
锁方法,是锁方法的对象,方法的默认对象是this
正确示例-
同步块- synchronized(obj){},obj称为同步监视器。 obj推荐使用共享资源。
package Thread;
//模拟取钱(正确示例)
public class SynBlockTest01 {
public static void main(String[] args) {
//账户
Account account = new Account(1000,"结婚礼金");
SynDrawing you = new SynDrawing(account,80,"bad的你");
SynDrawing wife = new SynDrawing(account,90,"happy的小蒋");
you.start();
wife.start();
}
}
class SynDrawing extends Thread{
Account account; //取钱的账户
int drawingMoney; //取出的钱
int packetTotal; //口袋里的钱
public SynDrawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
test();
}
//目标锁定账户
public void test(){
//这个if可以提升代码执行效率
if(account.money<=0){
return;
}
synchronized (account) {
if (account.money - drawingMoney < 0) {
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName() + "--账户 " + account.money);
System.out.println(this.getName() + "--口袋的钱 " + packetTotal);
}
}
}
运行结果:
同步块可以精准地锁定目标对象。(这个例子中是account)
抢票案例优化版本
/*
*线程安全:在并发时保证数据的正确性,效率尽可能高
* synchronized
* 同步块
* */
public class SynBlockTest03 {
public static void main(String[] args) {
SynWeb12306 web = new SynWeb12306();
new Thread(web,"小黄").start();
new Thread(web,"小王").start();
new Thread(web,"小蒋").start();
}
}
class SynWeb12306 implements Runnable{
private int ticketNums = 10;
private boolean flag = true;
@Override
public void run() {
while(flag){
// 模拟抢票延时
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
//test();
//falseTest();
goodTest();
}
}
public void test() {
//synchronized只能锁一个资源
synchronized (this) {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了" + ticketNums-- + "号票");
}
}
//漏锁了一个对象,导致线程依旧不安全
public void falseTest() {
synchronized ((Integer)ticketNums) {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了" + ticketNums-- + "号票");
}
}
//线程安全:尽可能锁定合理的范围,这样可以提升性能
public void goodTest() {
//双重检测,主要考虑的是临界值的问题
if (ticketNums <= 0) { //考虑的是没有票的情况
flag = false;
return;
}
synchronized (this) {
if (ticketNums <= 0) { //考虑的是最后1张票
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了" + ticketNums-- + "号票");
}
}
}
影院购票案例
package Thread;
import java.util.ArrayList;
import java.util.List;
public class HappyCinema2 {
public static void main(String[] args) {
//影院可用的位置
List<Integer> avaliable = new ArrayList<Integer>();
avaliable.add(1);
avaliable.add(2);
avaliable.add(3);
avaliable.add(4);
//顾客需要的位置
List<Integer> seats1 = new ArrayList<Integer>();
seats1.add(1);
seats1.add(2);
List<Integer> seats2 = new ArrayList<Integer>();
seats2.add(3);
seats2.add(5);
HCinema c = new HCinema(avaliable,"万达影院");
new Thread(new HappyCustomer(c,seats1),"小张和小机智").start();
new Thread(new HappyCustomer(c,seats2),"小赵").start();
System.out.println("欢迎光临!");
}
}
//顾客
class HappyCustomer implements Runnable{
HCinema hinema;
List<Integer> seats;
public HappyCustomer(HCinema hinema, List<Integer> seats) {
this.hinema = hinema;
this.seats = seats;
}
@Override
public void run() {
synchronized (hinema){
System.out.println(Thread.currentThread().getName()+" -想要购买的位置为:"+seats);
boolean flag = hinema.bookTickets(seats);
if(flag){
System.out.println("出票成功 "+ Thread.currentThread().getName()+" -购买的位置为:"+seats+"\n");
}else{
System.out.println("出票失败 "+ Thread.currentThread().getName()+" -购买失败,没位置了");
}
}
}
}
//影院
class HCinema{
List<Integer> avaliable; //可用的位置
String name;
public HCinema(List<Integer> avaliable, String name) {
this.avaliable = avaliable;
this.name = name;
}
//购票
public boolean bookTickets(List<Integer> seats){
System.out.println(this.name+"当前可用位置为: " + avaliable);
//两个容器的比较
List<Integer> copy = new ArrayList<Integer>();
copy.addAll(avaliable);
//相减
copy.removeAll(seats);
//判断是否成功
if(avaliable.size()-copy.size()!=seats.size()){
return false;
}else{
avaliable = copy;
return true;
}
}
}
运行结果:
抢票案例-面向对象思想
public class Happy12306 {
public static void main(String[] args) {
Web123066 w = new Web123066(2,"铁路局");
new Passenger(w,"小张",2).start();
new Passenger(w,"小机智",2).start();
}
}
//乘客 (继承,作为Thread的子代理)
class Passenger extends Thread{
int seats;
public Passenger(Runnable target, String name, int seats) {
super(target,name);
this.seats = seats;
}
}
//火车票网
class Web123066 implements Runnable{
int avaliable; //可用的位置
String name;
public Web123066(int avaliable, String name) {
this.avaliable = avaliable;
this.name = name;
}
@Override
public void run() {
Passenger p = (Passenger)Thread.currentThread(); //把当前线程强转为当前子类
boolean flag = this.bookTickets(p.seats);
if(flag){
System.out.println("出票成功 "+ Thread.currentThread().getName()+" -购买位置数量为:"+p.seats);
}else{
System.out.println("出票失败 "+ Thread.currentThread().getName()+" -购买失败,没位置了");
}
}
//购票
public synchronized boolean bookTickets(int seats){
//购票延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+"当前可用位置为: " + avaliable);
if(seats>avaliable){
return false;
}
avaliable -=seats;
return true;
}
}
运行结果:
Passenger p = (Passenger)Thread.currentThread(); //把当前线程强转为当前子类
三、并发容器
java中有自带的并发容器,名为util.concurrent
import java.util.concurrent.*
import java.util.concurrent.CopyOnWriteArrayList;
/*
* 并发安全操作容器
* */
public class SynContainer {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for(int i=0;i<1000;i++){
new Thread(()-> list.add(Thread.currentThread().getName())).start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
上一篇: HTTP(三):HTTP相关协议
下一篇: 如何设置微信实时到账?