JAVA基础-多线程-下

发布于 2024-07-29  6093 次阅读


线程同步机制

当多个线程需要同时访问和修改同一个对象时,需要创建一个线程池,形成等待队列,逐个依次访问,来避免并发问题

在访问时加入sychronize(锁机制),可以使一个线程访问此对象时获得排他锁,拒绝其他线程对此对象的访问。

线程不安全

下为一个线程不安全的例子

public class testUnsafe {
public static void main(String[] args) throws InterruptedException {
you u = new you();
u.HP = 100;// 初始生命值
attack atk = new attack(u,"ctf");//来自ctf的攻击
Thread t = new Thread(atk);
t.start();
attack atk1 = new attack(u,"awd");//来自awd的攻击
Thread t1 = new Thread(atk1);
t1.start();
}
}
class you {
public int HP;//玩家生命值
}
class attack implements Runnable {
you u;
String attacker;
public attack(you u,String atk){ //设置被攻击者和攻击者
this.u = u;
this.attacker = atk;
}
@Override
public void run() {
if(u.HP <= 60){
System.out.println("You die!");
System.exit(0);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}//模拟延迟来放大问题
System.out.printf("%s攻击了你。\n",attacker);
u.HP-=60;
System.out.printf("还剩%d点HP\n",u.HP);
}
}

alt text
在1s的延迟结束后,两个线程同时访问对象,访问到的都是HP为100的you对象,因此绕过了判断死亡的限制,线程不安全。

sychronize

sychronize:同步锁,在直接修饰方法的时候默认锁的是this,在修饰代码块时,锁括号内的对象:sychronize(obj){code}
在锁定对象时:被修饰方法或代码块内的所有代码线程都对锁定对象,都有队列访问机制,即一个线程访问对象时对象被上锁,被sychronize修饰的线程拿到此锁并访问,其他线程不得同时访问。

上面代码加上同步锁:

public class testUnsafe {
public static void main(String[] args) throws InterruptedException {
you u = new you();
u.HP = 100;// 初始生命值
attack atk = new attack(u,"ctf");//来自ctf的攻击
Thread t = new Thread(atk);
t.start();
attack atk1 = new attack(u,"awd");//来自awd的攻击
Thread t1 = new Thread(atk1);
t1.start();
}
}
class you {
public int HP;//玩家生命值
}
class attack implements Runnable {
you u;
String attacker;
public attack(you u,String atk){ //设置被攻击者和攻击者
this.u = u;
this.attacker = atk;
}
@Override
public void run() {
synchronized (u){ //为对象u上锁
if(u.HP <= 60){
System.out.println("You die!");
System.exit(0);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}//模拟延迟来放大问题
System.out.printf("%s攻击了你。\n",attacker);
u.HP-=60;
System.out.printf("还剩%d点HP\n",u.HP);
}
}
}

alt text

线程死锁

当两个进程互相等待对方释放自己需要的资源,而自己掌握着对方需要的资源锁时,会导致永远互相等待,即进程死锁
例子:

public class DeadLock {
public static void main(String a[]){
Thread t1 = new Thread(new change(0,"小明"));
Thread t2 = new Thread(new change(1,"小红"));
t1.start();
t2.start();
}
}
class incar{
//在车里
}
class outcar{
//在车外
}
class change implements Runnable{
static final incar incar = new incar();
static final outcar outcar = new outcar();
int choice ;
String name;
public change(int ch,String na){
this.choice = ch;
this.name = na;
}
public void changepos(){
if(choice == 1){
synchronized (incar){
System.out.printf("现在%s在车上,想下车\n",name);
synchronized (outcar){
System.out.printf("现在%s成功下车\n",name);
}
}
}
else {
synchronized (outcar){
System.out.printf("现在%s在车下,想上车\n",name);
synchronized (incar){
System.out.printf("现在%s成功上车\n",name);
}
}
}
}
@Override
public void run() {
changepos();
}
}

alt text
解释:如果只有上车了车里的人才能下车,下车了车外的人才能上车,那么小明永远在等待小红下车,小红永远在等待小明上车,程序卡死

解决方案:不要把对方的锁放到自己的锁块内:

synchronized (incar){
System.out.printf("现在%s在车上,想下车\n",name);
}
synchronized (outcar){
System.out.printf("现在%s成功下车\n",name);
}

Lock:显式定义同步锁

ReentrantLock对象被创建在需要锁定的对象的类中。lock方法锁定对象,unlock解锁。

import java.util.concurrent.locks.ReentrantLock;
public class testUnsafe {
public static void main(String[] args) throws InterruptedException {
you u = new you();
u.HP = 100;// 初始生命值
attack atk = new attack(u,"ctf");//来自ctf的攻击
Thread t = new Thread(atk);
t.start();
attack atk1 = new attack(u,"awd");//来自awd的攻击
Thread t1 = new Thread(atk1);
t1.start();
}
}
class you {
public final ReentrantLock lock = new ReentrantLock(); //创建锁
public int HP;//玩家生命值
}
class attack implements Runnable {
you u;
String attacker;
public attack(you u,String atk){ //设置被攻击者和攻击者
this.u = u;
this.attacker = atk;
}
@Override
public void run() {
u.lock.lock(); //上锁
if(u.HP <= 60){
System.out.println("You die!");
System.exit(0);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}//模拟延迟来放大问题
System.out.printf("%s攻击了你。\n",attacker);
u.HP-=60;
System.out.printf("还剩%d点HP\n",u.HP);
u.lock.unlock(); //解锁
}
}

