线程增强

第一节 多个生产者和消费者的生产者-消费者问题

问题的来源

/**
 * 功能:生产者消费者问题(这里创建了4个类)
 * 多个生产者 多个消费者  多个商品  (这样会出问题,生产者一直生产,消费者一直消费)
 */
public class Test {
    public static void main(String[] args) {

        ProduceFactory produceFactory=new ProduceFactory();
        //创建并启动多个生产者和消费者线程
        Runnable target1=new ProduceRunnable(produceFactory);
        for (int i = 0; i <5 ; i++) {
            new Thread(target1,"生产者"+i).start();
        }

        Runnable target2=new ConsumRunnable(produceFactory);
        for (int i = 0; i <10 ; i++) {
            new Thread(target2,"消费者"+i).start();
        }
    }
}

---------------------------------------------------------------------------------------------------------
import java.util.ArrayList;
import java.util.List;

/**
 * 商品工厂
 */
public class ProduceFactory {
    private List<String> list=new ArrayList<>();
    private int max=10;
    /**
     * 生产商品
     * @param produceName
     */
    public synchronized  void produce(String produceName){
        while(list.size()==max){
            try {
                this.wait(); //释放锁,进入等待队列
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //生产商品
        list.add(produceName);
        System.out.println(Thread.currentThread().getName()+"生产商品"+produceName+",目前商品数量:"+list.size());

        //通知消费者消费(这时是每次都通知,这里也可以增加条件通知)
        this.notify();   //由于存在多个生产者和消费者,在这里生产者消费者使用同一把锁,生产者消费者都在同一个等待队列中。唤醒谁敢保证唤醒的一定是消费者?
    }

    /**
     *消费商品
     */
    public synchronized void consume(){ //这里面加synchronized是为了保证线程同步安全性的,还不涉及通信
        //如果仓库为空,等待
        while(list.size()==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //消费商品
        String produceName=list.remove(0);
        System.out.println(Thread.currentThread().getName()+"消费商品"+produceName+",目前商品数量:"+list.size());
        this.notify();
    }
}
----------------------------------------------------------------------------------------------------------
**
 * 生产者线程要执行的任务
 */
public class ProduceRunnable implements Runnable{
    private ProduceFactory factory;

    public ProduceRunnable(ProduceFactory factory) {
        this.factory = factory;
    }

    @Override
    public void run() {
        int i=1;
        while(true){
            factory.produce("商品"+i);
            i++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

--------------------------------------------------------------------------------------------------------
/**
 *消费者要执行的任务
 */
public class ConsumRunnable implements Runnable {
    private ProduceFactory factory;

    public ConsumRunnable(ProduceFactory factory) {
        this.factory = factory;
    }

    @Override
    public void run() {
        while(true){
            //消费一个商品
            factory.consume();
            //
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

解决问题方案一

核心:使用synchronized+while解决

/**
 * 功能:生产者消费者问题
 * 多个生产者 多个消费者  多个商品
 * 采用匿名内部类的方式实现Runnable 这样就可以删掉两个消费者任务类和生产者任务类了
 */
public class Test {
    public static void main(String[] args) {
        //<=JDK1.7的版本需要加上final ,我们平时最好都加上
        //因为锁是this,谁调的方法,谁就是this
        //factory调的方法produce()/cunsume(),所以factory就是this,factory的不能发生变化,所以用final修饰。
        final ProduceFactory factory=new ProduceFactory(30);
        //创建并启动多个生产者和消费者线程

        //采用匿名内部类的方式实现Runnable
        Runnable target1=new Runnable() {
            @Override
            public void run() {
                int i=1;
                while(true){
                    factory.produce("商品"+i);
                    i++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        for (int i = 0; i <5 ; i++) {
            new Thread(target1,"生产者"+i).start();
        }

        //采用匿名内部类的方式实现Runnable
        for (int i = 0; i <10 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        //消费一个商品
                        factory.consume();
                        //
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "消费者" + i).start();
        }
    }
}
-----------------------------------------------------------------------------------------------------------
import java.util.ArrayList;
import java.util.List;

/**
 * 商品工厂
 * 收获:
 * 1.如果有多个线程,建议把wait()放到while循环中,被唤醒后会重新进行条件的判断,可能再次阻塞
 * 2.仓库中最多的商品动态指定,可以通过构造方法和set()方法指定。
 * 3.生产者和消费者线程可以使用匿名内部类来实现,匿名内部类可以访问外部类的变量。
 * 4.使用同步方法或者同步代码块(synchronized),所有线程(多个生产者和消费者线程)都在同一个等待队列中,
 * 如果这些线程使用了同一个同步监视器(也就是同一个锁)this。唤醒的时候无法确定唤醒哪一个。
 *
 *      能否生产者一个等待队列,消费者一个等待队列。唤醒的时候到相应的等待队列中唤醒线程。?
 *      能!!!!
 *      要使用lock锁
 *      一个Lock锁可以产生多个condition条件,一个condition条件对应一个等待队列。
 */
public class ProduceFactory {
    private List<String> list=new ArrayList<>();
    private int max=10;

    public ProduceFactory() {
    }
    //这个时候仓库中的最多的商品就可以动态指定了
    public ProduceFactory(int max) {
        this.max = max;
    }

    /**
     * 生产商品
     * @param produceName
     */
    public synchronized  void produce(String produceName){
        while(list.size()==max){
            try {
                this.wait(); //释放锁,进入等待队列
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //生产商品
        list.add(produceName);
        System.out.println(Thread.currentThread().getName()+"生产商品"+produceName+",目前商品数量:"+list.size());

        //通知消费者消费(这时是每次都通知,这里也可以增加条件通知)
        this.notify();   //由于存在多个生产者和消费者,在这里生产者消费者使用同一把锁,生产者消费者都在同一个等待队列中。唤醒谁敢保证唤醒的一定是消费者?
    }

    /**
     *消费商品
     */
    public synchronized void consume(){ //这里面加synchronized是为了保证线程同步安全性的,还不涉及通信
        //如果仓库为空,等待
        while(list.size()==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //消费商品
        String produceName=list.remove(0);
        System.out.println(Thread.currentThread().getName()+"消费商品"+produceName+",目前商品数量:"+list.size());
        this.notify();
    }
}

解决问题方案二
核心:一个Lock锁可以产生多个condition条件,一个condition条件对应一个等待队列。这个可以保证你(假如你是生产者)唤醒的肯定是消费者,而不是生产者。另外只有一把锁,这有一个消费者进入就绪状态。
(lock锁只管同步,不管通信的,如果需要通信需要得到队列 :
private Condition produceCond=lock.newCondition();
private Condition consumeCond=lock.newCondition();)得到这两个条件后,它到底是生产条件还是消费者条件在于“在什么地方用”,现在建立了两个条件就是希望生产者到一个队列里面来,消费者到另外的一个队列里面来。

**
 * 功能:生产者消费者问题
 * 多个生产者 多个消费者  多个商品
 * 采用匿名内部类的方式实现Runnable 这样就可以删掉两个消费者任务类和生产者任务类了
 */
public class Test {
    public static void main(String[] args) {
        //<=JDK1.7的版本需要加上final ,我们平时最好都加上
        //因为锁是this,谁调的方法,谁就是this,
        //factory调的方法produce()/cunsume(),所以factory就是this,factory的不能发生变化,所以用final修饰。
        final ProduceFactory factory=new ProduceFactory(30);
        //创建并启动多个生产者和消费者线程

        //采用匿名内部类的方式实现Runnable
        Runnable target1=new Runnable() {
            @Override
            public void run() {
                int i=1;
                while(true){
                    factory.produce("商品"+i);
                    i++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        for (int i = 0; i <5 ; i++) {
            new Thread(target1,"生产者"+i).start();
        }

        //采用匿名内部类的方式实现Runnable
        for (int i = 0; i <10 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        //消费一个商品
                        factory.consume();
                        //
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "消费者" + i).start();
        }
    }
}
-----------------------------------------------------------------------------------------------------------
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 商品工厂
 * 收获:
 * 1.如果有多个线程,建议把wait()放到while循环中,被唤醒后会重新进行条件的判断,可能再次阻塞
 * 2.仓库中最多的商品动态指定,可以通过构造方法和set()方法指定。
 * 3.生产者和消费者线程可以使用匿名内部类来实现,匿名内部类可以访问外部类的变量。
 * 4.使用同步方法或者同步代码块(synchronized),所有线程(多个生产者和消费者线程)都在同一个等待队列中,
 * 如果这些线程使用了同一个同步监视器(也就是同一个锁)this。唤醒的时候无法确定唤醒哪一个。
 *
 *      能否生产者一个等待队列,消费者一个等待队列。唤醒的时候到相应的等待队列中唤醒线程。?
 *      能!!!!
 *      要使用lock锁
 *      一个Lock锁可以产生多个condition条件,一个condition条件对应一个等待队列。这个可以保证你(假如你是生产者)唤醒的肯定是消费者,而不是生产者。另外只有一把锁,这有一个消费者进入就绪状态
 */
public class ProduceFactory {
    private List<String> list=new ArrayList<>();
    private int max=10;
    private Lock lock=new ReentrantLock();
    private Condition produceCond=lock.newCondition();
    private Condition consumeCond=lock.newCondition();


    public ProduceFactory() {
    }

    //这个时候仓库中的最多的商品就可以动态指定了
    public ProduceFactory(int max) {
        this.max = max;
    }

    /**
     * 生产商品
     * @param produceName
     */
    public void produce(String produceName){
        lock.lock();
        try{
            while(list.size()==max){
                try {
                    produceCond.await();//唤醒一个(到底是不是随机的一个,还要看锁公平不公平)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //生产商品
            list.add(produceName);
            System.out.println(Thread.currentThread().getName()+"生产商品"+produceName+",目前商品数量:"+list.size());

            //通知消费者消费(这时是每次都通知,这里也可以增加条件通知)
            //唤醒了一个消费者
            consumeCond.signal();   //由于存在多个生产者和消费者,在这里生产者消费者使用同一把锁,生产者消费者都在同一个等待队列中。唤醒谁敢保证唤醒的一定是消费者?
        }finally {
            lock.unlock();
        }
    }

    /**
     *消费商品
     */
    public void consume(){ //这里面加synchronized是为了保证线程同步安全性的,还不涉及通信
        lock.lock(); //要写在try-finally里面  ,最后锁要关闭
        try{
            //如果仓库为空,等待
            while(list.size()==0){
                try {
                    consumeCond.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //消费商品
            String produceName=list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消费商品"+produceName+",目前商品数量:"+list.size());
            produceCond.signalAll();//唤醒所有的生产者,但是只有一个生产者能拿到lock锁,然后进入就绪队列,通过调度获得CPU
        }finally {
            lock.unlock();
        }
    }
}

解决方案三
核心:使用BlockingQueue
说明:如果用的是ArrayBlockingQueue 在用add()、和remove()的时候,比如限制数量是30,如果生产者生产的快,消费者消费的慢,在队列中有剩30个,还要继续生产的时候,会报错:IllegalStateException:Queue full;如果生产者生产的慢,消费者消费的快,队列中没有,但消费者还要继续消费的时候,也会报错:NoSuchElementException;所以我们不用add()和remove(),换成put()和take();注意底层是数组的阻塞队列,必须指定容量,长度固定,不会自动扩容;链表的阻塞队列可以指定长度,如果不指定长度,长度就是整数的最大值。
在这里插入图片描述

/**
 * 功能:生产者消费者问题
 * 多个生产者 多个消费者  多个商品
 * 采用匿名内部类的方式实现Runnable 这样就可以删掉两个消费者任务类和生产者任务类了
 */
public class Test {
    public static void main(String[] args) {
        //<=JDK1.7的版本需要加上final ,我们平时最好都加上
        //因为锁是this,谁调的方法,谁就是this,
        //factory调的方法produce()/cunsume(),所以factory就是this,factory的不能发生变化,所以用final修饰。
        final ProduceFactory factory=new ProduceFactory(30);
        //创建并启动多个生产者和消费者线程

        //采用匿名内部类的方式实现Runnable
        Runnable target1=new Runnable() {
            @Override
            public void run() {
                int i=1;
                while(true){
                    factory.produce("商品"+i);
                    i++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        for (int i = 0; i <5 ; i++) {
            new Thread(target1,"生产者"+i).start();
        }

        //采用匿名内部类的方式实现Runnable
        for (int i = 0; i <10 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        //消费一个商品
                        factory.consume();
                        //
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "消费者" + i).start();
        }
    }
}
-----------------------------------------------------------------------------------------------------------
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static javax.swing.plaf.synth.Region.LIST;

/**
 * 商品工厂
 */
public class ProduceFactory {
    private BlockingQueue blockingQueue;//固定长度

    private int max = 10;

    public ProduceFactory() {
        //blockingQueue =new ArrayBlockingQueue(max);  //注意:底层是数组的这个阻塞队列,必须指定容量,长度固定,不会自动扩容的。
        blockingQueue=new LinkedBlockingDeque(max);//链表的阻塞队列可以指定长度,如果不指定长度,默认是整数的最大值。
    }

    //这个时候仓库中的最多的商品就可以动态指定了
    public ProduceFactory(int max) {
        this.max = max;
        //blockingQueue=new ArrayBlockingQueue(max);
        blockingQueue=new LinkedBlockingDeque(max);
    }

    /**
     * 生产商品
     *
     * @param produceName
     */
    public void produce(String produceName) {
        //生产商品
        //blockingQueue.add(produceName);
        try {
            blockingQueue.put(produceName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "生产商品" + produceName + ",目前商品数量:" + blockingQueue.size());
    }


    /**
     * 消费商品
     */
    public void consume() { //这里面加synchronized是为了保证线程同步安全性的,还不涉及通信

        //消费商品
        String produceName = null;
        try {
            produceName = (String)blockingQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "消费商品" + produceName + ",目前商品数量:" + blockingQueue.size());
    }
}

第二节.lock锁

一.synchronized的缺陷

(有了synchronized,为什么还要出来Lock锁)?
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?
  如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  2)线程执行发生异常,此时JVM会让线程自动释放锁。
  那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
  因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
  但是采用synchronized关键字来实现同步的话,就会导致一个问题:
  如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
  因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
  另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
  但是要注意以下几点:
  1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  2)采用synchronized不需要用户去手动释放锁,当synchronized方法或者代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。对于synchronized方法或者代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。 Lock必须手动释放。

二.java.util.concurrent.locks包下常用的类


1.Lock
首先要说明的就是Lock,通过查看Lock的源码可知,Lock是一个接口:

//Lock是一个接口,里面一共有6个方法
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

下面来逐个讲述Lock接口中每个方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。newCondition()在后面的线程通信中使用。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *获得锁的4种方式
 */
public class TestLock {
    Lock lock=new ReentrantLock();  //Re-entrant-lock可重入锁  之前的Synchroize也是可重入锁
    public static void main(String[] args) {

        //公平锁FareFync,非公平锁NonfairSync,默认非公平锁
        //什么叫公平啊?就是获得锁的时候是不是先来(也就是先等待的)的先拿.
        Lock lock2=new ReentrantLock();
        //下面的这4中方式都是可以获得锁的
        lock2.lock();//第一种获得锁的方式

        lock2.tryLock();//第二种获得锁的方式
        try {
            lock2.tryLock(10, TimeUnit.MILLISECONDS);//第三种获得锁的方式
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            lock2.lockInterruptibly();//第四种获得锁的方式
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //解锁的话就一种方式
        lock2.unlock();

        //还有一种方法,以前讲过了,可以得到一个队列。
        lock2.newCondition();
    }
}

下面的代码中相当于每一个方法里面都是有锁的?是,那么请问在method1()里面能不能调method2()在method2()里面能不能调method3()?可以。这时我们可以看到在menthod1()中到lock还没有被释放的时候,就进入了另外一个方法method2()了,在menthod2()里面需要再次加锁,那么这个锁和method1()里面的是不是同一把锁??是的,都是同一把锁,这就是可重入锁。(也就是说在一个方法(method1())里面一旦获得一把锁以后,在这个方法里面调其他方法,在其他方法中再次用到了这把锁,是可以重新进入的(进method2()),而不需要等待。但是注意解锁的时候,进几次就要解几次)

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *用代码说明什么叫可重入锁
 */
public class TestLock {
    Lock lock=new ReentrantLock();  //Re-entrant-lock可重入锁  之前的Synchroize也是可重入锁
    public static void main(String[] args) {

    }
	//用代码说明什么是可重入锁
    public void method1(){
        lock.lock();
        try{
            menthod2()
        }finally {
            lock.unlock();
        }
    }
    public void method2(){
        lock.lock();
        try{
           method3()
        }finally {
            lock.unlock();
        }

    }
    public void method3(){
        lock.lock();
        try{

        }finally {
            lock.unlock();
        }
    }
}

在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?

lock():首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;
lock.lock();
try  {
  //处理任务
}  catch  (Exception ex){
 }  finally  {
  lock.unlock();   //释放锁
}

tryLock():tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit):tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
lockInterruptibly():lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

2.ReentrantLock
ReentrantLock,意思是“可重入锁”。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
ReentrantLock锁在同一个时间点只能被一个线程锁持有;
而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取。
ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。“锁”是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在同一个时间点只能被一个线程获取(当某线程获取到“锁”时,其它线程就必须等待);ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
3.ReadWriteLock
ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}

一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。
4.ReentrantReadWriteLock
ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
  ReadWriteLock是一个接口。
ReentrantReadWriteLock是它的实现类,ReentrantReadWriteLock包括内部类ReadLock和WriteLock,这两个内部类实现了Lock接口。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/*
//  ReadWriteLock接口只有两个方法,一个是读锁,一个是写锁
public interface ReadWriteLock {
     //读锁
	Lock readLock();
	 //写锁
    Lock writeLock();
}
 */
 
/**
 *ReadWriteLock接口  readLock() writeLock()
 * ReentrantReadWriteLock  是ReadWriteLock接口的实现类
 *
 public class ReentrantReadWriteLock implements ReadWriteLock{
        final Sync sync;
        private final ReentrantReadWriteLock.ReadLock readerLock;
        private final ReentrantReadWriteLock.WriteLock writerLock;
        
		//构造方法,默认是非公平锁
        public ReentrantReadWriteLock() {
            this(false);
        }
        //在创建锁对象的时候给三个属性赋值
        public ReentrantReadWriteLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
            readerLock = new ReadLock(this);
            writerLock = new WriteLock(this);
        }
        // 写/读方法
        public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
 		public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

		//静态内部类
        public static class ReadLock implements Lock{

        }
        public static class WriteLock implements Lock{

        }
 }
 
 */
public class TestLock2 {
    public static void main(String[] args) {
        ReadWriteLock rwl=new ReentrantReadWriteLock();  //默认也是非公平锁,也是可重入的

        Lock readLock=rwl.readLock();  //多次返回的都是同一把读锁,同一把写锁
        Lock readLock2=rwl.readLock();
        Lock writeLock=rwl.writeLock();

        readLock.lock();
        readLock.unlock();

        System.out.println(readLock==readLock2);  //true

    }
}

测试读写锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试读写锁
 * 读操作 写操作
 * 对读操作加上读锁  对写操作加上写锁
 * 读锁 是共享锁  可以多个线程同时获取
 * 写锁 是排他锁 独占锁 互斥锁  一个线程得到了写锁后,其他线程获得写锁会阻塞
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        final Operator operator=new Operator();
        //创建5个读数据的线程
        for (int i = 0; i < 5; i++) {
            new Thread("线程"+i){  //使用的匿名内部类  创建了Thread类的子类  并重写了run方法
                @Override
                public void run() {
                    while(true){
                        operator.read();
                    }
                }
            }.start();
        }

        //创建5个写数据的线程
        for (int i = 0; i <5 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        operator.write();
                    }
                }
            }, "线程" + i).start();
        }
    }
}
class Operator{
    private Lock lock=new ReentrantLock();
    /**
     * 读操作要添加读锁,希望多个线程同时读取,提高效率
     */
    public void read(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始读取数据.......");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"数据读取完毕.......");
        }finally{
            lock.unlock();
        }
    }

    /**
     * 写操作 要使用写锁,保证安全性,只有一个线程独占
     */
    public void write(){
        //为了保证一个线程在写的时候,直到写完才能执行其他的线程,所以加了一把锁。这就是线程同步,保证原子性。
        //这个线程进来之后必须把这个事情做完,即使中间让出来CPU进行阻塞了,但是锁是不放的。在这个期间如果有其他线程想要拿锁的话,只能在外面等着。
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始写数据......");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"数据写完了......");
        }finally {
            lock.unlock();
        }
    }
}
//但是还是没有达到我想要的效果  我想要是的在写的时候,如果一个线程写就一直写完,但是在读的时候,在一个线程读的时候 ,其他线程也可以进行。为了提高性能嘛。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 测试读写锁
 * 读操作 写操作
 * 对读操作加上读锁  对写操作加上写锁
 * 读锁 共享锁  可以多个线程同时获取
 * 写锁 是排他锁 独占锁  一个线程得到了写锁后,其他线程获得写锁会阻塞
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        final Operator operator=new Operator();
        //创建5个读数据的线程
        for (int i = 0; i < 5; i++) {
            new Thread("线程"+i){  //使用的匿名内部类  创建了Thread类的子类  并重写了run方法
                @Override
                public void run() {
                    while(true){
                        operator.read();
                    }
                }
            }.start();
        }

        //创建5个写数据的线程
        for (int i = 0; i <5 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        operator.write();
                    }
                }
            }, "线程" + i).start();
        }
    }
}

class Operator{
    //private Lock lock=new ReentrantLock();
    ReadWriteLock rwl=new ReentrantReadWriteLock();
    /**
     * 读操作要添加读锁,希望多个线程同时读取,提高效率
     */
    public void read(){
        //lock.lock();
        rwl.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始读取数据.......");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"数据读取完毕.......");
        }finally{
            //lock.unlock();
            rwl.readLock().unlock();
        }
    }

    /**
     * 写操作 要使用写锁,保证安全性,只有一个线程独占
     */
    public void write(){
        //为了保证一个线程在写的时候,直到写完才能执行其他的线程,所以加了一把锁。这就是线程同步,保证原子性。
        //这个线程进来之后必须把这个事情做完,即使中间让出来CPU进行阻塞了,但是锁是不放的。在这个期间如果有其他线程想要拿锁的话,只能在外面等着。
        //lock.lock();
        rwl.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始写数据......");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"数据写完了......");
        }finally {
            //lock.unlock();
            rwl.writeLock().unlock();
        }
    }
}
//但是还是没有达到我想要的效果  我想要是的在写的时候,如果一个线程写就一直写完,但是在读的时候,在一个线程读的时候 ,其他线程也可以进行。为了提高性能嘛。

三、Lock和synchronized的选择

总结来说,Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。
  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

第三节 Condition条件

Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。
它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition
一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。
在Object的监视器模型上,一个对象和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。拥有一个同步队列
Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()。

void await() throws InterruptedException
造成当前线程在接到信号或被中断之前一直处于等待状态。
与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:

其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者其他某个线程调用此 Condition 的 signalAll() 方法;或者其他某个线程中断当前线程,且支持中断线程的挂起;或者发生“虚假唤醒”。在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证 它保持此锁。

void signal()
唤醒一个等待线程。
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

void signalAll()
唤醒所有等待线程。
如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。

说明:怎么又出来一个同步队列和一个等待队列,那么什么叫同步队列,什么叫等待队列??图中(锁池状态的是同步队列(什么时候进入这个队列?1.没有抢到锁 2.被唤醒了之后),而等待队列那个是等待队列(被wait()了之后的))简单的理解是同步队列存放着竞争同步资源的线程的引用(不是存放线程),而等待队列存放着待唤醒的线程的引用。

之前的时候一个队列只能有一个同步队列和一个等待队列。但是用了lock()之后,同步队列只有一个,但是等待队列可以有多个,为什么有多个?因为可以有多个Condition,一个Condition一个等待队列。
在这里插入图片描述

第四节 阻塞队列BlockingQueue

//  ArrayBlockingQueue的底层源码
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
		
	//put方法(里面加lock了)
	public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
   	}
}

一、什么是BlockingQueue?

BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:

  1. 当队列满了的时候进行入队列操作。
  2. 当队列空了的时候进行出队列操作
    因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。
    在Java中,BlockingQueue的接口位于 java.util.concurrent 包中(在Java5版本开始提供),由上面介绍的阻塞队列的特性可知,阻塞队列是线程安全的。
    新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。

二、BlockingQueue的用法

阻塞队列主要用在生产者/消费者的场景,下面这幅图展示了一个线程生产、一个线程消费的场景:
在这里插入图片描述
负责生产的线程不断的制造新对象并插入到阻塞队列中,直到达到这个队列的上限值。队列达到上限值之后生产线程将会被阻塞,直到消费的线程对这个队列进行消费。同理,负责消费的线程不断的从队列中消费对象,直到这个队列为空,当队列为空时,消费线程将会被阻塞,除非队列中有新的对象被插入。

三、BlockingQueue的方法

BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:

       抛出异常 	           特殊值       	     阻塞 	               超时 

插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用

四组不同的行为方式解释:
1(异常)
如果试图的操作无法立即执行,抛一个异常。
2(特定值)
如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
3(阻塞)
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4(超时)
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
BlockingQueue :不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
BlockingQueue: 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告Integer.MAX_VALUE 的剩余容量。
BlockingQueue :实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。

四、常见BlockingQueue



4.1 ArrayBlockingQueue
ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。
ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”

4.3 LinkedBlockingQueue

LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为 Integer.MAX_VALUE的容量 。它的内部实现是一个链表。
和ArrayBlockingQueue一样,LinkedBlockingQueue 也是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。

第四节 volilate关键字

一、基本概念

先补充一下概念:Java 内存模型中的可见性、原子性和有序性。
结论:volilate可以保证可见性和有序性,不能保证原子性。
可见性:
  可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。
  在 Java 中 volatile、synchronized 和 final 实现可见性。
原子性:
  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。 volatile不能保证有序性。
  在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。
有序性:
  Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
下面内容摘录自《Java Concurrency in Practice》:
下面一段代码在多线程环境下,将存在问题。

public class VolatileDemo {
    private static volatile boolean isOver = false;
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isOver) ;
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isOver = true;
    }
}

NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值。甚至NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序”。只要在某个线程中无法检测到重排序情况(即使在其他线程中可以明显地看到该线程中的重排序),那么就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程首先写入number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。
  在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行春旭进行判断,无法得到正确的结论。
  这个看上去像是一个失败的设计,但却能使JVM充分地利用现代多核处理器的强大性能。例如,在缺少同步的情况下,Java内存模型允许编译器对操作顺序进行重排序,并将数值缓存在寄存器中。此外,它还允许CPU对操作顺序进行重排序,并将数值缓存在处理器特定的缓存中。

二、Volatile原理

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
 在这里插入图片描述
 当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
  而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
当一个变量定义为 volatile 之后,将具备两种特性:
  1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

**
 *volilate关键字
 */
public class Test {
    private static volatile boolean flag=true;
    public static void main(String[] args) {
        //创建一个线程并启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(flag){
                    //System.out.println("-------");
                }
            }
        }).start();
        //让主线程休息一会
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag=false;
        //我在主线程里面对flag做的改变,在上面新建的线程(这是还没有将输出语句注释掉,变量前面也没添加volilate)能不能见到??
        //如果能见到,则上面的循环会停止;如果见不到则上面的循环不会停止。
        //运行结果是循环停止了,也就是上面 的线程见到 。

        //如果我们将循环里面的输出语句注释掉。尽管flag被改为false,但是循环不会结束,循环会一直在进行中,
        // 也就是说在这个时候,主线程对flag的改变没有告诉告诉另外一个线程。那么怎么才能告诉呢?

        //在这个变量前面加一个volilate  则循环就会停止。


    }
}

