多线程详解

多线程详解

多线程实际上就是一种多个任务同时处理的技术。

比如一个银行只有一个窗口,所有人到这里来排队取钱,这是我们平时的程序,所有人都要排队进行,线性执行。多线程技术则是开设了多个窗口一同对来的客户进行业务处理,多个人可在同一个时间执行取钱这个操作。

如果你理解了这个概念。我们就要从微观上面继续讲解。

在微观上这个例子并不能很好地解释多线程发生时的状况。

进程与线程

这里要先讲一个小知识,就是时间片轮转技术。它是多线程在微观上的实现,将CPU的运行时间分为多个小小的时间片,每个任务(线程)会被分配这些时间片,当被分到的时候就意味着CPU已经正在处理这个人任务。当这个时间片(分配的运行时间)运行结束(时间到了)。线程将会让出CPU资源,给其他线程。

您可能想问,说这个实际上似乎没有什么意义,毕竟所有的任务所需要的时间都是一样的,你分开进行,和一同排队进行CPU所要工作的时间不是一样的吗?

当然如此,但是你忽略了一个重点.就是任务会有阻塞这个问题.如果任务发成阻塞,再不是多线程的情况下,所有的线程都要等待.CPU不做任何运算,等待阻塞任务重新需要CPU资源.

但是在多线程中这个被阻塞的线程会让出资源,让其他资源进行执行.效率提高.也就是说,只要有任务,CPU就是在执行的,最大限度的利用了CPU资源.


有了这个先到知识,我们开始了解进程和线程。

我更愿意将线程和进程的关系看成这个样子。(也许不大准确)

我们操作计算机中的程序完成各种各样的任务,而每个程序则是依靠进程来工作,这在任务管理器中有很好的展示。

我们可以看见Typora这个软件有两个进程,占有了CPU。当然你也可以将运行的所有的exe程序理解为一个进程.

计算机依靠进程来分配各种计算机资源(内存,空间等....).

而线程就是依靠计算机分配给进程的这些资源来进行工作的,可以这么说线程是进程的实体化.

线程实际上是一个由程序入口,一个计算机指令序列和一个程序出口构成的,它依靠在进程中的资源(内存,空间)进行工作,线程本身不占据多少空间,(实际上也是占据一些的.非常少,可以忽略不计.)而CPU资源实际上是直接被分配给线程的,用以运行其中的计算机指令.也就是我们说的多线程,实际上是线程同步运行发生的各种问题.

线程中有的资源
一个指向当前指令的指针  --必须要知道要执行什么方法吧?所以要有一个
一个栈   --虚拟机栈
一个寄存器值集合  --程序计数器?
一个私有数据区   
(我觉得就是JVM线程隔离部分)

所以我们总结一下:

  • 计算机靠进程分配资源(系统进行资源分配的独立单位),线程则是被分配给CPU资源(CPU调度的独立单位).
  • 线程是进程的实体
  • 线程无空间,进程有独立空间概念.多个线程共享
  • 一个程序有多个进程,一个进程有多个线程

java创建线程

线程创建有两种方法,分别是继承Thread实现Runnable接口.

我们在讲解这两种方式之前要了解一部分相关的知识,打开javaApi我们可以发现,Thread类实际上实现了Runnable接口,他们之间具有多太关系.也就是说,Thread可以向上转型位Runnable类.

两者的关系没有什么本质的差别,实际上都是差不多的.

那么问题来了,为什么要出现这个Runnable方法呢?这不是多此一举嘛!

并不是的,我们知道在java中只能单继承,也就是说只能有一个父类.

在这种情况下,所有子类(Obeject的子类这种不算)都会出现问题,他们根本无法在实现多线程了.

package TEST;

public class HOMEWORK1 {
}
class home extends HOMEWORK1,Thread{//这里无法通过编译,会有红色波浪线
    
}

所以要有Runnable这种实现多线程的方式.

1.Thread实现多线程

方式很简单,只要让多线程的实现类直接继承Thread并实现run()方法就行.

 class Alogin extends Thread{
public void run(){
  LoginServer.dopost1("a","aa");
}
 }

启动方式比较简单,直接实例化调用start()方法即可.

2.Runnable实现多线程

