线程同步机制
当多个线程需要同时访问和修改同一个对象时,需要创建一个线程池,形成等待队列,逐个依次访问,来避免并发问题
在访问时加入
(锁机制),可以使一个线程访问此对象时获得排他锁,拒绝其他线程对此对象的访问。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);
}
}
在1s的延迟结束后,两个线程同时访问对象,访问到的都是HP为100的you对象,因此绕过了判断死亡的限制,线程不安全。
sychronize
:同步锁,在直接修饰方法的时候默认锁的是this,在修饰代码块时,锁括号内的对象:sychronize(obj){code}sychronize
在锁定对象时:被修饰方法或代码块内的所有代码线程都对锁定对象,都有队列访问机制,即一个线程访问对象时对象被上锁,被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);
}
}
}
线程死锁
当两个进程互相等待对方释放自己需要的资源,而自己掌握着对方需要的资源锁时,会导致永远互相等待,即进程死锁
例子:
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();
}
}
解释:如果只有上车了车里的人才能下车,下车了车外的人才能上车,那么小明永远在等待小红下车,小红永远在等待小明上车,程序卡死
解决方案:不要把对方的锁放到自己的锁块内:
synchronized (incar){
System.out.printf("现在%s在车上,想下车\n",name);
}
synchronized (outcar){
System.out.printf("现在%s成功下车\n",name);
}
Lock:显式定义同步锁
对象被创建在需要锁定的对象的类中。lock方法锁定对象,unlock解锁。ReentrantLock
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时等待生产者生产
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区分哪个进程应该等待,哪个进程应该执行
线程池
池化思想:减少资源消耗提高运行效率
用
来实现: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();
}
会出现生产者线程已经结束(即生产者已经全部生产完毕)而消费者线程仍然在运行的情况呢?
Comments NOTHING