第五节 线程池

一、线程池ThreadPoolExecutor

在这里插入图片描述
Executor:线程池顶级接口,只有一个方法
ExecutorService:真正的线程池接口
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭线程池
AbstractExecutorService:基本实现了ExecutorService的所有方法
ThreadPoolExecutor:默认的线程池实现类
ScheduledThreadPoolExecutor:实现周期性任务调度的线程池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

================================================

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}

========================================================

线程池参数
corePoolSize:核心池的大小
默认情况下,创建了线程池后,线程数为0,当有任务来之后,就会创建一个线程去执行任务。但是当线程池中线程数量达到corePoolSize,就会把到达的任务放到队列中等待。
maximumPoolSize:最大线程数。
corePoolSize和maximumPoolSize之间的线程数会自动释放,小于等于corePoolSize的不会释放。当大于了这个值就会将任务由一个丢弃处理机制来处理。
keepAliveTime:线程没有任务时最多保持多长时间后会终止
默认只限于corePoolSize和maximumPoolSize之间的线程
TimeUnit:keepAliveTime的时间单位
BlockingQueue:存储等待执行的任务的阻塞队列,有多中选择,可以是顺序队列、链式队列等。
ThreadFactory 线程工厂,默认是DefaultThreadFactory,Executors的静态内部类
RejectedExecutionHandler:拒绝处理任务时的策略。如果线程池的线程已经饱和,并且任务队列也已满,对新的任务应该采取什么策略。
比如抛出异常、直接舍弃、丢弃队列中最旧任务等,默认是直接抛出异常。
1、DiscardPolicy:什么也不做
2、AbortPolicy:java默认,抛出一个异常:
3、DiscardOldestPolicy:在线程池的等待队列中,将头取出一个抛弃,然后将当前线程放进去。
4、CallerRunsPolicy:如果发现线程池还在运行,就直接运行这个线程

