从生产者消费者开始讲线程

先说多线程的好处:
  • 多线程技术使程序的响应速度更快,用户界面可以在进行其它工作的同时一直处于活动状态;
  • 当前没有进行处理的任务时可以将处理器时间让给其它任务;
  • 占用大量处理时间的任务可以定期将处理器时间让给其它任务;
  • 可以随时停止任务;
  • 可以分别设置各个任务的优先级以优化性能。

创建线程主要的方法有两个:

1、继承Thread

1)定义一个类A 继承于Java.lang.Thread类
2)在A类中覆盖Thread类中的run方法。在run方法中我们编写需要执行的操作,run方法里的是线程执行体。
3)在main方法中创建线程对象,并启动线程的
A a = new A(); a.start();
可以看出,这个方法中,线程和线程所要执行的任务是绑定在一起的,无法共享资源。

2、实现Runable接口

1)定义一个类A实现java.lang.Runable接口。类A只是一个普通的类。
2)在A类中覆盖Runable接口中的run方法,编写需要执行的操作。
3)在main方法中,创建线程对象,并启动线程。
Thread t = new Thread(new A()); t.start()
在这个方法中,一个任务可以开辟多个线程(即一个类被传进多个线程实例),这些线程执行的是同一个任务,即资源共享。

生产者消费者进程

很明显,生产者和消费者是两个不同的进程,但是它们共享一个资源,所以要用第二种方法来实现。
作为被共享的资源,资源类要先被创建。