package TEST;
//测试类
public class HOMEWORK1 {
    public static void main(String[] args) {

        home home = new home();
        Thread thread = new Thread(home);//必须将Runnable对象传入Thread中才可以
        thread.start();//必须调start()开始,如果调用run()则只会按照一个普通程序执行,不会有多线程的特性

    }
}
//实现Runnable的类
class home implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread());
    }
}

3.Runnable与Thread

看了上面两个内容我们似乎知道了这两者之间的差别,

但是我们不要忘了Thread既然实现了Runnable接口就意味着可以向上转型,如下代码是正确的.

public class HOMEWORK1 {
    public static void main(String[] args) {
        Runnable home = new home();//实现Thread的类可以被向上转型为Runnable对象
        Thread thread = new Thread(home);//依然可以成功实现
        thread.start();
    }
}
class home extends Thread {
    public void run() {
        System.out.println(Thread.currentThread());
    }
}

4.线程在实践中的一些特性

实际上多线程序在运行中有一些和普通程序也有区别.其比较核心的就是随机性.

public class HOMEWORK1 {
    public static void main(String[] args) {

        Runnable home = new home();
        Thread thread = new Thread(home);
        thread.start();
        System.out.println("他先执行----执行成功");
    }
}

说白了就是跟线程相关的代码未必会按照顺序执行.

如下是输出:

用的还是上一个例子中的那个线程类,我们发现并没有因为 thread.start(); System.out.println("他先执行----执行成功");中先执行,就先动用这个线程.这就是随机性.(这是因为异步执行,main方法代表的是一个main线程,而Thread对象激活的thread线程和main不是一个线程,所以他俩同时执行.)

他先执行----执行成功
Thread[Thread-1,5,main]

要想解决这个问题就加一个sleep()就ok!

如下样子:

public class HOMEWORK1 {
    public static void main(String[] args) throws InterruptedException {
        Runnable home = new home();
        Thread thread = new Thread(home);
        thread.start();
        Thread.sleep(3000);
        System.out.println("他先执行----执行成功");
    }
}
Thread[Thread-1,5,main]
他先执行----执行成功

附加内容--停止线程

一个线程既然能启动,就能终止。那么在多线程中如何终止线程。

实际上多线程中并不能主动终止线程,这里的主动终止是指调用方法去终止

不能说完全没有,实际上也有一个方法

stop()

但因为容易导致问题,所以这个代码也就不再使用了.

所以停止线程一般使用一些复杂一些的方式,主要有如下几个:

return停止  //实际上是让run()方法终止,线程自动死亡
interrupt停止//这个操作实际上要复杂很多,要混合其他方法进行停止
沉睡中停止,sleep() //实际上也是利用异常停止
异常法停止
    

1.interrupt停止

interrupt实际上不能停止线程,只会给线程一个停止标志,可以被专门的方法检测到.你必须用相关的方法才能让相关的线程停止.

public class MyThread extends Thread {
    public void run(){
        for (int i = 0; i <100 ; i++) {
            System.out.println(i);
        }
    }
}
class RUN{
    /**
     * 完整的执行了程序,说明
     * interrupt并没有中断程序,正常来讲是要用5000000这种大数据的,但是电脑不好就用100代替.
     * 所以这里要假设一下,主线程停止大的两秒中,myThread线程不会消失.
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(2000);
        myThread.interrupt();//中断信号
    }

而这个让线程停止的方法则是

isterrupted
isInterrupted

两者的作用都是测试线程是否已经被中断

但是两者还是有差别的.

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i <500000 ; i++) {
            System.out.println(i);
        }
    }
}
class Run{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        System.out.println("是否停止"+thread.interrupted());//false
        System.out.println("是否停止:"+thread.interrupted());//false
        //这是因为isInterrupted判断的是当前线程,这个当前线程就是 main main一直没有给过中断
        System.out.println("end!");
    }
}
class Run2{
    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().interrupted());
        System.out.println(Thread.currentThread().interrupted());
        /**
         * true
         * false
         * interrupted(会发生置位
         */
    }
}
//如何正确使用isInterrupted
class RUN3{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread=new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        System.out.println("是否停止1?"+thread.isInterrupted());//true
        System.out.println("是否停止2?"+thread.isInterrupted());//true
    }
}