说明:比如线程池用的是newFixedThreadPool(10),相当于有10个线程,那么我可以来多少个任务呀?任务是不是也只能与10个呢??不是的,任务来10个的话,岂不是任务处理完就没有任务了!!所以可以有更多的任务,可以排队的,在哪里排队呢?就在阻塞队列里面排队。

/**
 * 测试进程池
 */

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPool {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService pool = Executors.newCachedThreadPool();
    }
}
-----------------------------------------------------------------------------------------------------------
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * 商品工厂
 * 收获:
 * 1.如果有多个线程,建议把wait()放到while循环中,被唤醒后会重新进行条件的判断,可能再次阻塞
 * 2.仓库中最多的商品动态指定,可以通过构造方法和set()方法指定。
 * 3.生产者和消费者线程可以使用匿名内部类来实现,匿名内部类可以访问外部类的变量。
 * 4.使用同步方法或者同步代码块(synchronized),所有线程(多个生产者和消费者线程)都在同一个等待队列中,
 * 如果这些线程使用了同一个同步监视器(也就是同一个锁)this。唤醒的时候无法确定唤醒哪一个。
 * <p>
 * 能否生产者一个等待队列,消费者一个等待队列。唤醒的时候到相应的等待队列中唤醒线程。?
 * 能!!!!
 * 要使用lock锁
 * 一个Lock锁可以产生多个condition条件,一个condition条件对应一个等待队列。这个可以保证你(假如你是生产者)唤醒的肯定是消费者,而不是生产者。另外只有一把锁,这有一个消费者进入就绪状态
 */