///资源类
public class ShareResource {
    String name;
    int kg;
//生产者要用到的push方法
    public void push(String name, int kg) {
            this.name = name;
            Thread.sleep(10);
            this.kg = kg;
    }
    public void pop() {
            System.out.println("食物名称:" + name + "重量" + kg);
        }
}
    ```
接着Resource的实例作为参数传递进Producer类和Consumer类。

//消费者进程
public class Consumer implements Runnable {
private ShareResource resource = null;
Consumer(ShareResource resource) {
this.resource = resource;
} //重载一个带参数的构造器,将共享的资源传进来
public void run() {
for (int i = 0; i < 50; i++) {
resource.pop();
}
}
}
//生产者进程
public class Producer implements Runnable {
private ShareResource resource = null;
Producer(ShareResource resource) {
this.resource = resource;
} //重载一个带参数的构造器,将共享的资源传进来
public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 == 1) {
resource.push("prok", 5);
} else {
resource.push("rice", 3);
}
}
}
}

最后是一个测试方法

public class Producer_Consumer {
public static void main(String[] args) {
ShareResource resource = new ShareResource();
Thread producer = new Thread(new Producer(resource));
Thread consumer = new Thread(new Consumer(resource));
producer.start();
consumer.start();
}
}

这样一个基本的生产者消费者框架就出来了,但是即使运行了,也存在线程安全问题,下面要讲线程的同步。

#####线程的同步 
线程的同步就是,虽然不能控制线程的执行顺序,但是我们可以控制哪些代码必须在一起执行,比如生产者进程要用的push()方法中,物品的名称和重量的赋值行为必须绑定在一起,如果不绑定在一起,中间断开了会出错。比如:原resource的值为猪肉 10kg,现在有一个新的值 羊肉 5kg 要被传入,当羊肉赋值给name之后,5kg还没来得及赋值,消费者线程已经开始执行pop操作,这时消费者取走的就时羊肉 10kg。 这就产生了错误。所以要引入synchronized 修饰词。 
**三种机制的模式都为 “加锁--修改公共变量--释放锁”** 
1.同步代码块 当要执行的代码被synchronized{}包含在内之后,里面的代码就称为一次原子操作。 代码会一次执行完name 和kg的赋值后自动释放锁。

public void push(String name, int kg) {
synchronized(this){
this.name = name;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.kg = kg;
}
}

2.同步方法 顾名思义,在方法前面加上synchronized修饰符。也是执行完毕之后自动释放锁。但是,千万不能用synchronized来修饰run方法,如下面是消费者进程的run方法,这样,一旦消费者线程开始执行,就会一次性取出50次,失去了线程存在的意义。

public synchronized void run() {
for (int i = 0; i < 50; i++) {
resource.pop();
}
}

3.lock机制 
其中最常用的是RetrantLock可重入锁.  RetrantLock是Lock接口的一个实现类。可重入锁由最近成功获得锁,并且还没有释放该锁的线程所拥有。
官方推荐我们的最佳用法如下,因为Lock机制不会自动解锁,所以必须在用完之后手动解锁,我们将要运行的代码写进try{ }中。

class X {
private final ReentrantLock lock = new ReentrantLock();//此处为创建一个锁对象
// ...

public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}

但是加入锁机制也不能使得程序按照我们的想法运行起来。因为生产者消费者还需要进行通信,生产者生产出一个产品之后通知消费者来取,生产一个消费一个的顺序。
**线程的通信**
线程的通信有两种方式,一种方式基于synchronized同步方法,另一种基于锁机制。
1. 基于synchronized同步方法  
这里主要用的是wait() 和notify()方法。这两个方法都只能被同步锁对象来调用,所以必须使用synchronized来修饰,并定义了一个boolean的isEmpty变量,来判断是否要进入该方法。

public class Resource {
String name;
int kg;
private final ReentrantLock lock = new ReentrantLock();
boolean isEmpty = true;
public void push(String name, int kg) {
synchronized (this) {
try {
while (!isEmpty) {
this.wait();
}
this.name = name;
Thread.sleep(10);
this.kg = kg;
isEmpty = false;
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized public void pop() {
try {
while (isEmpty) {
this.wait();
}
System.out.println("食物名称:" + name + "重量" + kg);
isEmpty=true;
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

第二种方法用到Lock机制。Lock能完成Synchronized所实现的所有功能。但是Lock机制要与Condition绑定在一起使用。此时用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

public class Resource {
String name;
int kg;
private final ReentrantLock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
boolean isEmpty = true;

public void push(String name, int kg) {
        try {
            while (!isEmpty) {
                condition.await();
            }
            lock.lock();
            this.name = name;
            Thread.sleep(10);
            this.kg = kg;
            isEmpty = false;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{
            lock.unlock();
        }
}

 public void pop() {
    try {
        while (isEmpty) {
            condition.await();
        }
        lock.lock();
        System.out.println("食物名称:" + name + "重量" + kg);
        isEmpty = true;
        condition.signal();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
}

}

运行结果如下:

食物名称:rice重量3
食物名称:prok重量5
食物名称:rice重量3
食物名称:prok重量5
食物名称:rice重量3
食物名称:prok重量5
食物名称:rice重量3
食物名称:prok重量5
食物名称:rice重量3
食物名称:prok重量5
食物名称:rice重量3

在这个生产者消费者的例子中,Lock机制相比于Synchronized的优势不明显,因为这里只有两个线程,一个阻塞之后唤醒的必然是另外一个。所以只需要new出一个condition对象。但是线程中,不管是signal()还是nofify()方法都是从线程池中随机挑选出一个线程来唤醒,如果存在多个生产者和消费者,那么生产者唤醒的就未必是消费者了,也有可能是另一个生产者。这个时候只需要new两个condition对象。
如下 muxProducer 和muxConsumer,这样当生产者阻塞了唤醒的一定是消费者,消费者阻塞了唤醒的一定是生产者。

public class Resource {
String name;
int kg;
private final ReentrantLock lock = new ReentrantLock();
public Condition muxProducer = lock.newCondition();
public Condition muxConsumer = lock.newCondition();
boolean isEmpty = true;

public void push(String name, int kg) {
    lock.lock();
        try {
            while (!isEmpty) {
                muxProducer.await();
            }
            this.name = name;
            Thread.sleep(10);
            this.kg = kg;
            isEmpty = false;
            muxConsumer.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{
            lock.unlock();
        }
}

 public void pop() {
    lock.lock();
    try {
        while (isEmpty) {
            muxConsumer.await();
        }
        System.out.println(Thread.currentThread().getName()+"取得食物:" + name + "重量" + kg);
        isEmpty = true;
        muxProducer.signal();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
}

}

**这里一定要注意, lock.lock();一定要写在muxProducer.await();
之前。不然就要报错。因为任务如果要执行等待或者进行唤醒,前提是必须拥有锁。**
运行结果为:

Thread-0生产出米饭3kg
Thread-2取得食物:rice重量3
Thread-1生产出米饭3kg
Thread-2取得食物:rice重量3
Thread-1生产出猪肉5kg
Thread-3取得食物:prok重量5
Thread-1生产出米饭3kg
Thread-2取得食物:rice重量3
Thread-1生产出猪肉5kg
Thread-3取得食物:prok重量5

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容