从上面的代码中我们可以看到两者的差别isterrupted方法会导致停止标志置位,即第一次调用如果答案是true,则将停止标志重新置为false.

interrupt方法如果在线程处于sleep()时挂起调用那么就会出现抛出一个异常

isInterrupted则是只判断线程是否停止,而不对其进行任何操作.

2.return停止

return停止实际上我会在之后讲,即如果run()方法执行完毕,线程自动死亡.所以return可以结束run()方法,run()方法结束则线程死亡,线程死亡了自然线程就结束了.

这就是return结束线程的逻辑.


3.沉睡中停止,sleep() ,异常法停止

这两个问题其实也是一个问题.其核心在于如果在线程中出现异常,线程会自动停止执行,并释放锁.

而**沉睡中停止,sleep() **方法就是依靠这个

Thread.sleep();
Thread.interrupt;

这样程序就会报错,自然程序就停止了.

5.同步

a.多线程问题的由来

在讲解之后的同步问题之前,我们还要讲解一下为什么要有同步的方法的存在.

出现这种问题的原因就在于多个线程使用同一个资源.

这种对同一个资源的操作容易导致了问题.

如果一个资源没用这种共享发生,就不会有问题.

例子如下:

package TEST;

public class HOMEWORK1 {
    public static void main(String[] args) throws InterruptedException {

        Runnable home = new home();
        System.out.println("线程不共享资源");
        home1 home1 = new home1();
        home1 home2 = new home1();
        home1.start();
        home2.start();
        Thread.sleep(1000);
        System.out.println("线程共享资源");
        Thread thread = new Thread(home,"一号线程");
        Thread thread1 = new Thread(home,"二号线程");
        Thread thread3 = new Thread(home,"三号线程");
        thread.start();
        thread1.start();
        thread3.start();
    }
}
class home extends Thread {
    private int count=5;
    public void run() {
        count--;//共享资源每次线程都会减1,值会变化(而且还不是正确的变化,这就是所谓的脏读)
        System.out.println(Thread.currentThread()+"    的值为"+count);
    }
}
class home1 extends Thread {
    private int count=5;
    public void run() {
        count--;//不共享资源.每个都是独立的值不会变化
        System.out.println(Thread.currentThread()+"    的值为"+count);
    }
}

输出的值

Thread[Thread-1,5,main]    的值为4
Thread[Thread-2,5,main]    的值为4
线程共享资源
Thread[三号线程,5,main]    的值为3
Thread[二号线程,5,main]    的值为2
Thread[一号线程,5,main]    的值为3

其实从这个就可以看出来,到底是什么情况了.按照我们的想法count被执行递减.即4,3,2.

可是问题来了,并没有这样发生--这就是共享资源的多线程导致的问题,叫做脏读,同一时间中多个线程的操作出现了问题.

解决办法

要想解决这个问题,有两种主要的办法.

SynchronizedReentrant类两种方式.

Synchronized方法

这种方法实际上就是将加上它的方法,变成一个临界区,类似于打印机,只能有一个人同时使用打印机,而其他人在这个时候只能等待.

也就说如果一个方法加上了Synchronized方法,那么其他线程就只能排队运行性这个方法.一个线程遇见这个关键字的时候就会试图去获取这个锁,可是如果获取失败,他会等待,并且不断地尝试直到成功.如果有多个线程,那么就会发生争抢.

将上面代码home类更改一下:

class home extends Thread {
    private int count=5;
    synchronized public void  run() {//加入了同步关键字
        count--;
        System.out.println(Thread.currentThread()+"    的值为"+count);
    }
}

我们发现这回输出的内容就正确了

Thread[一号线程,5,main]    的值为4
Thread[三号线程,5,main]    的值为3
Thread[二号线程,5,main]    的值为2

这里有个要注意的事情就是synchronized方法必须用在有共享的对象上.他必须被很多个线程一同使用,才有加入同步的意义,要不然加入同步就是行为艺术,没什么大用.

而且synchronized对象并不会影响对于非synchronized方法的调用.

对于这个例子:

例子  1-1
class method{
    private int count=5;
    public void str(){
        System.out.println(Thread.currentThread()+"执行");
    }
    synchronized public void gong(){
        //死循环
        while(count==5){
//            System.out.println("chenggong");
        }
    }
}