public class ProduceFactory {
    private BlockingQueue blockingQueue;//固定长度

    private int max = 10;

    public ProduceFactory() {
        //blockingQueue =new ArrayBlockingQueue(max);  //注意:底层是数组的这个阻塞队列,必须指定容量,长度固定,不会自动扩容的。
        blockingQueue=new LinkedBlockingDeque(max);//链表的阻塞队列可以指定长度,如果不指定长度,默认是整数的最大值。
    }

    //这个时候仓库中的最多的商品就可以动态指定了
    public ProduceFactory(int max) {
        this.max = max;
        //blockingQueue=new ArrayBlockingQueue(max);
        blockingQueue=new LinkedBlockingDeque(max);
    }

    /**
     * 生产商品
     *
     * @param produceName
     */
    public void produce(String produceName) {
        //生产商品
        //blockingQueue.add(produceName);
        try {
            blockingQueue.put(produceName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "生产商品" + produceName + ",目前商品数量:" + blockingQueue.size());


    }

    /**
     * 消费商品
     */
    public void consume() { //这里面加synchronized是为了保证线程同步安全性的,还不涉及通信

        //消费商品
        String produceName = null;
        try {
            produceName = (String)blockingQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "消费商品" + produceName + ",目前商品数量:" + blockingQueue.size());
    }
}

-----------------------------------------------------------------------------------------------------------
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 功能:生产者消费者问题
 * 多个生产者 多个消费者  多个商品
 * 采用匿名内部类的方式实现Runnable 这样就可以删掉两个消费者任务类和生产者任务类了
 */
public class Test {
    public static void main(String[] args) {
        //<=JDK1.7的版本需要加上final ,我们平时最好都加上
        //因为锁是this,谁调的方法,谁就是this,
        //factory调的方法produce()/cunsume(),所以factory就是this,factory的不能发生变化,所以用final修饰。
        final ProduceFactory factory=new ProduceFactory(30);
        //创建并启动多个生产者和消费者线程

        //采用匿名内部类的方式实现Runnable
        Runnable target1=new Runnable() {
            @Override
            public void run() {
                int i=1;
                while(true){
                    factory.produce("商品"+i);
                    i++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        //创建一个线程池
        ExecutorService pool= Executors.newCachedThreadPool();

        for (int i = 0; i <5 ; i++) {
            pool.execute(target1);
        }

        //采用匿名内部类的方式实现Runnable
        for (int i = 0; i <10 ; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        //消费一个商品
                        factory.consume();
                        //
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }
        //关闭线程池
        pool.shutdown();
    }
}

二、ForkJoin框架

  1. 什么是ForkJoin框架 适用场景
    虽然目前处理器核心数已经发展到很大数目,但是按任务并发处理并不能完全充分的利用处理器资源,因为一般的应用程序没有那么多的并发处理任务。基于这种现状,考虑把一个任务拆分成多个单元,每个单元分别得到执行,最后合并每个单元的结果。
    Fork/Join框架是JAVA7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
    在这里插入图片描述
    在这里插入图片描述
    2.工作窃取算法(work-stealing)
    一个大任务拆分成多个小任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列中,并且每个队列都有单独的线程来执行队列里的任务,线程和队列一一对应。
    但是会出现这样一种情况:A线程处理完了自己队列的任务,B线程的队列里还有很多任务要处理。
    A是一个很热情的线程,想过去帮忙,但是如果两个线程访问同一个队列,会产生竞争,所以A想了一个办法,从双端队列的尾部拿任务执行。而B线程永远是从双端队列的头部拿任务执行。
    在这里插入图片描述
    **注意:**线程池中的每个线程都有自己的工作队列(PS,这一点和ThreadPoolExecutor不同,ThreadPoolExecutor是所有线程公用一个工作队列,所有线程都从这个工作队列中取任务),当自己队列中的任务都完成以后,会从其它线程的工作队列中偷一个任务执行,这样可以充分利用资源。
    工作窃取算法的优点:
    利用了线程进行并行计算,减少了线程间的竞争。
    工作窃取算法的缺点:
    1、如果双端队列中只有一个任务时,线程间会存在竞争。
    2、窃取算法消耗了更多的系统资源,如会创建多个线程和多个双端队列。
    3.主要类
    1、ForkJoinTask:使用该框架,需要创建一个ForkJoin任务,它提供在任务中执行fork和join操作的机制。一般情况下,我们并不需要直接继承ForkJoinTask类,只需要继承它的子类,它的子类有两个:
    a、RecursiveAction:用于没有返回结果的任务。
    b、RecursiveTask:用于有返回结果的任务。
    2、ForkJoinPool:任务ForkJoinTask需要通过ForkJoinPool来执行。
    3、ForkJoinWorkerThread:ForkJoinPool线程池中的一个执行任务的线程。

每日练习:

public class Test {
    public static void main(String[] args) {
        //创建一个用于输出数字的对象
        final Print print=new Print();
        //创建3个线程
        for (int i = 0; i <3 ; i++) {
            new Thread(new Runnable() {  //使用匿名内部类
                //调用Print 里面的print()方法,调用4次。
                public void run() {
                    for (int j = 0; j < 4; j++) {
                        print.print();
                    }

                }
            },i+"").start();
        }
    }
}
//
class Print{
    //1-60个数字
    int no=1;
    //记录状态,3个线程轮循输出
    int status=0;
    public synchronized void print(){
        //获取线程名称,线程名称0-2
        int threadName=Integer.parseInt(Thread.currentThread().getName());
        while((status%3)!=threadName){
            //不是当前线程输出
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //当前线程输出数字,输出5个数字
        System.out.print("线程"+threadName+":");
        for (int count = 0; count < 5; count++) {
            if(count>0){
                System.out.print(",");
            }
            System.out.print(no++);
        }
        System.out.println();

        status=(status%3) +1; //也可以status++

        //输出结束,唤醒其他所有的等待线程
        this.notifyAll();

    }
}

import java.util.ArrayList;
import java.util.List;

/**
 * 技术点:读写互斥通过使用isWrite、isRead变量进行判断;写写互斥可以通过同步代码块实现
 */
public class Test7 {
    public static void main(String[] args) throws Exception {
        final MyContainer c=new MyContainer();
        //两个线程,每个线程新增10个数据
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++)//为了数据多一点,来个for循环,循环体只有一行代码的时候可以不写大括号
                        c.add(new Object());
                }
            },"add"+i).start();
        }
        //主线程睡10毫秒
        Thread.sleep(10);

