Java多线程基础
麦兜 / 2020-02-24 / Java / 阅读量 178

进程与线程

  • 进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
  • 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
  • 每个程序都是一个进程,一个进程里面可以包含多个线程。(比如你在听歌的时候是一个线程,下载音乐又是另一个线程,它们不会互相干扰),线程之间可以共享进程的资源。

进程.png

Java多线程

  • Java虚拟机允许应用程序同时执行多个执行线程。

1、继承Thread

Thread常用的方法

Modifier and TypeMethodDescription
voidstart()导致此线程开始执行; Java虚拟机调用此线程的run方法。
static voidsleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 会占用线程的睡眠
voidjoin()等待这个线程死亡。
static voidyield()对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。
voidrun()如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
更多方法请查阅官方API文档

继承Thread需要重写它的run 方法,调用时用start方法才能启动线程。

代码:

public class ThreadTest {

  public static void main(String[] args) {
      count count1=new count();
      count count2=new count();
      count1.start();
      count2.start();
  }
}


class count extends Thread
{
    @Override
    public void run() {
        for(int i=0;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

部分-返回结果:

Thread-0:64
Thread-0:65
Thread-0:66
Thread-0:67
Thread-0:68
Thread-0:69
Thread-0:70
Thread-0:71
Thread-1:3
Thread-1:4
Thread-0:72
Thread-1:5
Thread-0:73
Thread-1:6
Thread-0:74
Thread-1:7
Thread-0:75

线程0和线程1互相交叉运行互不阻塞。

查看线程状态

程序路径:%JAVA_HOME%/bin/jvisualvm.exe

jvisualvm1.png
jvisualvm2.png

2、实现Runnable接口

其实Thread也是实现了Runnable接口,所以只需要把run方法重写就行了 让Thread去调用。只不过Thread需要继承而Runnable是接口。(好处自己回顾一下面向对象知识)

Thread.png

代码:

public class RunnableTest {
  public static void main(String[] args) {
      count count1=new count();
      count count2=new count();
      new Thread(count1).start();
      new Thread(count2).start();
  }
}


class count implements Runnable
{
    @Override
    public void run() {
        for(int i=0;i<100;i++)
        {
            try {
                Thread.sleep(100);//延时100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

部分-返回结果:

Thread-0:26
Thread-1:27
Thread-0:27
Thread-1:28
Thread-0:28
Thread-1:29
Thread-0:29
Thread-1:30
Thread-0:30
Thread-1:31
Thread-0:31
Thread-1:32
Thread-0:32
Thread-1:33
Thread-0:33
Thread-1:34
Thread-0:34

线程0和线程1互相交叉运行互不阻塞。

多线程的锁

给我一把锁,我能创造出一种规则。

线程异步会造成不安全,比如下方图 value两次++ 值应该改变为12。

原因:线程1 读取时 value还是10,因为计算机调度快 线程2也马上可以读取value等于10 。
线程异步.png

public class countVlaue {

  public static void main(String[] args) {
      Test Test=new Test();

      new Thread(Test).start(); //线程1
      new Thread(Test).start();//线程2
  }
}
class  Test implements Runnable{
    int vlaue=10;

    @Override
    public void run() {
        try {
            Thread.sleep(3000); //为了线程同时读到值
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        vlaue++;
        System.out.println(vlaue);
    }
}

输出结果:

11
11

Synchronized 的解析

在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充;Java对象头是实现synchronized的锁对象的基础,一般而言,synchronized使用的锁对象是存储在Java对象头里。

实现原理: JVM 是通过进入、退出 对象监视器(Monitor) 来实现对方法、同步块的同步的,而对象监视器的本质依赖于底层操作系统的 互斥锁(Mutex Lock) 实现。

Synchronized 的使用

  • Synchronized 用于线程同步,避免线程共享资源的异常。
  • Synchronized 可用于方法锁、代码块锁、类锁。
  • Synchronized 默认为Synchronized (this) 参数为对象,静态类锁 参数为Synchronized(类.class)

    public class countVlaue {
    
      public static void main(String[] args) {
          Test Test=new Test();
    
          new Thread(Test).start(); //线程1
          new Thread(Test).start();//线程2
      }
    }
    class  Test implements Runnable{
        int vlaue=10;
    
        @Override
        public synchronized void run() { //添加synchronized关键字 
            try {
                Thread.sleep(3000); //为了线程同时读到值
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            vlaue++;
            System.out.println(vlaue);
        }
    }

输入结果:

11
12

ReentrantLock的使用

主要的方法

Modifier and TypeMethodDescription
voidlock()获得锁。
voidunlock()尝试释放此锁。
更多方法请查阅官方API文档
import java.util.concurrent.locks.ReentrantLock;

public class countVlaue {

  public static void main(String[] args) {
      Test Test=new Test();

      new Thread(Test).start(); //线程1
      new Thread(Test).start();//线程2
  }
}
class  Test implements Runnable{
    int vlaue=10;
    ReentrantLock lock=new ReentrantLock();
    @Override
    public  void run() {

        try {
            lock.lock(); //建议放在try里面
            vlaue++;
            System.out.println(vlaue);
            Thread.sleep(3000); //为了线程同时读到值
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock(); //解锁放在finally
        }

    }
}

输入结果:

11
12

死锁:两个线程互相占用对方需要的锁,会造成卡顿现象。

线程池

好处:提高响应速度、降低资源消耗、便于管理。

相关API:ExecutorServiceExecutors

消费者与生产者模式

业务需求

背景:消费者到肯德基店购买鸡,但是肯德基不能现杀鸡需要准备货物才能运营给消费者消费,所以消费者就必须等待并告诉肯德基需要准备货物。

肯德基是供应商,如果只生产鸡没有去宣传告诉消费者那永远运营不起来。

(鸡不可能 同时生产 同时消费)

业务图1.png

方法

Modifier and TypeMethodDescription
voidwait()释放锁并阻塞但并不会持有锁
voidnotify()唤醒某一条线程 未必唤醒成功
voidnotifyAll()唤醒全部线程

代码

public class PC{
  public static void main(String[] args) {
    Cache Cache=new Cache(); //同一个缓存区
    new Producer(Cache).start();
    new Consumer(Cache).start();
  }
}

//生产者
class Producer extends Thread {

  Cache cache;
  public Producer(Cache cache) {
    this.cache = cache;
  }

  @Override
  public void run() {

    for (int i = 0; i < 15; i++) {
      cache.push(i);
      System.out.println("生产第" + i + "只");
    }
  }
}


//消费者
class Consumer extends Thread {

  Cache cache;

  public Consumer(Cache cache) {

    this.cache = cache;
  }
  @Override
  public void run() {
    for (int i = 0; i < 15; i++) {
      System.out.println("消费第" + cache.pop().id + "只");
    }
  }
}

//共同消费的产品:鸡
class chicken {
  int id;

  public chicken(int id) {
    this.id = id;
  }
}

//缓冲区
class Cache {
  private chicken[] chickens = new chicken[10];
  public static int count = 0;

  //生产
  public synchronized void push(int id) {
    if (count == chickens.length) { //生产仓库满了需要等待消费者消费
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    //否则就生产鸡 并同时告诉消费者
    chicken chicken = new chicken(id);
    chickens[count++] = chicken;
    this.notifyAll();
  }

  //消费
  public synchronized chicken pop() {
    chicken chicken = null;
    if (count == 0) { //仓库没有鸡 消费者就必须等待
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    //消费者消费了 需要告诉生产者继续生产
    count--;
    chicken = chickens[count];
    this.notifyAll();
    return chicken;
  }
}

输出结果

生产第0只
生产第1只
生产第2只
生产第3只
生产第4只
生产第5只
生产第6只
生产第7只
生产第8只
生产第9只
生产第10只
消费第9只
消费第10只
消费第8只
消费第11只
消费第7只
消费第6只
消费第5只
消费第4只
消费第3只
消费第2只
消费第1只
消费第0只
生产第11只
生产第12只
生产第13只
生产第14只
消费第14只
消费第13只
消费第12只
1 + 4 =
快来做第一个评论的人吧~