当一个线程调用gong()方法的时候,他永远也无法退出.但是另一个线程调用str()方法却依然可以执行.

package TEST;

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        method method = new method();
        thread thread = new thread("一号线程",method);
        thread.start();
        Thread.sleep(1000);
        thread2 thread2 = new thread2("二号线程",method);
        thread2.start();
    }
}
class thread extends Thread{
  private   method method;
    public thread(String name,  method method){
        super(name);
        this.method=method;
    }
    @Override
    public void run() {
        super.run();
        method.str();
        method.gong();
    }
}
class thread2 extends Thread{
    private method method;
    public thread2(String name,  method method){
        super(name);
        this.method=method;
    }
    @Override
    public void run() {
        super.run();
        method.str();
        method.gong();
//        method.str();
    }
}

输出结果如下:

Thread[一号线程,5,main]执行5
Thread[二号线程,5,main]执行5

实际上加上synchronized关键字的方法并不是单纯的锁上了方法,而是对对象的锁.

像一个场景:

共享的类中有两个同步方法,一个写方法一个读方法.

线程1调用了写方法.而同时线程2调用了读方法,那么这最终的结果肯定不是我们想要的.

所以我们要调用

怎么解释这个问题呢

那就要更改一下方法了

class method{
    private int count=5;
    public void str(){
        count--;
        System.out.println(Thread.currentThread()+"执行"+count);//如果另一个线程调用count--会让死循环停止,可是这一切并没有发生.死循环依然存在.这可能就是证明了什么叫给对象加锁了吧.
    }
     public void gong(){
        while(count==5){
        }
    }
}

还有一点要注意就是如果出现异常,自动放弃锁.即一个线程调用方法运行导致抛出了错误,那这个锁会自动被释放.

为了证明这一点,我还是换一下方法

class method{
    private int count=5;
    public void str(){
//        count--;
        System.out.println(Thread.currentThread()+"执行"+count);
    }
     public void gong() throws Exception {
        int i=0;
        while(count==5){
          i++;
            if(i==10){//如果i==10的时候会抛出一个异常
                throw new Exception("infiniteLoop StopsExecution!");
            }
        }
    }
}

返回的结果是

java.lang.Exception: infiniteLoop StopsExecution!
	at TEST.method.gong(test2.java:59)
	at TEST.thread.run(test2.java:24)
Thread[二号线程,5,main]执行5
java.lang.Exception: infiniteLoop StopsExecution!
	at TEST.method.gong(test2.java:59)
	at TEST.thread2.run(test2.java:41)

正常情况下(例子1-1的情况)第二个线程是无法执行的,但是现在抛出两个错误.说明第二个线程执行了.那么这个问题也很好的说明了,出现异常,自动放弃锁..

有一下性质要记住:

  • 同步不具有继承性
  • 出现异常锁自动释放
  • 在日常操作中单纯给写方法加入同步并不会让最终的值没有问题,实际上如果读方法不加锁,最重的结果还是一样的

synchronized同步语句块

如果一个方法中操作过于复杂,但是用到同步的部分又少之又少,那么我们就可以采用同步语句块技术.

以前是将整个方法都给封锁住,现在可以只封锁一个方法中的一部分,可以说很好的节省了时间.

做一个小栗子:

class method{
    private int count=0;
    public void method(){
        for (int i = 0; i <3 ; i++) {//每个线程的第一段0-2数字不一定是连续的
            System.out.println(Thread.currentThread().getName()+"   "+i);
        }
        synchronized (this){//每个线程的第二段0-2数字一定是连续的
            for (int i = 0; i <3; i++) {
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
        }
    }
}

输出结果

线程A   0  <--线程A 第一段0-2开始
线程B   0  <--线程B 第一段0-2开始
线程B   1
线程B   2
线程B   0  <---线程B 第二段0-2开始
线程B   1
线程B   2  <---线程B 第二段0-2结束
线程A   1   <--线程A 第一段0-2结束
线程A   2
线程A   0    <--线程A 第二段0-2开始
线程A   1
线程A   2   <--线程A 第二段0-2结束

我们发现第二段永远时连在一起的,但是第一段却是交叉打印的.

这也就是说:当一个线程访问object对象的synchronized代码块的时候,另一个线程依然可以访问非synchronized代码块.

这也意味着synchronized代码块有了更小的封锁位置,也意味着更高的效率.

当然我们举得例子使用的this其他的对象其实也可以但是千万要记得,要使用一个对象才好使,要不然就没有同步效果了.

例子:

class method{
    private int count=0;
    public void method(){
        String string=new String();
        for (int i = 0; i <3 ; i++) {
            System.out.println(Thread.currentThread().getName()+"   "+i);
        }
        synchronized (string){//修改的地方.每个调用的线程实际上都是用一个全新的对象,因为每当调用的时候才会产生一个全新的string对象
            for (int i = 0; i <3; i++) {
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
        }
    }
}

结果如下

线程A   0
线程A   1
线程A   2
线程A   0
线程B   0
线程B   1
线程A   1
线程B   2
线程B   0
线程B   1
线程B   2
线程A   2

我们可以发现,这里完全是混乱的,意味着并没有实现同步.

同步的特例

静态同步synchronized方法与synchronized(class)

这两种方式分别是个static方法加入一个同步关键词以及让同步代码块中放入一个.class

这些方式实际上是对类加锁,所有的对象都将排队执行.

例子如下:

class method{
    private int count=0;
    public void method(){
        String string=new String();
        for (int i = 0; i <3 ; i++) {
            System.out.println(Thread.currentThread().getName()+"   "+i);
        }
        synchronized (method.class){//给整个对象加锁
            for (int i = 0; i <3; i++) {
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
        }
    }
}
//测试类也有了变化
public class test3 {
    public static void main(String[] args) {
        method method = new method();
        method method1=new method();
        thread1 thread1 = new thread1("线程A",method);//传入了不同的method对象
        thread3 thread3 = new thread3("线程B",method1);//传入了不同的method对象
        thread1.start();
        thread3.start();
    }
}

记不记得我们刚开始讲的,如果传入不同的对象,结果会是两个线程交替执行.

结果如下.你会发现他们是同步的,同步的令人惊压.这其实就是锁住了整个类的结果.所有的对象都会被视作同一个对象(在同步代码块眼中),他们会同步进行.

线程A   0
线程A   1
线程A   2
线程B   0
线程B   1
线程B   2

ReentrantLock类

这个类是新增的一个类,其本质来讲似乎和synchronized方法是一样的.调用lock的线程自动持有对象监视器,其他线程只有等待被锁释放时再次争抢.使用和效果synchronized关键字一样,几乎没有什么区别.

这个方法也是锁住整个对象中所有使用了锁的部分.

小栗子:


package Thread.ConditionTestMoreMethod;

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

public class MyService {
    private Lock lock = new ReentrantLock();//声明锁对象