        //一共是两个线程,每个线程查10个数据
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <10 ; j++)
                        System.out.println(Thread.currentThread().getName()+"  get - "+c.get(0));
                }
            },"get"+i).start();
        }
    }

}
class MyContainer{
    //数据载体
    List<Object> list=new ArrayList();
    final Object writeLock=new Object();
    //是否有线程正在执行写操作
    boolean isWrite=false;
    //是否有线程正在执行读操作
    boolean isRead=false;

    //写
    public void add(Object obj){
        //判断有没有其他线程读,如果有读取的话,自旋解决读写互斥
        while(isRead){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //同步代码块,锁定写锁
        synchronized (writeLock){
            try{
                //先判断有没有其他线程写
                while(isWrite){
                    //如果有其他线程写,则进入等待队列,双保险。(即判断,又通讯)
                    try {
                        writeLock.wait();  //可以做线程的通讯了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //当前线程写
                isWrite=true;
                list.add(obj);
                System.out.println(Thread.currentThread().getName()+"  add -"+obj);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                writeLock.notify();//写完之后,唤醒其他的写线程。
            }finally {
                //这里可以不使用finally,因为sychronized本身就是原子操作,已经保证了原子性。
                // 这里之所以这样写是为了保证代码风格的统一。
                isWrite=false;
            }
        }

    }
    //读
    public Object get(int index){
        if(index>=list.size()){
            throw new IndexOutOfBoundsException();
        }
        while(isWrite){
            //有线程执行写操作。当前线程阻塞,自旋等待
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try{
            //当前线程正在执行读操作
            isRead=true;
            Object value=list.get(index);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return value;
        }finally {  //为了保证代码的绝对有效性,所以放在了finally里面。因为用了finally,
                    // 所以上面加上了try,这样写还可以保证get(),它俩是原子的,避免可能出现的cup时间片到期
            isRead=false;
        }
        /*当然我们可以这样写,但是要注意的是在isRead=false和return之间可能线程中断,
        也就是说在执行完isRead=false之后,cpu的时间片到期了,线程释放锁资源,线程在这停住了,读操作还没有结束,
        但是已经不再读取状态了,那么其他线程进入做了一个写操作、删除、或者修改,
        那么这个return value,这个数据就是一个脏数据了。所以要像上面的那样做成原子性,查完数据(即get完)直接return,
        finally里面做修改,因为执行到return语句时候,代表返回值已确认。
        isRead=true;
        Object value=list.get(index);
        isRead=false;
        return value;
        */


    }
}

说明:
while(isWrite){
//有线程执行写操作,当前线程阻塞
这里我们可以用wait,但是这里没有用,为什么呢??

}

在线程做状态切换的时候,最慢的就是锁池状态和等待队列,(锁池状态就是:synchronize修饰的时候,比如method(),当这个方法被多个线程调用的时候,只有一个线程可以执行,其他线程要等待锁标记的释放才能执行,这叫锁池状态,是阻塞状态的一种。它也是一个池,这个池里面放的都是线程,当锁标记释放以后,把这个池里面的线程交出来,cpu分时间片,分给那个线程,那个线程执行,拿到锁标记,其他线程重新再回到池中。等待队列就是wait()/notify() wait()就是进入等待队列,notify() 就是从等待队列里面唤醒,这叫等待队列状态的一个切换。这两种效率都比较低,很慢),我们说反复的进行线程状态的切换,太慢了,现在的电脑cpu都是多核的,而且执行效率比较高,那么我们考虑使用JVM锁状态中的一种叫自旋锁(在极端的时间里让它自旋空转一会,比方说循环10毫秒,因为电脑里面10毫秒可以算很多很多的工作了,这样的话就可以让出CPU时间片,让写操作的线程执行完写操作,然后我就可以进行读取了。自旋锁的实现方案特别简单即Thread.sleep(10))


如果不加condition,也就是也进行同步(也就是我获得锁的时候,别人不能运行,我释放锁的时候,所有的人抢这把锁就可以了。),不进行线程通讯的话,输出结果会有0和-1输出,不是我们想要的,为什么呢??这个是不是没锁住??锁住了,分析:第一个进行线程进行lock()的时候,三个线程通过判断都进入循环了,但是只有一把锁,加入线程1抢到这把锁了,在cpu执行的时候,如果它的时间片还没到期的时候,他会不停的循环运行从30-1,到1以后再减减变成0 了,然后1号线程判断逻辑不满足,退出,释放锁。加入2号线程拿到锁了,输出0,再减减,变成-1,再去循环判断不满足,退出,释放锁,这时候加入线程3拿到锁, 输出-1,再减减,变成-2,循环判断都不满足大于零,退出。所以最后多输出了两张票,如果真的是网络售票的时候则会多卖两张票,有重号,肯定不行。所以要用到condition,进行线程通讯,线程1进行输出的时候,让线程2、3等待,线程1输出完了释放锁,把另外两个线程唤醒,让他们3个交替运行。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *模拟售票(如果只用线程同步,而没有进行线程通讯的话,输出会出现错误,会有0和-1输出)
 */
public class Test4 {
    public static void main(String[] args) {
        final Sales s=new Sales(30);
        for (int i = 0; i <3 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    s.sale();
                }
            },"线程"+i).start();
        }
    }
}
class Sales{
    private int count;
    private Lock lock=new ReentrantLock();
    public Sales(int count){
        this.count=count;
    }
    public void sale(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }
}
//输出结果:
/*
线程0:30
线程0:29
线程0:28
线程2:27
线程2:26
线程2:25
线程2:24
线程2:23
线程2:22
线程2:21
线程2:20
线程2:19
线程2:18
线程2:17
线程2:16
线程2:15
线程2:14
线程2:13
线程2:12
线程2:11
线程2:10
线程2:9
线程2:8
线程2:7
线程2:6
线程2:5
线程2:4
线程2:3
线程2:2
线程2:1
线程1:0
线程0:-1
*/

有一种错误情况是这样的,就是把lock.lock放到了循环的外面,这样会出现一个结果:只有一个线程执行,也就是只有线程一在卖票,直到将30张票卖完。代码和结果截图如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *模拟售票(如果只用线程同步,而没有进行线程通讯的话,输出会出现错误,会有0和-1输出)
 */
public class Test2 {
    public static void main(String[] args) {
        final Sale s=new Sale(30);
        for (int i = 0; i <3 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    s.sale();
                }
            },"线程"+i).start();
        }
    }
}
class Sale{
    private int count;
    private Lock lock=new ReentrantLock();
    public Sale(int count){
        this.count=count;
    }
    public void sale(){
        //当数字大于0,循环输出
        lock.lock();
        try{
            while(count>0) {
                System.out.println(Thread.currentThread().getName() + ":" + count);
                count--;
            }
        }finally{
            lock.unlock();
        }
    }
}