线程协作:生产者消费者问题

方法名 作用
wait() 此线程进入等待,直到被其他线程唤醒,与sleep不同,wait()会释放线程锁
wait(long timeout) 线程等待timeout毫秒
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒此对象创建的所有线程

以上都是Object类的方法,都只能在同步方法或同步代码块中使用。

生产者消费者问题

生产者线程生产商品,消费者线程消费购买商品。
生产者在生产完商品后要通知消费者购买商品,消费者购买完商品后要通知生产者生产商品。

生产者:负责生成数据的模块
消费者:负责处理数据的模块

1.管程法

生产者将生产好的数据放入缓冲区,消费者负责从缓冲区拿出数据

public class testSynC {
public static void main(String arg[]){
SynContainer con = new SynContainer();
Thread pro = new Thread(new Productor(con));
Thread cons = new Thread(new Consumer(con));
pro.start();
cons.start();
}
}
class Productor implements Runnable {
SynContainer con;
public Productor(SynContainer con){
this.con = con;
}
@Override
public void run() {
for(int i = 0 ; i<1000 ; i++){
System.out.printf("生产了第%d只鸡\n",i);
try {
con.push(new chicken(i)); //生产
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Consumer implements Runnable{
SynContainer con;
public Consumer(SynContainer con){
this.con = con;
}
@Override
public void run() {
for(int i = 0 ; i<1000 ; i++){
try {
int chickenid = con.pop().id;
System.out.printf("消费了第%d只鸡 %d\n",chickenid,i); //消费
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}}
class chicken{
int id;
public chicken(int id){
this.id = id;
}
}
class SynContainer{
int c = 0;
private chicken[] chickens = new chicken[10];
public synchronized void push(chicken chicken) throws InterruptedException {
if(c == chickens.length){
this.wait(); //如果缓冲区满,则等待消费者消费
}
chickens[c++] = chicken;
this.notifyAll(); //生产出一只鸡,通知消费者消费
}
public synchronized chicken pop() throws InterruptedException {
if(c == 0 ){
this.wait(); //如果缓冲区没有鸡,等待生产者生产
}
chicken chik = chickens[--c]; //消费一只鸡,通知生产者生产
this.notifyAll();
return chik;
}
}

上述代码中,生产者与消费者线程都在不断地进行生产和消费,生产者会在缓冲区满的时候等待消费者消费,消费者会在缓冲区为0时等待生产者生产
alt text

2.信号灯法

public class testFlag {
public static void main(String arg[]){
flagHandler con = new flagHandler();
Thread pro = new Thread(new Kitchen(con));
Thread cons = new Thread(new Eater(con));
pro.start();
cons.start();
}
}
class Kitchen implements Runnable {
flagHandler con;
public Kitchen(flagHandler con){
this.con = con;
}
@Override
public void run() {
for(int i = 0 ; i<1000 ; i++){
try {
con.push();
System.out.printf("生产了第%d只鱼\n",i); //消费
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Eater implements Runnable{
flagHandler con;
public Eater(flagHandler con){
this.con = con;
}
@Override
public void run() {
for(int i = 0 ; i<1000 ; i++){
try {
con.pop();
System.out.printf("消费了第%d只鱼\n",i); //消费
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}}
class flagHandler {
boolean flag = false;//ture:鱼做好了,等顾客吃 ; false:鱼没做好,等厨师做
public synchronized void push() throws InterruptedException {
if(flag){
wait();
}
flag=!flag;
this.notifyAll();
}
public synchronized void pop() throws InterruptedException {
if(!flag){
wait();
}
flag=!flag;
this.notifyAll();
}
}

较为简单,用flag区分哪个进程应该等待,哪个进程应该执行
alt text

线程池

池化思想:减少资源消耗提高运行效率
Callable来实现:
将管程法的main函数改为:

public static void main(String arg[]){
SynContainer con = new SynContainer();
ExecutorService ser = Executors.newFixedThreadPool(2); //创建容量为2个线程的线程池
Future<Boolean> s1 = ser.submit(new Consumer(con)); //提交线程对象到池
Future<Boolean> s2 = ser.submit(new Productor(con));
ser.shutdown(); //关闭线程池
}

实现线程池。

我的疑惑

在缓冲区实现线程交互的例子中,push的逻辑为:

if(c == chickens.length){
this.wait(); //如果缓冲区满,则等待消费者消费
}
chickens[c++] = chicken;
this.notifyAll(); //生产出一只鸡,通知消费者消费

为什么改成如下代码时:

if(c < chickens.length){
chickens[c++] = chicken;
this.notifyAll();
}
else {
this.wait();
}

会出现生产者线程已经结束(即生产者已经全部生产完毕)而消费者线程仍然在运行的情况呢?

A web ctfer from 0RAYS
最后更新于 2024-08-24