    public void methodA() throws InterruptedException {
        lock.lock();//所住
        System.out.println("MethodA    :" + Thread.currentThread().getName() + "time=" + System.currentTimeMillis() + "start");
        Thread.sleep(5000);//停止一段时间看看会不会被劫走
        System.out.println("MethodA    :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "end");
        lock.unlock();//解除锁定
    }

    public void methodB() throws InterruptedException {
        lock.lock();
        System.out.println("MethodA    :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "start");
        Thread.sleep(5000);
        System.out.println("MethodB    :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "end");
        lock.unlock();
    }
}

class ThreadA extends Thread {
    private MyService service;

    public ThreadA(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.methodA();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadAA extends Thread {
    private MyService service;

    public ThreadAA(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.methodA();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread {
    private MyService service;

    public ThreadB(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.methodB();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadBB extends Thread {
    private MyService service;

    public ThreadBB(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.methodB();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Test {
    public static void main(String[] args) {
        MyService myService = new MyService();
        ThreadA threadA = new ThreadA(myService);
        threadA.setName("A");//这是Thread自带的方法
        threadA.start();
        ThreadAA threadAA = new ThreadAA(myService);
        threadAA.setName("AA");
        threadAA.start();
        ThreadB threadB = new ThreadB(myService);
        threadB.setName("B");
        threadB.start();
        ThreadBB threadBB = new ThreadBB(myService);
        threadBB.setName("BB");
        threadBB.start();
    }
}

结果

MethodA    :A time=1589378808963start
MethodA    :A time1589378808963end
MethodA    :AA time=1589378808964start
MethodA    :AA time1589378808964end
MethodA    :B time1589378808965start
MethodB    :B time1589378808965end
MethodA    :BB time1589378808966start
MethodB    :BB time1589378808966end

这是一个对象锁,就是将整个对象全部锁住.就如同上面的结果一样.在访问一个线程MethodA的时候其他线程是连B都碰不了的.即使MethodA和MethodB毫无关联.


在lock对象中也存在notify()wait()这种方法,只不过使用Condition对象来调用的.Condition提供的方法最主要的好处在于他提供了一种叫做选择性通知的方式.这个功能是很重要的.像是notify()wait()这种方式是由Object提供的.而对他们的操作也是由JVM随机操作的.要想唤醒其他线程就必须将所有的剩余线程都唤醒.无疑这将导致严重的效率问题.

你可能对此有些疑惑,但是不必担心,请在下面的例子中仔细注意Condition对象的数量.

为此我们准备了一个小例子:

package Thread.MustUseConditon;

import Thread.ConditionTestMoreMethod.MyService;

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

public class MustUseCondition {
    Lock lock = new ReentrantLock();//创建锁
    public Condition a = lock.newCondition();//每个条件对象发出的指令只跟自己这个对象的有关系.
    public Condition b = lock.newCondition();//而wait( 这种方法则是对所有都要上锁.所以也正是因此

    public void awaitA() throws InterruptedException {
        lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
        System.out.println("begin await时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
        a.await();//等待,看看什么时候回复.
        System.out.println("end await时间为" + System.currentTimeMillis() + "              " + Thread.currentThread().getName());
        lock.unlock();//千万记得解锁,要不然就死锁了
    }

    public void awaitb() throws InterruptedException {
        lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
        System.out.println("begin awaitB时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
        b.await();//等待,看看什么时候回复.
        System.out.println("end awaitB时间为" + System.currentTimeMillis() + "              " + Thread.currentThread().getName());
        lock.unlock();//千万记得解锁,要不然就死锁了
    }

    public void signallA() throws InterruptedException {
        lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
        System.out.println("signall时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
        a.signalAll();//释放全部锁
        lock.unlock();
    }
//下面的方法并不会被调用,实际上一直到结束,这个都不会被调用.
    public void signallB() throws InterruptedException {
        lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
        System.out.println("signall时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
        b.signalAll();//释放全部锁
        lock.unlock();
    }
}

class ThreadA extends Thread {
    private MustUseCondition service;

    public ThreadA(MustUseCondition service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.awaitA();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread {
    private MustUseCondition service;

    public ThreadB(MustUseCondition service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.awaitb();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class test {
    public static void main(String[] args) throws InterruptedException {
        MustUseCondition mustUseCondition = new MustUseCondition();
        ThreadA threadA = new ThreadA(mustUseCondition);
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB(mustUseCondition);
        threadB.setName("B");
        threadB.start();
        Thread.sleep(3000);//确保A和B都停止
        mustUseCondition.signallA();
    }
}

可能并不小,但是我们可以用比较简单的语言来解释这个程序所要表达的意思.

Synchronized方法中,如果使用wait()方法,这个线程会等待.要想再启动,必须等待signal()方法.跟所有的线程一同启动.

但是实际上很多线程是没有必要启动的.condition对象为我们提供了一个新的方式,我们这回可以声明不同的condition对象,让他们为我们有等待需求的condition对象进行分类,让有关联的使用同一个condition对象(调用await()方法).

这样就可以大大的增加效率.


生产者消费者模式

生产者消费者模式,实际上就是一个类中的两个不同的方法有这一个公用的信号量.经过设计,这个信号量可以锁住所有使用本方法的线程.只有调用另一个方法(一共两个方法)才能解锁.

package Thread.ConditionTest;

import java.util.concurrent.locks.*;

public class ConditionTest {
    private ReentrantLock lock1 = new ReentrantLock();
    private Condition condition = lock1.newCondition();
    private boolean hasValue = false;
  //生产者
    public void set() throws InterruptedException {
        lock1.lock();//先锁住,这样所有访问这个线程的内容就全都卡住了.
        while (hasValue) {
            condition.await();
        }//用循环才能卡住线程,要不然一会一格Signall又全起来了
        hasValue = true;
        System.out.println("打印********");
        condition.signal();
        lock1.unlock();
    }
//消费者
    public void get() throws InterruptedException {
        lock1.lock();//先锁住
        while (!hasValue) {
            condition.await();
        }//用循环才能卡住线程,要不然一会一格Signall又全起来了
        hasValue = false;
        System.out.println("打印!!!!!!!!!!!");
        condition.signal();
        lock1.unlock();
    }
}
//测试部分
class ThreadA extends Thread {
    private ConditionTest service;

    public ThreadA(ConditionTest service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 4; i++) {
                service.get();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread {
    private ConditionTest service;

    public ThreadB(ConditionTest service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 4; i++) {
                service.set();//开4个线程
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class test {
    public static void main(String[] args) {
        ConditionTest conditionTest = new ConditionTest();
        ThreadA threadA = new ThreadA(conditionTest);
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB(conditionTest);
        threadB.start();
    }
}

打印********
打印!!!!!!!!!!!
打印********
打印!!!!!!!!!!!
打印********
打印!!!!!!!!!!!
打印********
打印!!!!!!!!!!!

结果可以很清楚的看到,内容是交替打印的,但是我们一次开启了8个线程也就是说.有相当一部分线程会发生阻塞(这是用到了线程的随机性).这就是生产者消费者模型的问题.


公平锁非公平锁

公平锁:表示线程获取所得顺序是按照线程加锁的顺序来分配的,即先来先得到的FIFO先进先出顺序.而非公平锁:就是一种获取锁的抢先机制,是随机获得锁的,和非公平锁不一样的是先来不一定先得到.

(这个不要跟线程的随机性搞混.这个是在线程的随机性基础之上的行为.而非导致随机性的原因)

这里就只讲一下使用的方法

lock=new ReentrantLock(isFair);//公平锁,打印出来的内容不是乱序
lock=new ReentrantLock(false);//非公平锁.打印出来的内容会是乱序

一些特殊的方法

int getHoldCount()的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数.

我们将生产者消费者的代码进行一点小的修改

public class ConditionTest {
    private ReentrantLock lock1 = new ReentrantLock();
    private Condition condition = lock1.newCondition();
    private boolean hasValue = false;

    public void set() throws InterruptedException {
        lock1.lock();.
        while (hasValue) {
            condition.await();
        }
        hasValue = true;
        System.out.println("set调用了几次lock"+lock1.getHoldCount());//这里修改了
        condition.signal();
        get();
        lock1.unlock();
    }

    public void get() throws InterruptedException {
        lock1.lock();
        while (!hasValue) {
            condition.await();
        }
        hasValue = false;
        System.out.println("get锁住了多少个"+lock1.getHoldCount());//这里修改了
        condition.signal();
        lock1.unlock();
    }
}

结果如下

set调用了几次lock1
get锁住了多少个2
set调用了几次lock1
get锁住了多少个2
set调用了几次lock1
get锁住了多少个2
set调用了几次lock1
get锁住了多少个2
这个代码中所有对于调用get的线程都被锁住了.

int getQueueLength()返回正等待获取此锁定的线程估计数,比如5个线程,1一个线程执行,那么调用方法后返回4.

为了方便,我们更改了一些生产者消费者的代码.并且只运行这个代码.

public void set() throws InterruptedException {
        lock1.lock();//先锁住,这样所有访问这个线程的内容就全都卡住了.
//        while (hasValue) {
//            condition.await();
//        }//用循环才能卡住线程,要不然一会一格Signall又全起来了
        hasValue = true;
        Thread.sleep(1000);
        System.out.println("set调用了几次lock"+lock1.getQueueLength());
        condition.signal();
        get();
        lock1.unlock();
    }
   public static void main(String[] args) {
        ConditionTest conditionTest = new ConditionTest();
        for (int i = 0; i < 4; i++) {
            ThreadA threadA = new ThreadA(conditionTest);
            threadA.start();
        }
//如下代码将开启4个再run中调用set方法的线程.
set调用了几次lock3
set调用了几次lock3
set调用了几次lock3
set调用了几次lock3
set调用了几次lock2
set调用了几次lock2

这是一部分结果,我们可以看到这个基本陈工.

原文地址:https://www.cnblogs.com/yanzezhong/p/12887896.html