结果:

在加上1个condition以后,用了c.await(),和c.signalAll(),在唤醒其他线程的时候,这里选了几个不可行的位置,并说明了不可行的原因:

import java.sql.Connection;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *模拟售票(如果只用线程同步,而没有进行线程通讯的话,输出会出现错误,会有0和-1输出)
 */
public class Test4 {
    public static void main(String[] args) {
        final Sales s=new Sales(30);
        for (int i = 0; i <3 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    s.sale();
                }
            },"线程"+i).start();
        }
    }
}
class Sales{
    private int count;
    private Lock lock=new ReentrantLock();
    private Condition c=lock.newCondition();
    public Sales(int count){
        this.count=count;
    }

    public void sale(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //【位置2】c.signalAll();
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }
}

【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
【位置2】先去唤醒其他线程,自己再await(),会报错IllegalMonitorStateException(监控器状态异常),这是为什么??有线程1,2,3,上来都在抢锁,线程1抢到锁,线程2,3等待锁,然后这个获得锁的线程1开始唤醒其他线程(即2、3),但是2、3是等待锁的状态,线程1去唤醒他俩,出现了状态切换有问题,所以报错。记住所有的await和singalAll都要在加上锁之后进行,没有锁就没法进行。

暴露问题,下面的代码加上了3个condition,写了3个方法,将signalAll放在了finally中,结果如图所示:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *模拟售票(如果只用线程同步,而没有进行线程通讯的话,输出会出现错误,会有0和-1输出)
 */
public class Test4 {
    public static void main(String[] args) {
        final Sales s=new Sales(30);
        //创建线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale1();
            }
        },"线程1").start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale2();
            }
        },"线程2").start();
        //线程3
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale3();
            }
        },"线程3").start();

    }
}
class Sales{
    private int count;
    private Lock lock=new ReentrantLock();
    private Condition c1=lock.newCondition();
    private Condition c2=lock.newCondition();
    private Condition c3=lock.newCondition();
    public Sales(int count){
        this.count=count;
    }

    public void sale1(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                c2.signalAll();
                c3.signalAll();
                //释放锁
                lock.unlock();
            }

        }
    }

    public void sale2(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                c1.signalAll();
                c3.signalAll();
                //释放锁
                lock.unlock();
            }

        }
    }

    public void sale3(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c3.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                c1.signalAll();
                c2.signalAll();
                //释放锁
                lock.unlock();
            }

        }
    }
}


分析原因:因为await()以后,后面的代码不执行,这也是我们不往finally里面写代码的原因。所以三个线程都是都进行await()状态。所以我们将singalAll换一个地方,但是有出现了新的问题,虽然线程之间的通信没有问题了,但是3个线程全部都卡在了await,睡觉了,程序没有停止。如下代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *模拟售票(如果只用线程同步,而没有进行线程通讯的话,输出会出现错误,会有0和-1输出)
 */
public class Test4 {
    public static void main(String[] args) {
        final Sales s=new Sales(30);
        //创建线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale1();
            }
        },"线程1").start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale2();
            }
        },"线程2").start();
        //线程3
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale3();
            }
        },"线程3").start();

    }
}
class Sales{
    private int count;
    private Lock lock=new ReentrantLock();
    private Condition c1=lock.newCondition();
    private Condition c2=lock.newCondition();
    private Condition c3=lock.newCondition();
    public Sales(int count){
        this.count=count;
    }

    public void sale1(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c2.signalAll();
                    c3.signalAll();
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }

    public void sale2(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c1.signalAll();
                    c3.signalAll();
                    c2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }

    public void sale3(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c1.signalAll();
                    c2.signalAll();
                    c3.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }
}

执行结果截图:

原因:也就是在await()的时候,没有结束条件,也就是说,我们什么时候有await(),什么时候没有await(),需要判断一下。

成功的代码(也就是加上了await的判断之后):

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *模拟售票(如果只用线程同步,而没有进行线程通讯的话,输出会出现错误,会有0和-1输出)
 */
public class Test4 {
    public static void main(String[] args) {
        final Sales s=new Sales(30);
        //创建线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale1();
            }
        },"线程1").start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale2();
            }
        },"线程2").start();
        //线程3
        new Thread(new Runnable() {
            @Override
            public void run() {
                s.sale3();
            }
        },"线程3").start();

    }
}
class Sales{
    private int count;
    private Lock lock=new ReentrantLock();
    private Condition c1=lock.newCondition();
    private Condition c2=lock.newCondition();
    private Condition c3=lock.newCondition();
    public Sales(int count){
        this.count=count;
    }

    public void sale1(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                //【位置2】也是可行的c2.signalAll();  
                //c3.signalAll();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                	//【位置1,可行】
                    c2.signalAll();
                    c3.signalAll();
                    if(count>0){
                        c1.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }

    public void sale2(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c1.signalAll();
                    c3.signalAll();
                    if(count>0){
                        c2.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }

    public void sale3(){
        //当数字大于0,循环输出
        while(count>0){
            try{
                //获取锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+":"+count);
                count--;
                try {
                    c1.signalAll();
                    c2.signalAll();
                    if(count>0){
                        c3.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //【位置1】在这里唤醒不对,因为上面await()睡了之后,下面的这一行代码不运行。只有被其他线程唤醒的时候才运行。
                //【位置1】c.signalAll();
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}

运行结果:

说明:其实加上了condition 之后,大大的加大了编码难度,但是可以试验一下,单并发量足够高的时候,它的效率是比sychronized,wait,notify的效率要高的。位置【1】,【2】都是可行的,综上所述:记住所有的条件想要进行await和sinalAll前提是加锁,先去lock.lock()然后再去sinalAll和await()。
这道题使用一个condition也可以,自己实现。

第五题

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 */
public class Test5 {
    public static void main(String[] args) {
        final Computer c=new Computer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <10 ; i++) {  //为了看出特点,来个循环,生产10次,消费10次.消费者和生产者应该轮询工作。
                    c.put();
                }
            }
        },"生产者").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    c.get();
                }
            }
        },"消费者").start();
    }

}
class Computer{
    //容量只能是1,生产一个必须等待被消费,消费后必须等待再生产
    boolean isEmpty=false;
    Lock lock=new ReentrantLock();
    Condition producer=lock.newCondition();
    Condition consumer=lock.newCondition();

    public void put(){
        //生产
        try{
            lock.lock();
            if(isEmpty){  //不是空的
                //不可以生产,生产者等待
                try {
                    producer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("生产电脑");
            isEmpty=true;
            //唤醒消费者
            consumer.signalAll();
        }finally {
            lock.unlock();
        }
    }

    //
    public void get(){
        //消费
        try{
            lock.lock();
            if(!isEmpty){  //是空的
                //不能消费,消费者等待
                try {
                    consumer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费电脑");
            isEmpty=false;
            //唤醒生产者
            producer.signalAll();
        }finally{

        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值