线程 ***

线程 ***

1. 相关概念

  • 程序

  • 进程

  • 线程

  • package com.qf.thread;

    import java.util.Scanner;

    /**
    * 1: 相关的概念
    * 1:程序:静态的概念,计算机指令的集合。
    * program
    * 2:进程:
    * process
    * 运行中的程序就是进程。
    * 程序的一次执行就称为进程。
    * 特点:
    * 1:多个进程可以并发执行。(看上去是并发执行,但是如果是单cpu,单核,那么所有的进程是串行)。
    * 2:系统会为每个进程分配对应的资源使用,而且进程之间的资源是不共享的。
    * 【进程是系统进行资源分配的最小单位】
    *     3:进程创建和销毁对系统资源的消耗是比较大的。
    *
    * 3: 线程
    * thread
    * 1:进程中的一条任务线。进程中的一条独立的执行路径。
    * 2:线程不能独立存在,必须以进程为载体。在进程中存活。
    * 3:线程负责将进程中的代码交给cpu去执行。
    * 4:线程是可以并发执行。一个进程中可以包含多个线程。
    * 5:一个进程中至少要有一个线程,如果一个进程中没有任何的线程,那么 os会杀死进程。
    * 6:进程是任务的载体,线程是进程中负责执行任务的。
    * 7: 进程中的第一个线程,称为主线程。java程序中的主线程:main。
    * 8:进程中的所有的线程是共享所在的进程的资源的。
    * 9:【线程是cpu进行调度执行任务的最小单位。】
    * 进程中的线程抢夺cpu的控制权来执行线程的任务。cpu将自己的划分为非常微小的时间片给不同的进程中的线程来使用。
    *
    * 4:进程的线程关系
    * 1:进程是线程的载体。进程负责给线程提供资源。线程负责执行进程中的任务。
    * 2:进程中至少要有一个存活的线程。一个进程中的线程的个数没有限制。
    * 3:进程可以在任务中创建新的线程,也可以杀死进程中的线程。
    * 4:如果将进程杀死,进程中的所有的线程都会被杀死。
    * 5:java 程序中的所有的需要执行的代码都是在某一个线程中被执行的。
    *
    * java 程序中的主线程是main线程。main线程的主体任务就是 main方法中的所有的代码。
    *
    * 5:单线程程序的优缺点:
    * 优点:消耗的系统资源少。代码简单。
    * 缺点:一旦线程被阻塞了,那么整个程序就暂停了。
    *
    * 6:多线程程序的优缺点:
    * 优点:可以充分的利用进程的资源,一个线程被阻塞了,其他的线程不受任何的影响。
    * 缺点:会消耗更多的系统资源。
    *
    */
    public class Test {

    public static void main(String[] args) {
    test1();
    test2();
    }

    private static void test1() {
    while(true){
    System.out.println("1");
    }
    }

    private static void test2() {
    while(true){
    System.out.println("2");
    }
    }
    }

2. 线程的创建的方式

  • 继承 java.lang.Thread

  • 实现 java.lang.Runnable

3. 案例

  • 售卖火车票的线程的两种实现

  • package com.qf.thread;

    /**
    * java 中创建线程的方式:
    * 1:继承 java.lang.Thread
    * 重写 run 方法。
    * 在run 方法中指定线程对象的任务代码。
    * 创建 Thread 子类对象,然后 通过调用 start 方法,启动线程。
    *
    */
    /**
    *
    * 模拟火车站卖票。
    */
    public class TestSaler {

    public static void main(String[] args) {
    TicketSystem system = new TicketSystem();
    // 创建了5个窗口,5个线程。
    Saler saler1 = new Saler("窗口-1",system);
    Saler saler2 = new Saler("窗口-2",system);
    Saler saler3 = new Saler("窗口-3",system);
    Saler saler4 = new Saler("窗口-4",system);
    Saler saler5 = new Saler("窗口-5",system);

    // 启动线程,开始卖票
    saler1.start();
    saler2.start();
    saler3.start();
    saler4.start();
    saler5.start();
    }
    }

    // 票务系统。
    class TicketSystem {
    // 初始票的个数。
    private int ticketCount = 100;

    // 每调用一次,代表出票一张。
    public boolean sale() {
    // 让线程休息一下。
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    // 如果有余票
    if (ticketCount > 0) {
    ticketCount--;
    // 输出卖票的信息。
    System.out.println(Thread.currentThread().getName() + "\t卖了一张票,剩余:" + ticketCount + "\t张");
    return true;
    }
    System.out.println(Thread.currentThread().getName() + "\t出票失败了");
    return false;

    }
    }

    class Saler extends Thread {
    // 持有唯一的票务系统对象的引用。
    private TicketSystem system;

    public Saler(String name,TicketSystem system) {
    super(name);
    this.system = system;
    }

    // 必须只有一个票务系统。
    // static TicketSystem system = new TicketSystem();

    // 重写 run 方法,在run 中规定线程的任务。
    @Override
    public void run() {
    while (true) {
    boolean result = system.sale();
    if (!result)
    return;
    }
    }
    }
  • package com.qf.thread;
    /**
    * 线程的第二种实现方式:
    * 实现 java.lang.Runnable 接口。
    * Runnable 接口的子类是用来定义任务的。
    *
    * 1: 定义任务类,实现Runnable 接口,
    * 2:在子类的实现 run 方法中,添加线程的任务代码。
    * 3:创建任务对象。将任务对象作为实参交给 Thread 对象。
    * 4:启动 Thread 对象。start();
    *
    * 总结:
    * 1:继承Thread 类
    * 优点:代码实现比较简单。
    * 缺点:继承了线程类,就不能继承其他的类。
    * 2:实现 Runnable 接口
    * 优点:还可以继承其他的类。
    * 缺点:代码实现稍微复杂。
    * 3:实现 Runnable 接口 共享数据更简单。继承Thread 类共享数据稍微复杂。
    */
    public class TestRunnable {

    public static void main(String[] args) {
    // 创建任务对象。
    TicketSystemRunnable task = new TicketSystemRunnable();

    // 将任务交给线程对象。
    Thread saler1 = new Thread(task,"窗口-1");
    Thread saler2 = new Thread(task,"窗口-2");
    Thread saler3 = new Thread(task,"窗口-3");
    Thread saler4 = new Thread(task,"窗口-4");
    Thread saler5 = new Thread(task,"窗口-5");

    // 启动线程,执行任务。
    saler1.start();
    saler2.start();
    saler3.start();
    saler4.start();
    saler5.start();
    }
    }


    //任务类。
    class TicketSystemRunnable implements Runnable {
    // 初始票的个数。
    private int ticketCount = 100;

    // 每调用一次,代表出票一张。
    public boolean sale() {
    // 让线程休息一下。
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    // 如果有余票
    if (ticketCount > 0) {
    ticketCount--;
    // 输出卖票的信息。
    System.out.println(Thread.currentThread().getName() + "\t卖了一张票,剩余:" + ticketCount + "\t张");
    return true;
    }
    System.out.println(Thread.currentThread().getName() + "\t出票失败了");
    return false;

    }
    @Override
    public void run() {
    while(true){
    boolean result = sale();
    if(!result)return;
    }
    }
    }
  •  

4. 线程的状态(基本)

  • 新建状态(初始状态)

  • 就绪状态(可运行状态)

  • 运行状态

  • 死亡状态(终止状态)

5. 线程常用方法

  • 线程的优先级设置

  • package com.qf.thread;
    /**
    * 线程的优先级相关的实例方法:
    * setPriority(int value)
    * 设置线程对象的优先级。
    * getPriority()
    * 获取线程对象的优先级。
       public final static int MIN_PRIORITY = 1;
       public final static int NORM_PRIORITY = 5;
       public final static int MAX_PRIORITY = 10;
       
       总结:通过设置线程的优先级,可以让优先级高的线程被cpu调度执行的概率大大的增加。优先级小的线程被cpu调度执行的概率大大的降低。
    *
    */
    public class TestPriority {

    public static void main(String[] args) {
    MyThread1 thread1 = new MyThread1();
    thread1.setName("张三");
    thread1.setPriority(Thread.MAX_PRIORITY);
    thread1.start();

    MyThread1 thread2 = new MyThread1();
    thread2.setName("李四");
    thread2.setPriority(Thread.MIN_PRIORITY);
    thread2.start();
    }
    }

    class MyThread1 extends Thread{
    private int i;
    @Override
    public void run() {
    while(true){
    System.out.println(getName() + "--"+i++);
    }
    }
    }
  •  

  • join

    • 先start 后join

    • package com.qf.thread;
      /**
      * 线程的实例方法
      * join()
      *
      * 作用:可以实现线程插队的效果,优先执行指定的线程。
      * 当一个线程对象调用了join方法。会导致当前线程(执行了xxx.join()代码的线程)从【运行状态】,进去【阻塞状态】
      * 当调用join()方法的线程对象 的任务执行完毕之后,被阻塞的线程才能解除阻塞进入 【就绪状态】
      * join 方法先调用start 然后调用join
      *
      * join(long millis)
      * 和无参的 join 的区别。解除阻塞线程的条件不同。多了一个解除阻塞的最长等待的时间。到达时间,自动解除阻塞。
      *
      */
      public class TestJoin {

      public static void main(String[] args) throws Exception{

      for (int i = 0; i < 10; i++) {
      if(i == 5){
      ChengThread cheng = new ChengThread();
      cheng.setName("程咬金");
      cheng.start();
      // 导致main线程被阻塞了。当 cheng 线程执行任务完毕。main线程解除阻塞。进入就绪状态。
      cheng.join();
      }
      System.out.println(Thread.currentThread().getName() + "-->" + i);
      }
      }
      }
      class ChengThread extends Thread{
      @Override
      public void run() {
      for (int i = 0; i < 10; i++) {
      System.out.println(Thread.currentThread().getName() + "-->" + i);
      }
      }
      }
  • yield

  • package com.qf.thread;
    /**
    * 线程类的静态方法:
    * Thread.yield();
    * 会导致当前线程(cpu正在调度执行的线程)从运行状态,进行就绪状态。
    */
    public class TestYield {

    public static void main(String[] args) {
    new MyThead2().start();
    new MyThead2().start();
    }

    }
    class MyThead2 extends Thread{
    @Override
    public void run() {
    for (int i = 1; i <= 10; i++) {
    System.out.println(Thread.currentThread().getName() + "-->" + i);
    Thread.yield();
    }
    }
    }
  • sleep

  • package com.qf.thread;

    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    /**
    * Thread.sleep(long millis);
    * 作用:会导致当前线程进入阻塞状态。让线程从运行状态进入阻塞状态(休眠)。从进入阻塞状态开始计时,
    * 到实参时间结束,线程从阻塞状态
    * 进入就绪状态。
    *
    */
    public class TestSleep {

    public static void main(String[] args) throws Exception {
    test1();
    }



    private static void test() throws Exception{
    System.out.println("Ready");
    Thread.sleep(1000);
    for (int i = 3; i >0; i--) {
    System.out.println(i);
    Thread.sleep(1000);
    }
    System.out.println("Go");
    }

    // 要求没隔一秒打印一次当前时间。xx:xx:xx
    private static void test1() {
    DateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    Date date = new Date();
    int counter = 0;
    while(true){
    long start = System.currentTimeMillis();
    date.setSeconds(date.getSeconds()+1);
    String str = sdf.format(date);
    System.out.println(str);
    long cost = System.currentTimeMillis() - start;
    try {
    Thread.sleep(1000-cost);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    counter++;
    // counter
    if(counter == 600){
    date = new Date();
    counter = 0;
    }

    }
    }
    }
  •  

  • 守护线程

    • 先设置后启动

    • main不能设置为守护线程

    • package com.qf.thread;

      /**
      * 线程分为两类:
      * 1:用户线程|前台线程
      * 2:守护线程|后台线程
      *
      * 一个线程创建出来,默认是用户线程。
      * 进程的生命周期:
      * 进程的生命周期由进程中存活的用户线程来决定。只要还有存活的用户线程,那么进程就是存活的。
      * 【如果一个进程中的所有的存活的线程只是守护线程了,那么jvm会杀死进程。】
      *
      * 守护线程的作用:
      * 1:守护线程一般是给用户线程提供服务的。
      * 2:GC 功能是一个单独的线程来管理的,这个线程优先级很低,而且还是一个守护线程。
      *
      * 注意:
      * 1:守护线程必须先设置,后启动。
      * thread.setDaemon(true);
      thread.start();
      2:main 线程不能设置为守护线程。
      *
      */
      public class TestDaemon {

      public static void main(String[] args) {
      // Thread.currentThread().setDaemon(true);

      DaemonThread thread = new DaemonThread();
      thread.setDaemon(true);
      thread.start();

      for (int i = 0; i < 10; i++) {
      try {
      Thread.sleep(200);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + "-->" + i);
      }
      }

      }
      class DaemonThread extends Thread{
      @Override
      public void run() {
      for (int i = 0; i < 50; i++) {
      try {
      Thread.sleep(500);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + "-->" + i);
      }
      }
      }
    •  

6. 线程同步

  • 线程安全例子

  • package com.qf.sync;

    public class TestSync {
    public static void main(String[] args) {
    WithdrawTask task = new WithdrawTask();

    Thread san = new Thread(task,"张三");
    Thread sanWife = new Thread(task,"张三媳妇");

    san.start();
    sanWife.start();
    }
    }

    package com.qf.sync;
    /**
    *
    *账户类。
    */
    public class Account {
    // 余额
    private int  balance = 1500;

    public boolean withdraw(int money){
    String name = Thread.currentThread().getName();
    // 同步代码块。
    synchronized (this) {
    if(balance >= money){
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    balance -= money;
    System.out.println(name + "\t取钱【成功】了,剩余余额:"+balance);
    return true;
    }
    }

    System.out.println(name + "\t取钱【失败】了,剩余余额:"+balance);
    return false;
    }
    }

    package com.qf.sync;

    public class WithdrawTask implements Runnable{
    private Account account;

    public WithdrawTask() {
    account = new Account();
    }

    @Override
    public void run() {
    account.withdraw(1000);
    }
    }
  • 线程同步的两种实现

  • 同步锁的选用

  • package com.qf.sync;
    /**
    * 线程安全问题:
    * 1:只有多线程的情形下才存在线程安全问题。
    * 多个线程访问同一个数据。就会存在线程安全问题。
    * 2:线程安全问题的解决方案:
    * 原理:对访问同一个数据的代码要实现线程之间的互斥访问。任何一个时间点只允许一个线程来访问,不能多个线程同时访问。
    * 解决方案:
    * 1:同步代码块
    * 语法:synchronized (同步锁) {需要互斥访问的代码 }
    * synchronized:
    * 1:java的关键字
    * 2:用于线程互斥访问某些代码。
    * 同步锁:
    * 1:也称为同步监视器对象。
    * 2:可以是任意对象。
    *
    * 同步代码块的执行过程:
    * 1:线程A 访问同步代码块,给同步锁添加锁标识,开始执行同步代码块中的内容。
    * 2:线程B 也要访问同步代码块,发现同步锁被其他的线程添加锁标识,在旁边等待。锁标识去掉。
    * 3:线程A,执行完同步代码块中的内容,要解锁,去掉锁标识。
    * 4:线程B,发现锁已经被解锁了,锁标识去掉了,那么就给锁添加锁标识,上锁,开始执行同步代码块中的内容。
    *
    * 同步代码块只是实现了方法中的局部代码实现了线程之间的互斥访问。
    *
    * 2:同步方法
    * 给方法添加同步语法。
    * 让多个线程实现互斥访问同步方法。
    * 语法:在方法的返回值类型前添加关键字 synchronized
    *
    * 同步方法的本质上还是同步代码块。
    * 同步方法的同步监视器对象
    * 1:如果同步的是实例方法,那么同步监视器是 this。
    * 2:如果同步的是静态方法,那么同步监视器是 当前类的 Class 对象。
    *
    *   3:同步监视器对象的选择:
    *   1:监视器对象必须具有唯一性。
    *   2:监视器对象具有不可改变的特点,不能指向其他的对象。
    *   可以使用静态的不变的对象
    *   1:final staitc Object o = new Object();
    *   2:最合适的监视器对象:当前类的Class 对象。
    *   已经创建好了,唯一不变。 【类名.class】
    *   该对象是 静态同步方法的 默认的监视器对象。
    *
    *
    */
    public class TestSync {
    public static void main(String[] args) {
    WithdrawTask task = new WithdrawTask();

    Thread san = new Thread(task,"张三");
    Thread sanWife = new Thread(task,"张三媳妇");

    san.start();
    sanWife.start();
    }
    }
  • 线程安全类

    • StringBuffer

    • Vector

    • Hashtable

7. 线程死锁(**)

package com.qf.thread;
/**
* 死锁:
* 死锁出现的条件:同步代码块的嵌套。
* 尽量避免出现同步代码块的嵌套。
*
* 死锁一旦出现,没有办法通过代码去解决死锁。
*/
public class DeadLockTest {

public static void main(String[] args) {
DeadLockThread thread1 = new DeadLockThread(0);
thread1.setName("A");
DeadLockThread thread2 = new DeadLockThread(1);
thread2.setName("B");

thread1.start();
thread2.start();
}

}

class DeadLockThread extends Thread{
public static final Object o1 = new Object();
public static final Object o2 = new Object();

private int id;

public DeadLockThread(int id) {
this.id = id;
}
@Override
public void run() {
if(id == 0){// A 线程
synchronized (o1) {
// 在这里停下来。
System.out.println(getName() + "锁住了o1请求锁o2");
synchronized (o2) {
System.out.println(getName() + "锁住了o1也锁了o2");
}
}
}else{//B
synchronized (o2) {
// 在这里停下来。
System.out.println(getName() + "锁住了o2请求锁o1");
synchronized (o1) {
System.out.println(getName() + "锁住了o2也锁了o1");
}
}
}
}
}

8. 线程通信

9. 生产和消费者

package com.qf.pc;

public class TestPc {

public static void main(String[] args) {
MyStack<Steam> stack = new MyStack<>();

Producer producer = new Producer(stack);
producer.setName("生产者");
Consumer consumer = new Consumer(stack);
consumer.setName("消费者-1");
Consumer consumer2 = new Consumer(stack);
consumer2.setName("消费者-2");

producer.start();
consumer.start();
consumer2.start();

}

}

package com.qf.pc;
/**
* 商品类。馒头
*
*/
public class Steam {
private int id;

public Steam(int id) {
super();
this.id = id;
}

@Override
public String toString() {
return "馒头:" + id;
}
}

package com.qf.pc;

public class Producer extends Thread{

private MyStack<Steam> stack;

public Producer(MyStack<Steam> stack) {
super();
this.stack = stack;
}


@Override
public void run() {
for (int i = 0; i < 6; i++) {
stack.push(new Steam(i+1));
}
}
}

package com.qf.pc;

import java.util.Arrays;

/**
* 自定义容器。栈。 底层使用数组作为栈的容器。
*/
public class MyStack<E> {
// 初始容量。
private final static int DEFAULT_SIZE = 3;
private final static int MAX_SIZE = DEFAULT_SIZE;

// 用于保存元素的数组。
private Object[] elementData;

// 定义栈顶指针。代表了要存入数据的索引,还代表了元素的个数。
private int index;

public MyStack() {
elementData = new Object[DEFAULT_SIZE];
index = 0;
}

// 压栈操作。
public boolean push(E e) {
synchronized (this) {
// 特殊情况 满了。
if (isFull()) {
// 让生产者线程在当前容器对象上等待。进入阻塞状态。并对 this 解锁。
try {
System.out.println(Thread.currentThread().getName() + "\t准备等待了!!");
this.wait();
System.out.println(Thread.currentThread().getName() + "\t被唤醒了");
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}

try {
Thread.sleep(100);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

// 常规情况
elementData[index] = e;
index++;

System.out.println(Thread.currentThread().getName() + "\t生产了商品:" + e);
// 生产者一旦生产了商品,那么就可以通知消费者继续消费。
this.notify();

return true;
}
}

// 获取栈顶元素。
public E pop() {
synchronized (this) {
// 特殊情况。
while (isEmpty()) {
// 让消费者线程在当前this 容器上等待。进入阻塞状态。并对 this 解锁。
try {
System.out.println(Thread.currentThread().getName() + "\t准备等待了!!");
this.wait();
System.out.println(Thread.currentThread().getName() + "\t被唤醒了");
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}

try {
Thread.sleep(100);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

// 常规情况
index--;
E e = (E) elementData[index];
// 避免内存泄漏。
elementData[index] = null;

System.out.println(Thread.currentThread().getName() + "\t消费了商品:" + e);

// 消费者线程消费了商品,就可以通知生产者继续生产。
// 会唤醒一个在当前对象上等待的其他的线程(只能是生产者线程)。
this.notify();

return e;
}
}

// 栈是否满了,不能扩容了。
private boolean isFull() {
return index == MAX_SIZE;
}

// 判断栈是否为空。
public boolean isEmpty() {
return index == 0;
}

public void clear() {
// 清空所有的元素,并指向栈底
for (int i = 0; i < index; i++) {
elementData[i] = null;
}
index = 0;
}

// 获取元素个数。
public int size() {
return index;
}

public String toString() {
// 将elementData 中有效数据复制到一个新数组中,返回。
return Arrays.toString(Arrays.copyOf(elementData, index));
}
}

package com.qf.pc;

public class Consumer extends Thread{
private MyStack<Steam> stack;

public Consumer(MyStack<Steam> stack) {
super();
this.stack = stack;
}


@Override
public void run() {
for (int i = 0; i < 3; i++) {
stack.pop();
}
}
}

10 线程状态(阻塞)

  • 三种阻塞分类

    • 等待阻塞 wait-->等待池

    • 同步阻塞 请求资源 锁池

    • 其他的阻塞:sleep、join、IO等。

  • 一道面试题

  • 重入锁

11. 线程池 ***

  • 相关的接口和类 java.util.concurrent

  • 顶层接口- Executor

    • 规范,只包含一个方法,athor Doug Lea

  • ExecutorService:线程接口

    • submit(Runnable task)

  • Executors 工厂类,用来创建不同的线程池。

    • ExecutorService es = Executors.newFixedThreadPool(4);

      • es.submit(Runnable task)

      • es.shutdown(); es.shutdownNow();

    • ExecutorService es = Executors.newCachedThreadPool();

    • ExecutorService es = Executors.newSingleThreadExecutor();

    • ScheduledExecutorService es = Executors.newScheduledThreadPool(1);

      • es.schedule(command, delay, unit)

      • es.scheduleAtFixedRate(command, initialDelay, period, unit)

      • es.scheduleWithFixedDelay(command, initialDelay, delay, unit)

    • unit:TimeUnit 枚举类

    • package com.qf.pool;

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

      /**
      * 线程池概述:
      * 1:jdk1.5 提供了创建线程池对象的快捷的方式。可以轻松的创建一个线程池对象。
      * 2:线程池中存放的就是线程,通过线程池可以很轻松的将要执行的任务交给线程池中的线程来执行。
      * 3:java.util.concurrent.Executor 顶级接口。
      * 4:Executor 的子接口 ExecutorService ,规定了线程池应该具有的功能。
      * 5:创建线程池对象的方式
      * 1:java.util.concurrent.Executors 工具类。
      * 2:通过 ThreadPoolExecutor 构造方法。
      *
      */
      public class TestPool {

      public static void main(String[] args) {
      test5();
      }

      private static void test4() {
      // 定时任务线程池。
      ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

      // 有一个线程,5秒之后开始执行指定的任务,任务每隔1秒执行一次。
      // 固定周期执行指定的任务。
      pool.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
      System.out.println("hello");
      try {
      Thread.sleep(500);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      }, 5000, 1000, TimeUnit.MILLISECONDS);



      // pool.shutdown();
      }

      private static void test5() {
      // 定时任务线程池。
      ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

      // 指定固定的间隔时间执行指定的任务。
      pool.scheduleWithFixedDelay(new Runnable() {
      public void run() {
      try {
      Thread.sleep(3000);
      } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }
      System.out.println("hello");
      }
      }, 2000, 1000, TimeUnit.MILLISECONDS);



      // pool.shutdown();
      }


      private static void test3() {
      // 创建单独的一个线程的线程池。
      ExecutorService pool = Executors.newSingleThreadExecutor();
      // 创建20个任务交给 pool 执行。
      for (int i = 0; i < 10; i++) {
      pool.execute(new Runnable(){
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
      try {
      Thread.sleep(1000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+"-->任务结束了");
      }
      });
      }
      // 所有的任务都做完之后,再关闭线程池,释放资源。
      pool.shutdown();
      // 不等所有的任务都执行完毕,就直接关闭了线程池。
      // pool.shutdownNow();
      }

      private static void test2() {
      // 创建固定数量线程的线程池。
      ExecutorService pool = Executors.newFixedThreadPool(5);
      // 创建20个任务交给 pool 执行。
      for (int i = 0; i < 20; i++) {
      pool.execute(new Runnable(){
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
      try {
      Thread.sleep(2000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+"-->任务结束了");
      }
      });
      }
      // 所有的任务都做完之后,再关闭线程池,释放资源。
      pool.shutdown();
      // 不等所有的任务都执行完毕,就直接关闭了线程池。
      // pool.shutdownNow();
      }

      private static void test1() {
      // 创建一个线程数量不确定的线程池,
      ExecutorService pool = Executors.newCachedThreadPool();

      // 创建20个任务交给 pool 执行。
      for (int i = 0; i < 20; i++) {
      pool.execute(new Runnable(){
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
      try {
      Thread.sleep(1000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+"-->任务结束了");
      }
      });
      }
      // 所有的任务都做完之后,再关闭线程池,释放资源。
      pool.shutdown();
      // 不等所有的任务都执行完毕,就直接关闭了线程池。
      // pool.shutdownNow();
      }

      }
    •  

  • Timer (自行了解)

    • 对延迟执行做了封装

12. 阿里多线程开发规范介绍

  • 获取单例对象要确保线程安全。

  • 创建线程和线程池时候,需要指定线程名称,方便出错回溯。

  • 线程资源必须通过线程池提供,不允许在程序中显式的创建线程。

  • 线程池不允许使用 Executors去创建,而是通过ThreadPoolExecutor的方式。

    • FixedThreadPool 和 SingleThreadPool 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(Out Of Memory)。

    • CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数为 Integer.MAX_VALUE,可能会导致创建大量的线程,导致 OOM。

13. ThreadPoolExecutor 的七个参数

  • corePoolSize - 核心线程数

  • maximumPoolSize - 最大线程数。

  • keepAliveTime - 非核心线程的存活时间。

  • unit - keepAliveTime - 时间单位。

  • workQueue - 执行前用于保持任务的队列。(LinkedBlockingQueue)

  • threadFactory - 执行程序创建新线程时使用的工厂。(Executors.defaultThreadFactory())

  • handler - (拒绝策略)由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

    • AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。(核心任务,默认策略)

    • DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。(非核心任务)

    • DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务。

    • CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。

    • 当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略

  • 使用顺序:核心线程--工作队列--最大线程

  • 测试,出现异常,确保可以关闭线程池,try-cathc-finally

  • package com.qf.pool;

    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;

    /**
    * - corePoolSize - 核心线程数
    - maximumPoolSize - 最大线程数。
    - keepAliveTime - 非核心线程的存活时间。
    - unit - keepAliveTime - 时间单位。
    - workQueue - 执行前用于保持任务的队列。(LinkedBlockingQueue)
    - threadFactory - 执行程序创建新线程时使用的工厂。(Executors.defaultThreadFactory())
    - **handler - (拒绝策略)由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。**
     - AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。(核心任务,默认策略)
     - DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。(非核心任务)
     - DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。
     使用该策略要特别小心,因为它会直接抛弃之前的任务。(喜新厌旧)
     - CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。

     四个拒绝策略,对应着四个类。四个类是 ThreadPoolExecutor 类中的四个 静态内部类。

     - 当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略**。
    - **使用顺序:核心线程--工作队列--最大线程**
    *
    *
    *
    *LinkedBlockingQueue: 底层使用链表实现的阻塞队列。
    * 阻塞队列:如果队列满了,添加任务的线程会被阻塞。直到队列中的任务被线程执行了,线程继续对队列存入任务。
    */
    public class TestPoolExector {

    public static void main(String[] args) {
    ThreadPoolExecutor pool = null;
    try {
    pool = new ThreadPoolExecutor(
    2, //核心线程数。不会被销毁。
    10, //最大线程数。非核心线程=最大数-核心数。
    60, //非核心线程可以空闲的时间。超过时间销毁。
    TimeUnit.SECONDS, //时间单位。
    new LinkedBlockingQueue<Runnable>(5), //工作队列。
    Executors.defaultThreadFactory(), //创建线程池中的线程的工厂。
    new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略。

    for (int i = 0; i <16 ; i++) {
    pool.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"-->任务结束了");
    }
    });
    }
    } finally {
    if(pool != null)
    pool.shutdown();
    }


    }

    }
  •  

12. Callable 接口

  • jdk1.5 加入,与Runnable 接口类似,实现的子类代表了一个线程任务。

  • 具有泛型返回值,可以声明异常。

  • 使用方式

    • 创建 Callable 实例

    • 将 上面创建的实例,转换为一个未来任务 FutrueTask。

    • 将 FutrueTask 交给线程去执行。

    • 获取执行的结果

  • 主要配合线程池使用

  • package com.qf.call;

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;

    import com.qf.util.MyUtil;

    /**
    * 创建线程的第三种方式:
    * 1:jdk 1.5 版本推出的。
    * 2:使用方式:实现一个 Callable接口。
    *
    * 优点:
    * 1:任务结束可以获得一个返回值,作为任务的结果。
    * 2:call 实现的方法中,如果有异常了,可以不使用try-catch,而使用 throws 抛出。
    * 父接口中默认 抛出了 Exception。
    * 3:通过 FutureTask 这个类提供了一些 操作线程任务的方法。可以使用。
    *
    * 该实现方式主要配合线程池使用。
    */
    public class TestCall {
    public static void main(String[] args) throws Exception, ExecutionException {
    // 创建任务对象。
    MyCallable callable = new MyCallable();
    // 可以对任务线程提供一些更高级的操作。
    FutureTask<Integer> task = new FutureTask<>(callable);

    Thread thread = new Thread(task);
    thread.start();

    // 获取任务的返回的结果。阻塞的方法,直到获得了线程任务的返回值之后,阻塞解除。不然一直阻塞等待任务的结果。
    Integer result = task.get();
    System.out.println(result);



    // 取消线程的任务,如果线程已经开始执行任务,那么可以中断任务。
    // task.cancel(true);
    // task.isCancelled();
    // task.isDone();
    }
    }

    //任务类。
    class MyCallable implements Callable<Integer>{

    // 重写 call 方法,规定的你任务,已经返回你的任务执行完毕之后的结果。
    @Override
    public Integer call() throws Exception {
    Thread.sleep(5000);
    return MyUtil.random(0, 100);
    }

    }
  •  

13. Future 接口

  • 使用线程池执行,submit 提交返回 Future

  • Future 表示异步计算的结果。

  • 练习:一个线程计算1-50累加和,一个计算51-100累加和,然后再计算累加和相加的结果。

  • package com.qf.lock;

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;

    public class TestFuture {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService pool = Executors.newFixedThreadPool(2);

    // 第一个任务。 提交任务。
    Future<Integer> future1 = pool.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    int sum = 0;
    for (int i = 1; i <= 50; i++) {
    sum += i;
    Thread.sleep(100);
    }
    return sum;
    }
    });

    // 第二个任务
    Future<Integer> future2 = pool.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    int sum = 0;
    for (int i = 51; i <= 100; i++) {
    sum += i;
    Thread.sleep(100);
    }
    return sum;
    }
    });

    // 获取两个任务的结果。
    int result = future1.get() + future2.get();

    System.out.println(result);

    pool.shutdown();
    }

    }
  •  

14. Lock 接口

  • jdk1.5 加入,显式定义,结构灵活,功能强大,性能优越。

  • 常用方法

    • void lock() 获取锁,如果锁被占用,等待。

    • void unlock() 释放锁

    • boolean tryLock() 尝试获取锁,成功返回true,否则返回false。

  • Lock 和 synchronized 的区别

    • 来源: lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

    • 异常是否释放锁: synchronized在发生异常时候会自动释放占有的锁,因此不会出现 死锁 而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

    • 是否知道获取锁 Lock可以通过 trylock() 来知道有没有获取锁,而synchronized不能;

    • Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

    • 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

    • synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,

15. 重入锁 ReentrantLock

  • Lock 接口的实现类,具有互斥锁的功能。

  • ReentrantLock 案例:买票|取钱

  • package com.qf.lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 
     * 账户类。
     */
    public class Account {
    	private Lock lock = new ReentrantLock(true);
    	
    	// 余额
    	private int balance = 1500;
    
    	public boolean withdraw(int money) {
    		String name = Thread.currentThread().getName();
    		
    		lock.lock();
    		
    		try {
    			
    			if (balance >= money) {
    				try {
    					Thread.sleep(1);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    
    				balance -= money;
    				System.out.println(name + "\t取钱【成功】了,剩余余额:" + balance);
    				return true;
    			}
    			
    		} finally {
    			// 同步代码块。
    			lock.unlock();
    		}
    		
    
    		System.out.println(name + "\t取钱【失败】了,剩余余额:" + balance);
    		return false;
    	}
    
    }
    
  •  

16. ReentrantReadWriteLock

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁和写锁,支持多次分配读锁。使多个读操作可以并发执行。

  • 写 写 互斥

  • 读 写 互斥

    • lock.writeLock().lock();

    • lock.writeLock().unlock();

  • 读 读 不互斥

    • lock.readLock().lock();

    • lock.readLock().unlock();

  • 读写锁案例,要求能看懂即可。

  • package com.qf.lock;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
    
    public class TestReadWriteLock {
    
    	public static void main(String[] args) {
    		test1();
    	}
    
    	private static void test1() {
    		Mydata mydata = new Mydata(10);
    		
    		
    		ExecutorService pool = Executors.newFixedThreadPool(30);
    		
    		for (int i = 0; i < 20; i++) {
    			pool.execute(new Runnable() {
    				@Override
    				public void run() {
    					mydata.getValue();
    				}
    			});
    		}
    		
    		for (int i = 0; i < 2; i++) {
    			pool.execute(new Runnable() {
    				@Override
    				public void run() {
    					mydata.setValue(20);
    				}
    			});
    		}
    		
    		pool.shutdown();
    		
    		System.out.println(1);
    //		总共需要的时长 >2.2s
    		long start = System.currentTimeMillis();
    //		pool 所有的任务都完成了,并释放了资源。
    		/**
    		 * Returns true if all tasks have completed following shut down.Note that 
    		 * isTerminated is never true unlesseither shutdown or shutdownNow was called first.
    		 */
    		while(!pool.isTerminated()){
    		}
    		///执行到这里,意味着 pool 的任务结束了。
    		long cost = System.currentTimeMillis() - start;
    		System.out.println(cost);
    		
    		
    	}
    
    }
    
    class Mydata{
    //	private Lock lock = new ReentrantLock();
    //	创建读写锁对象。
    	ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    //	获取到读锁。读锁和读锁不互斥。
    	ReadLock readLock = lock.readLock();
    //	后去到写锁。
    	WriteLock writeLock = lock.writeLock();
    	
    	private int value;
    
    	public Mydata(int value) {
    		super();
    		this.value = value;
    	}
    
    	public int getValue() {
    //		在读数据的部分,使用读锁上锁。
    		readLock.lock();
    		try {
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			return value;
    		} finally {
    			readLock.unlock();
    		}
    	}
    
    	public void setValue(int value) {
    //		在修改的地方。使用写锁上锁。
    		writeLock.lock();
    		try {
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			this.value = value;
    		} finally {
    			writeLock.unlock();
    		}
    	}
    	
    	
    }
    
  •  

17. 扩展知识

  • Condition 接口 和 Lock 配合 实现 生产者和消费者问题。

  • 第一代线程安全集合类

    Vector 、HashTable 使用synchronized修饰方法,效率低下

  • 第二代线程非安全集合类

    ArrrayLIst、HashMap

    线程不安全但是性能好,用来代替Vector、Hashtable

    使用ArrayList、HashMap,需要线程安全时

    使用Collections.synchronizedList(list); Collections.synchronizedMap(map)

    底层使用synchronized代码块锁 锁在方法里面

  • 第三代线程安全集合类

    java.util.concurrent.*

    ConcurrentHashMap;

    CopyOnWriteArrayList;

    CopyOnWriteArraySet; //注意不是CopyOnWriteHashSet *

    底层大都采用Lock锁 (1.8的ConcurrentHashMap不适用Lock锁),保证线程安全的同时,性能也很高

    线程 ***

    1. 相关概念

    • 程序

    • 进程

    • 线程

    • package com.qf.thread;

      import java.util.Scanner;

      /**
      * 1: 相关的概念
      * 1:程序:静态的概念,计算机指令的集合。
      * program
      * 2:进程:
      * process
      * 运行中的程序就是进程。
      * 程序的一次执行就称为进程。
      * 特点:
      * 1:多个进程可以并发执行。(看上去是并发执行,但是如果是单cpu,单核,那么所有的进程是串行)。
      * 2:系统会为每个进程分配对应的资源使用,而且进程之间的资源是不共享的。
      * 【进程是系统进行资源分配的最小单位】
      *     3:进程创建和销毁对系统资源的消耗是比较大的。
      *
      * 3: 线程
      * thread
      * 1:进程中的一条任务线。进程中的一条独立的执行路径。
      * 2:线程不能独立存在,必须以进程为载体。在进程中存活。
      * 3:线程负责将进程中的代码交给cpu去执行。
      * 4:线程是可以并发执行。一个进程中可以包含多个线程。
      * 5:一个进程中至少要有一个线程,如果一个进程中没有任何的线程,那么 os会杀死进程。
      * 6:进程是任务的载体,线程是进程中负责执行任务的。
      * 7: 进程中的第一个线程,称为主线程。java程序中的主线程:main。
      * 8:进程中的所有的线程是共享所在的进程的资源的。
      * 9:【线程是cpu进行调度执行任务的最小单位。】
      * 进程中的线程抢夺cpu的控制权来执行线程的任务。cpu将自己的划分为非常微小的时间片给不同的进程中的线程来使用。
      *
      * 4:进程的线程关系
      * 1:进程是线程的载体。进程负责给线程提供资源。线程负责执行进程中的任务。
      * 2:进程中至少要有一个存活的线程。一个进程中的线程的个数没有限制。
      * 3:进程可以在任务中创建新的线程,也可以杀死进程中的线程。
      * 4:如果将进程杀死,进程中的所有的线程都会被杀死。
      * 5:java 程序中的所有的需要执行的代码都是在某一个线程中被执行的。
      *
      * java 程序中的主线程是main线程。main线程的主体任务就是 main方法中的所有的代码。
      *
      * 5:单线程程序的优缺点:
      * 优点:消耗的系统资源少。代码简单。
      * 缺点:一旦线程被阻塞了,那么整个程序就暂停了。
      *
      * 6:多线程程序的优缺点:
      * 优点:可以充分的利用进程的资源,一个线程被阻塞了,其他的线程不受任何的影响。
      * 缺点:会消耗更多的系统资源。
      *
      */
      public class Test {

      public static void main(String[] args) {
      test1();
      test2();
      }

      private static void test1() {
      while(true){
      System.out.println("1");
      }
      }

      private static void test2() {
      while(true){
      System.out.println("2");
      }
      }
      }

    2. 线程的创建的方式

    • 继承 java.lang.Thread

    • 实现 java.lang.Runnable

    3. 案例

    • 售卖火车票的线程的两种实现

    • package com.qf.thread;

      /**
      * java 中创建线程的方式:
      * 1:继承 java.lang.Thread
      * 重写 run 方法。
      * 在run 方法中指定线程对象的任务代码。
      * 创建 Thread 子类对象,然后 通过调用 start 方法,启动线程。
      *
      */
      /**
      *
      * 模拟火车站卖票。
      */
      public class TestSaler {

      public static void main(String[] args) {
      TicketSystem system = new TicketSystem();
      // 创建了5个窗口,5个线程。
      Saler saler1 = new Saler("窗口-1",system);
      Saler saler2 = new Saler("窗口-2",system);
      Saler saler3 = new Saler("窗口-3",system);
      Saler saler4 = new Saler("窗口-4",system);
      Saler saler5 = new Saler("窗口-5",system);

      // 启动线程,开始卖票
      saler1.start();
      saler2.start();
      saler3.start();
      saler4.start();
      saler5.start();
      }
      }

      // 票务系统。
      class TicketSystem {
      // 初始票的个数。
      private int ticketCount = 100;

      // 每调用一次,代表出票一张。
      public boolean sale() {
      // 让线程休息一下。
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      // 如果有余票
      if (ticketCount > 0) {
      ticketCount--;
      // 输出卖票的信息。
      System.out.println(Thread.currentThread().getName() + "\t卖了一张票,剩余:" + ticketCount + "\t张");
      return true;
      }
      System.out.println(Thread.currentThread().getName() + "\t出票失败了");
      return false;

      }
      }

      class Saler extends Thread {
      // 持有唯一的票务系统对象的引用。
      private TicketSystem system;

      public Saler(String name,TicketSystem system) {
      super(name);
      this.system = system;
      }

      // 必须只有一个票务系统。
      // static TicketSystem system = new TicketSystem();

      // 重写 run 方法,在run 中规定线程的任务。
      @Override
      public void run() {
      while (true) {
      boolean result = system.sale();
      if (!result)
      return;
      }
      }
      }
    • package com.qf.thread;
      /**
      * 线程的第二种实现方式:
      * 实现 java.lang.Runnable 接口。
      * Runnable 接口的子类是用来定义任务的。
      *
      * 1: 定义任务类,实现Runnable 接口,
      * 2:在子类的实现 run 方法中,添加线程的任务代码。
      * 3:创建任务对象。将任务对象作为实参交给 Thread 对象。
      * 4:启动 Thread 对象。start();
      *
      * 总结:
      * 1:继承Thread 类
      * 优点:代码实现比较简单。
      * 缺点:继承了线程类,就不能继承其他的类。
      * 2:实现 Runnable 接口
      * 优点:还可以继承其他的类。
      * 缺点:代码实现稍微复杂。
      * 3:实现 Runnable 接口 共享数据更简单。继承Thread 类共享数据稍微复杂。
      */
      public class TestRunnable {

      public static void main(String[] args) {
      // 创建任务对象。
      TicketSystemRunnable task = new TicketSystemRunnable();

      // 将任务交给线程对象。
      Thread saler1 = new Thread(task,"窗口-1");
      Thread saler2 = new Thread(task,"窗口-2");
      Thread saler3 = new Thread(task,"窗口-3");
      Thread saler4 = new Thread(task,"窗口-4");
      Thread saler5 = new Thread(task,"窗口-5");

      // 启动线程,执行任务。
      saler1.start();
      saler2.start();
      saler3.start();
      saler4.start();
      saler5.start();
      }
      }


      //任务类。
      class TicketSystemRunnable implements Runnable {
      // 初始票的个数。
      private int ticketCount = 100;

      // 每调用一次,代表出票一张。
      public boolean sale() {
      // 让线程休息一下。
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      // 如果有余票
      if (ticketCount > 0) {
      ticketCount--;
      // 输出卖票的信息。
      System.out.println(Thread.currentThread().getName() + "\t卖了一张票,剩余:" + ticketCount + "\t张");
      return true;
      }
      System.out.println(Thread.currentThread().getName() + "\t出票失败了");
      return false;

      }
      @Override
      public void run() {
      while(true){
      boolean result = sale();
      if(!result)return;
      }
      }
      }
    •  

    4. 线程的状态(基本)

    • 新建状态(初始状态)

    • 就绪状态(可运行状态)

    • 运行状态

    • 死亡状态(终止状态)

    5. 线程常用方法

    • 线程的优先级设置

    • package com.qf.thread;
      /**
      * 线程的优先级相关的实例方法:
      * setPriority(int value)
      * 设置线程对象的优先级。
      * getPriority()
      * 获取线程对象的优先级。
         public final static int MIN_PRIORITY = 1;
         public final static int NORM_PRIORITY = 5;
         public final static int MAX_PRIORITY = 10;
         
         总结:通过设置线程的优先级,可以让优先级高的线程被cpu调度执行的概率大大的增加。优先级小的线程被cpu调度执行的概率大大的降低。
      *
      */
      public class TestPriority {

      public static void main(String[] args) {
      MyThread1 thread1 = new MyThread1();
      thread1.setName("张三");
      thread1.setPriority(Thread.MAX_PRIORITY);
      thread1.start();

      MyThread1 thread2 = new MyThread1();
      thread2.setName("李四");
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.start();
      }
      }

      class MyThread1 extends Thread{
      private int i;
      @Override
      public void run() {
      while(true){
      System.out.println(getName() + "--"+i++);
      }
      }
      }
    •  

    • join

      • 先start 后join

      • package com.qf.thread;
        /**
        * 线程的实例方法
        * join()
        *
        * 作用:可以实现线程插队的效果,优先执行指定的线程。
        * 当一个线程对象调用了join方法。会导致当前线程(执行了xxx.join()代码的线程)从【运行状态】,进去【阻塞状态】
        * 当调用join()方法的线程对象 的任务执行完毕之后,被阻塞的线程才能解除阻塞进入 【就绪状态】
        * join 方法先调用start 然后调用join
        *
        * join(long millis)
        * 和无参的 join 的区别。解除阻塞线程的条件不同。多了一个解除阻塞的最长等待的时间。到达时间,自动解除阻塞。
        *
        */
        public class TestJoin {

        public static void main(String[] args) throws Exception{

        for (int i = 0; i < 10; i++) {
        if(i == 5){
        ChengThread cheng = new ChengThread();
        cheng.setName("程咬金");
        cheng.start();
        // 导致main线程被阻塞了。当 cheng 线程执行任务完毕。main线程解除阻塞。进入就绪状态。
        cheng.join();
        }
        System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
        }
        }
        class ChengThread extends Thread{
        @Override
        public void run() {
        for (int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
        }
        }
    • yield

    • package com.qf.thread;
      /**
      * 线程类的静态方法:
      * Thread.yield();
      * 会导致当前线程(cpu正在调度执行的线程)从运行状态,进行就绪状态。
      */
      public class TestYield {

      public static void main(String[] args) {
      new MyThead2().start();
      new MyThead2().start();
      }

      }
      class MyThead2 extends Thread{
      @Override
      public void run() {
      for (int i = 1; i <= 10; i++) {
      System.out.println(Thread.currentThread().getName() + "-->" + i);
      Thread.yield();
      }
      }
      }
    • sleep

    • package com.qf.thread;

      import java.text.DateFormat;
      import java.text.SimpleDateFormat;
      import java.util.Date;

      /**
      * Thread.sleep(long millis);
      * 作用:会导致当前线程进入阻塞状态。让线程从运行状态进入阻塞状态(休眠)。从进入阻塞状态开始计时,
      * 到实参时间结束,线程从阻塞状态
      * 进入就绪状态。
      *
      */
      public class TestSleep {

      public static void main(String[] args) throws Exception {
      test1();
      }



      private static void test() throws Exception{
      System.out.println("Ready");
      Thread.sleep(1000);
      for (int i = 3; i >0; i--) {
      System.out.println(i);
      Thread.sleep(1000);
      }
      System.out.println("Go");
      }

      // 要求没隔一秒打印一次当前时间。xx:xx:xx
      private static void test1() {
      DateFormat sdf = new SimpleDateFormat("HH:mm:ss");
      Date date = new Date();
      int counter = 0;
      while(true){
      long start = System.currentTimeMillis();
      date.setSeconds(date.getSeconds()+1);
      String str = sdf.format(date);
      System.out.println(str);
      long cost = System.currentTimeMillis() - start;
      try {
      Thread.sleep(1000-cost);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }

      counter++;
      // counter
      if(counter == 600){
      date = new Date();
      counter = 0;
      }

      }
      }
      }
    •  

    • 守护线程

      • 先设置后启动

      • main不能设置为守护线程

      • package com.qf.thread;

        /**
        * 线程分为两类:
        * 1:用户线程|前台线程
        * 2:守护线程|后台线程
        *
        * 一个线程创建出来,默认是用户线程。
        * 进程的生命周期:
        * 进程的生命周期由进程中存活的用户线程来决定。只要还有存活的用户线程,那么进程就是存活的。
        * 【如果一个进程中的所有的存活的线程只是守护线程了,那么jvm会杀死进程。】
        *
        * 守护线程的作用:
        * 1:守护线程一般是给用户线程提供服务的。
        * 2:GC 功能是一个单独的线程来管理的,这个线程优先级很低,而且还是一个守护线程。
        *
        * 注意:
        * 1:守护线程必须先设置,后启动。
        * thread.setDaemon(true);
        thread.start();
        2:main 线程不能设置为守护线程。
        *
        */
        public class TestDaemon {

        public static void main(String[] args) {
        // Thread.currentThread().setDaemon(true);

        DaemonThread thread = new DaemonThread();
        thread.setDaemon(true);
        thread.start();

        for (int i = 0; i < 10; i++) {
        try {
        Thread.sleep(200);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
        }

        }
        class DaemonThread extends Thread{
        @Override
        public void run() {
        for (int i = 0; i < 50; i++) {
        try {
        Thread.sleep(500);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
        }
        }
      •  

    6. 线程同步

    • 线程安全例子

    • package com.qf.sync;

      public class TestSync {
      public static void main(String[] args) {
      WithdrawTask task = new WithdrawTask();

      Thread san = new Thread(task,"张三");
      Thread sanWife = new Thread(task,"张三媳妇");

      san.start();
      sanWife.start();
      }
      }

      package com.qf.sync;
      /**
      *
      *账户类。
      */
      public class Account {
      // 余额
      private int  balance = 1500;

      public boolean withdraw(int money){
      String name = Thread.currentThread().getName();
      // 同步代码块。
      synchronized (this) {
      if(balance >= money){
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }

      balance -= money;
      System.out.println(name + "\t取钱【成功】了,剩余余额:"+balance);
      return true;
      }
      }

      System.out.println(name + "\t取钱【失败】了,剩余余额:"+balance);
      return false;
      }
      }

      package com.qf.sync;

      public class WithdrawTask implements Runnable{
      private Account account;

      public WithdrawTask() {
      account = new Account();
      }

      @Override
      public void run() {
      account.withdraw(1000);
      }
      }
    • 线程同步的两种实现

    • 同步锁的选用

    • package com.qf.sync;
      /**
      * 线程安全问题:
      * 1:只有多线程的情形下才存在线程安全问题。
      * 多个线程访问同一个数据。就会存在线程安全问题。
      * 2:线程安全问题的解决方案:
      * 原理:对访问同一个数据的代码要实现线程之间的互斥访问。任何一个时间点只允许一个线程来访问,不能多个线程同时访问。
      * 解决方案:
      * 1:同步代码块
      * 语法:synchronized (同步锁) {需要互斥访问的代码 }
      * synchronized:
      * 1:java的关键字
      * 2:用于线程互斥访问某些代码。
      * 同步锁:
      * 1:也称为同步监视器对象。
      * 2:可以是任意对象。
      *
      * 同步代码块的执行过程:
      * 1:线程A 访问同步代码块,给同步锁添加锁标识,开始执行同步代码块中的内容。
      * 2:线程B 也要访问同步代码块,发现同步锁被其他的线程添加锁标识,在旁边等待。锁标识去掉。
      * 3:线程A,执行完同步代码块中的内容,要解锁,去掉锁标识。
      * 4:线程B,发现锁已经被解锁了,锁标识去掉了,那么就给锁添加锁标识,上锁,开始执行同步代码块中的内容。
      *
      * 同步代码块只是实现了方法中的局部代码实现了线程之间的互斥访问。
      *
      * 2:同步方法
      * 给方法添加同步语法。
      * 让多个线程实现互斥访问同步方法。
      * 语法:在方法的返回值类型前添加关键字 synchronized
      *
      * 同步方法的本质上还是同步代码块。
      * 同步方法的同步监视器对象
      * 1:如果同步的是实例方法,那么同步监视器是 this。
      * 2:如果同步的是静态方法,那么同步监视器是 当前类的 Class 对象。
      *
      *   3:同步监视器对象的选择:
      *   1:监视器对象必须具有唯一性。
      *   2:监视器对象具有不可改变的特点,不能指向其他的对象。
      *   可以使用静态的不变的对象
      *   1:final staitc Object o = new Object();
      *   2:最合适的监视器对象:当前类的Class 对象。
      *   已经创建好了,唯一不变。 【类名.class】
      *   该对象是 静态同步方法的 默认的监视器对象。
      *
      *
      */
      public class TestSync {
      public static void main(String[] args) {
      WithdrawTask task = new WithdrawTask();

      Thread san = new Thread(task,"张三");
      Thread sanWife = new Thread(task,"张三媳妇");

      san.start();
      sanWife.start();
      }
      }
    • 线程安全类

      • StringBuffer

      • Vector

      • Hashtable

    7. 线程死锁(**)

    package com.qf.thread;
    /**
    * 死锁:
    * 死锁出现的条件:同步代码块的嵌套。
    * 尽量避免出现同步代码块的嵌套。
    *
    * 死锁一旦出现,没有办法通过代码去解决死锁。
    */
    public class DeadLockTest {

    public static void main(String[] args) {
    DeadLockThread thread1 = new DeadLockThread(0);
    thread1.setName("A");
    DeadLockThread thread2 = new DeadLockThread(1);
    thread2.setName("B");

    thread1.start();
    thread2.start();
    }

    }

    class DeadLockThread extends Thread{
    public static final Object o1 = new Object();
    public static final Object o2 = new Object();

    private int id;

    public DeadLockThread(int id) {
    this.id = id;
    }
    @Override
    public void run() {
    if(id == 0){// A 线程
    synchronized (o1) {
    // 在这里停下来。
    System.out.println(getName() + "锁住了o1请求锁o2");
    synchronized (o2) {
    System.out.println(getName() + "锁住了o1也锁了o2");
    }
    }
    }else{//B
    synchronized (o2) {
    // 在这里停下来。
    System.out.println(getName() + "锁住了o2请求锁o1");
    synchronized (o1) {
    System.out.println(getName() + "锁住了o2也锁了o1");
    }
    }
    }
    }
    }

    8. 线程通信

    9. 生产和消费者

    package com.qf.pc;

    public class TestPc {

    public static void main(String[] args) {
    MyStack<Steam> stack = new MyStack<>();

    Producer producer = new Producer(stack);
    producer.setName("生产者");
    Consumer consumer = new Consumer(stack);
    consumer.setName("消费者-1");
    Consumer consumer2 = new Consumer(stack);
    consumer2.setName("消费者-2");

    producer.start();
    consumer.start();
    consumer2.start();

    }

    }

    package com.qf.pc;
    /**
    * 商品类。馒头
    *
    */
    public class Steam {
    private int id;

    public Steam(int id) {
    super();
    this.id = id;
    }

    @Override
    public String toString() {
    return "馒头:" + id;
    }
    }

    package com.qf.pc;

    public class Producer extends Thread{

    private MyStack<Steam> stack;

    public Producer(MyStack<Steam> stack) {
    super();
    this.stack = stack;
    }


    @Override
    public void run() {
    for (int i = 0; i < 6; i++) {
    stack.push(new Steam(i+1));
    }
    }
    }

    package com.qf.pc;

    import java.util.Arrays;

    /**
    * 自定义容器。栈。 底层使用数组作为栈的容器。
    */
    public class MyStack<E> {
    // 初始容量。
    private final static int DEFAULT_SIZE = 3;
    private final static int MAX_SIZE = DEFAULT_SIZE;

    // 用于保存元素的数组。
    private Object[] elementData;

    // 定义栈顶指针。代表了要存入数据的索引,还代表了元素的个数。
    private int index;

    public MyStack() {
    elementData = new Object[DEFAULT_SIZE];
    index = 0;
    }

    // 压栈操作。
    public boolean push(E e) {
    synchronized (this) {
    // 特殊情况 满了。
    if (isFull()) {
    // 让生产者线程在当前容器对象上等待。进入阻塞状态。并对 this 解锁。
    try {
    System.out.println(Thread.currentThread().getName() + "\t准备等待了!!");
    this.wait();
    System.out.println(Thread.currentThread().getName() + "\t被唤醒了");
    } catch (InterruptedException e1) {
    e1.printStackTrace();
    }
    }

    try {
    Thread.sleep(100);
    } catch (InterruptedException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
    }

    // 常规情况
    elementData[index] = e;
    index++;

    System.out.println(Thread.currentThread().getName() + "\t生产了商品:" + e);
    // 生产者一旦生产了商品,那么就可以通知消费者继续消费。
    this.notify();

    return true;
    }
    }

    // 获取栈顶元素。
    public E pop() {
    synchronized (this) {
    // 特殊情况。
    while (isEmpty()) {
    // 让消费者线程在当前this 容器上等待。进入阻塞状态。并对 this 解锁。
    try {
    System.out.println(Thread.currentThread().getName() + "\t准备等待了!!");
    this.wait();
    System.out.println(Thread.currentThread().getName() + "\t被唤醒了");
    } catch (InterruptedException e1) {
    e1.printStackTrace();
    }
    }

    try {
    Thread.sleep(100);
    } catch (InterruptedException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
    }

    // 常规情况
    index--;
    E e = (E) elementData[index];
    // 避免内存泄漏。
    elementData[index] = null;

    System.out.println(Thread.currentThread().getName() + "\t消费了商品:" + e);

    // 消费者线程消费了商品,就可以通知生产者继续生产。
    // 会唤醒一个在当前对象上等待的其他的线程(只能是生产者线程)。
    this.notify();

    return e;
    }
    }

    // 栈是否满了,不能扩容了。
    private boolean isFull() {
    return index == MAX_SIZE;
    }

    // 判断栈是否为空。
    public boolean isEmpty() {
    return index == 0;
    }

    public void clear() {
    // 清空所有的元素,并指向栈底
    for (int i = 0; i < index; i++) {
    elementData[i] = null;
    }
    index = 0;
    }

    // 获取元素个数。
    public int size() {
    return index;
    }

    public String toString() {
    // 将elementData 中有效数据复制到一个新数组中,返回。
    return Arrays.toString(Arrays.copyOf(elementData, index));
    }
    }

    package com.qf.pc;

    public class Consumer extends Thread{
    private MyStack<Steam> stack;

    public Consumer(MyStack<Steam> stack) {
    super();
    this.stack = stack;
    }


    @Override
    public void run() {
    for (int i = 0; i < 3; i++) {
    stack.pop();
    }
    }
    }

    10 线程状态(阻塞)

    • 三种阻塞分类

      • 等待阻塞 wait-->等待池

      • 同步阻塞 请求资源 锁池

      • 其他的阻塞:sleep、join、IO等。

    • 一道面试题

    • 重入锁

    11. 线程池 ***

    • 相关的接口和类 java.util.concurrent

    • 顶层接口- Executor

      • 规范,只包含一个方法,athor Doug Lea

    • ExecutorService:线程接口

      • submit(Runnable task)

    • Executors 工厂类,用来创建不同的线程池。

      • ExecutorService es = Executors.newFixedThreadPool(4);

        • es.submit(Runnable task)

        • es.shutdown(); es.shutdownNow();

      • ExecutorService es = Executors.newCachedThreadPool();

      • ExecutorService es = Executors.newSingleThreadExecutor();

      • ScheduledExecutorService es = Executors.newScheduledThreadPool(1);

        • es.schedule(command, delay, unit)

        • es.scheduleAtFixedRate(command, initialDelay, period, unit)

        • es.scheduleWithFixedDelay(command, initialDelay, delay, unit)

      • unit:TimeUnit 枚举类

      • package com.qf.pool;

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

        /**
        * 线程池概述:
        * 1:jdk1.5 提供了创建线程池对象的快捷的方式。可以轻松的创建一个线程池对象。
        * 2:线程池中存放的就是线程,通过线程池可以很轻松的将要执行的任务交给线程池中的线程来执行。
        * 3:java.util.concurrent.Executor 顶级接口。
        * 4:Executor 的子接口 ExecutorService ,规定了线程池应该具有的功能。
        * 5:创建线程池对象的方式
        * 1:java.util.concurrent.Executors 工具类。
        * 2:通过 ThreadPoolExecutor 构造方法。
        *
        */
        public class TestPool {

        public static void main(String[] args) {
        test5();
        }

        private static void test4() {
        // 定时任务线程池。
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

        // 有一个线程,5秒之后开始执行指定的任务,任务每隔1秒执行一次。
        // 固定周期执行指定的任务。
        pool.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
        System.out.println("hello");
        try {
        Thread.sleep(500);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        }
        }, 5000, 1000, TimeUnit.MILLISECONDS);



        // pool.shutdown();
        }

        private static void test5() {
        // 定时任务线程池。
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

        // 指定固定的间隔时间执行指定的任务。
        pool.scheduleWithFixedDelay(new Runnable() {
        public void run() {
        try {
        Thread.sleep(3000);
        } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        }
        System.out.println("hello");
        }
        }, 2000, 1000, TimeUnit.MILLISECONDS);



        // pool.shutdown();
        }


        private static void test3() {
        // 创建单独的一个线程的线程池。
        ExecutorService pool = Executors.newSingleThreadExecutor();
        // 创建20个任务交给 pool 执行。
        for (int i = 0; i < 10; i++) {
        pool.execute(new Runnable(){
        @Override
        public void run() {
        System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
        try {
        Thread.sleep(1000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-->任务结束了");
        }
        });
        }
        // 所有的任务都做完之后,再关闭线程池,释放资源。
        pool.shutdown();
        // 不等所有的任务都执行完毕,就直接关闭了线程池。
        // pool.shutdownNow();
        }

        private static void test2() {
        // 创建固定数量线程的线程池。
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // 创建20个任务交给 pool 执行。
        for (int i = 0; i < 20; i++) {
        pool.execute(new Runnable(){
        @Override
        public void run() {
        System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
        try {
        Thread.sleep(2000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-->任务结束了");
        }
        });
        }
        // 所有的任务都做完之后,再关闭线程池,释放资源。
        pool.shutdown();
        // 不等所有的任务都执行完毕,就直接关闭了线程池。
        // pool.shutdownNow();
        }

        private static void test1() {
        // 创建一个线程数量不确定的线程池,
        ExecutorService pool = Executors.newCachedThreadPool();

        // 创建20个任务交给 pool 执行。
        for (int i = 0; i < 20; i++) {
        pool.execute(new Runnable(){
        @Override
        public void run() {
        System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
        try {
        Thread.sleep(1000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-->任务结束了");
        }
        });
        }
        // 所有的任务都做完之后,再关闭线程池,释放资源。
        pool.shutdown();
        // 不等所有的任务都执行完毕,就直接关闭了线程池。
        // pool.shutdownNow();
        }

        }
      •  

    • Timer (自行了解)

      • 对延迟执行做了封装

    12. 阿里多线程开发规范介绍

    • 获取单例对象要确保线程安全。

    • 创建线程和线程池时候,需要指定线程名称,方便出错回溯。

    • 线程资源必须通过线程池提供,不允许在程序中显式的创建线程。

    • 线程池不允许使用 Executors去创建,而是通过ThreadPoolExecutor的方式。

      • FixedThreadPool 和 SingleThreadPool 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(Out Of Memory)。

      • CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数为 Integer.MAX_VALUE,可能会导致创建大量的线程,导致 OOM。

    13. ThreadPoolExecutor 的七个参数

    • corePoolSize - 核心线程数

    • maximumPoolSize - 最大线程数。

    • keepAliveTime - 非核心线程的存活时间。

    • unit - keepAliveTime - 时间单位。

    • workQueue - 执行前用于保持任务的队列。(LinkedBlockingQueue)

    • threadFactory - 执行程序创建新线程时使用的工厂。(Executors.defaultThreadFactory())

    • handler - (拒绝策略)由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

      • AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。(核心任务,默认策略)

      • DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。(非核心任务)

      • DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务。

      • CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。

      • 当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略

    • 使用顺序:核心线程--工作队列--最大线程

    • 测试,出现异常,确保可以关闭线程池,try-cathc-finally

    • package com.qf.pool;

      import java.util.concurrent.Executors;
      import java.util.concurrent.LinkedBlockingQueue;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;

      /**
      * - corePoolSize - 核心线程数
      - maximumPoolSize - 最大线程数。
      - keepAliveTime - 非核心线程的存活时间。
      - unit - keepAliveTime - 时间单位。
      - workQueue - 执行前用于保持任务的队列。(LinkedBlockingQueue)
      - threadFactory - 执行程序创建新线程时使用的工厂。(Executors.defaultThreadFactory())
      - **handler - (拒绝策略)由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。**
       - AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。(核心任务,默认策略)
       - DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。(非核心任务)
       - DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。
       使用该策略要特别小心,因为它会直接抛弃之前的任务。(喜新厌旧)
       - CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。

       四个拒绝策略,对应着四个类。四个类是 ThreadPoolExecutor 类中的四个 静态内部类。

       - 当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略**。
      - **使用顺序:核心线程--工作队列--最大线程**
      *
      *
      *
      *LinkedBlockingQueue: 底层使用链表实现的阻塞队列。
      * 阻塞队列:如果队列满了,添加任务的线程会被阻塞。直到队列中的任务被线程执行了,线程继续对队列存入任务。
      */
      public class TestPoolExector {

      public static void main(String[] args) {
      ThreadPoolExecutor pool = null;
      try {
      pool = new ThreadPoolExecutor(
      2, //核心线程数。不会被销毁。
      10, //最大线程数。非核心线程=最大数-核心数。
      60, //非核心线程可以空闲的时间。超过时间销毁。
      TimeUnit.SECONDS, //时间单位。
      new LinkedBlockingQueue<Runnable>(5), //工作队列。
      Executors.defaultThreadFactory(), //创建线程池中的线程的工厂。
      new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略。

      for (int i = 0; i <16 ; i++) {
      pool.execute(new Runnable() {
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName()+"-->开始执行任务");
      try {
      Thread.sleep(100);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+"-->任务结束了");
      }
      });
      }
      } finally {
      if(pool != null)
      pool.shutdown();
      }


      }

      }
    •  

    12. Callable 接口

    • jdk1.5 加入,与Runnable 接口类似,实现的子类代表了一个线程任务。

    • 具有泛型返回值,可以声明异常。

    • 使用方式

      • 创建 Callable 实例

      • 将 上面创建的实例,转换为一个未来任务 FutrueTask。

      • 将 FutrueTask 交给线程去执行。

      • 获取执行的结果

    • 主要配合线程池使用

    • package com.qf.call;

      import java.util.concurrent.Callable;
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.FutureTask;

      import com.qf.util.MyUtil;

      /**
      * 创建线程的第三种方式:
      * 1:jdk 1.5 版本推出的。
      * 2:使用方式:实现一个 Callable接口。
      *
      * 优点:
      * 1:任务结束可以获得一个返回值,作为任务的结果。
      * 2:call 实现的方法中,如果有异常了,可以不使用try-catch,而使用 throws 抛出。
      * 父接口中默认 抛出了 Exception。
      * 3:通过 FutureTask 这个类提供了一些 操作线程任务的方法。可以使用。
      *
      * 该实现方式主要配合线程池使用。
      */
      public class TestCall {
      public static void main(String[] args) throws Exception, ExecutionException {
      // 创建任务对象。
      MyCallable callable = new MyCallable();
      // 可以对任务线程提供一些更高级的操作。
      FutureTask<Integer> task = new FutureTask<>(callable);

      Thread thread = new Thread(task);
      thread.start();

      // 获取任务的返回的结果。阻塞的方法,直到获得了线程任务的返回值之后,阻塞解除。不然一直阻塞等待任务的结果。
      Integer result = task.get();
      System.out.println(result);



      // 取消线程的任务,如果线程已经开始执行任务,那么可以中断任务。
      // task.cancel(true);
      // task.isCancelled();
      // task.isDone();
      }
      }

      //任务类。
      class MyCallable implements Callable<Integer>{

      // 重写 call 方法,规定的你任务,已经返回你的任务执行完毕之后的结果。
      @Override
      public Integer call() throws Exception {
      Thread.sleep(5000);
      return MyUtil.random(0, 100);
      }

      }
    •  

    13. Future 接口

    • 使用线程池执行,submit 提交返回 Future

    • Future 表示异步计算的结果。

    • 练习:一个线程计算1-50累加和,一个计算51-100累加和,然后再计算累加和相加的结果。

    • package com.qf.lock;

      import java.util.concurrent.Callable;
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.Future;

      public class TestFuture {

      public static void main(String[] args) throws InterruptedException, ExecutionException {
      ExecutorService pool = Executors.newFixedThreadPool(2);

      // 第一个任务。 提交任务。
      Future<Integer> future1 = pool.submit(new Callable<Integer>() {
      @Override
      public Integer call() throws Exception {
      int sum = 0;
      for (int i = 1; i <= 50; i++) {
      sum += i;
      Thread.sleep(100);
      }
      return sum;
      }
      });

      // 第二个任务
      Future<Integer> future2 = pool.submit(new Callable<Integer>() {
      @Override
      public Integer call() throws Exception {
      int sum = 0;
      for (int i = 51; i <= 100; i++) {
      sum += i;
      Thread.sleep(100);
      }
      return sum;
      }
      });

      // 获取两个任务的结果。
      int result = future1.get() + future2.get();

      System.out.println(result);

      pool.shutdown();
      }

      }
    •  

    14. Lock 接口

    • jdk1.5 加入,显式定义,结构灵活,功能强大,性能优越。

    • 常用方法

      • void lock() 获取锁,如果锁被占用,等待。

      • void unlock() 释放锁

      • boolean tryLock() 尝试获取锁,成功返回true,否则返回false。

    • Lock 和 synchronized 的区别

      • 来源: lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

      • 异常是否释放锁: synchronized在发生异常时候会自动释放占有的锁,因此不会出现 死锁 而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

      • 是否知道获取锁 Lock可以通过 trylock() 来知道有没有获取锁,而synchronized不能;

      • Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

      • 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

      • synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,

    15. 重入锁 ReentrantLock

    • Lock 接口的实现类,具有互斥锁的功能。

    • ReentrantLock 案例:买票|取钱

    • package com.qf.lock;
      
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      /**
       * 
       * 账户类。
       */
      public class Account {
      	private Lock lock = new ReentrantLock(true);
      	
      	// 余额
      	private int balance = 1500;
      
      	public boolean withdraw(int money) {
      		String name = Thread.currentThread().getName();
      		
      		lock.lock();
      		
      		try {
      			
      			if (balance >= money) {
      				try {
      					Thread.sleep(1);
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      
      				balance -= money;
      				System.out.println(name + "\t取钱【成功】了,剩余余额:" + balance);
      				return true;
      			}
      			
      		} finally {
      			// 同步代码块。
      			lock.unlock();
      		}
      		
      
      		System.out.println(name + "\t取钱【失败】了,剩余余额:" + balance);
      		return false;
      	}
      
      }
      
    •  

    16. ReentrantReadWriteLock

    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁和写锁,支持多次分配读锁。使多个读操作可以并发执行。

    • 写 写 互斥

    • 读 写 互斥

      • lock.writeLock().lock();

      • lock.writeLock().unlock();

    • 读 读 不互斥

      • lock.readLock().lock();

      • lock.readLock().unlock();

    • 读写锁案例,要求能看懂即可。

    • package com.qf.lock;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      import java.util.concurrent.locks.ReentrantReadWriteLock;
      import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
      import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
      
      public class TestReadWriteLock {
      
      	public static void main(String[] args) {
      		test1();
      	}
      
      	private static void test1() {
      		Mydata mydata = new Mydata(10);
      		
      		
      		ExecutorService pool = Executors.newFixedThreadPool(30);
      		
      		for (int i = 0; i < 20; i++) {
      			pool.execute(new Runnable() {
      				@Override
      				public void run() {
      					mydata.getValue();
      				}
      			});
      		}
      		
      		for (int i = 0; i < 2; i++) {
      			pool.execute(new Runnable() {
      				@Override
      				public void run() {
      					mydata.setValue(20);
      				}
      			});
      		}
      		
      		pool.shutdown();
      		
      		System.out.println(1);
      //		总共需要的时长 >2.2s
      		long start = System.currentTimeMillis();
      //		pool 所有的任务都完成了,并释放了资源。
      		/**
      		 * Returns true if all tasks have completed following shut down.Note that 
      		 * isTerminated is never true unlesseither shutdown or shutdownNow was called first.
      		 */
      		while(!pool.isTerminated()){
      		}
      		///执行到这里,意味着 pool 的任务结束了。
      		long cost = System.currentTimeMillis() - start;
      		System.out.println(cost);
      		
      		
      	}
      
      }
      
      class Mydata{
      //	private Lock lock = new ReentrantLock();
      //	创建读写锁对象。
      	ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
      //	获取到读锁。读锁和读锁不互斥。
      	ReadLock readLock = lock.readLock();
      //	后去到写锁。
      	WriteLock writeLock = lock.writeLock();
      	
      	private int value;
      
      	public Mydata(int value) {
      		super();
      		this.value = value;
      	}
      
      	public int getValue() {
      //		在读数据的部分,使用读锁上锁。
      		readLock.lock();
      		try {
      			try {
      				Thread.sleep(100);
      			} catch (InterruptedException e) {
      				e.printStackTrace();
      			}
      			return value;
      		} finally {
      			readLock.unlock();
      		}
      	}
      
      	public void setValue(int value) {
      //		在修改的地方。使用写锁上锁。
      		writeLock.lock();
      		try {
      			try {
      				Thread.sleep(100);
      			} catch (InterruptedException e) {
      				e.printStackTrace();
      			}
      			this.value = value;
      		} finally {
      			writeLock.unlock();
      		}
      	}
      	
      	
      }
      
    •  

    17. 扩展知识

    • Condition 接口 和 Lock 配合 实现 生产者和消费者问题。

    • 第一代线程安全集合类

      Vector 、HashTable 使用synchronized修饰方法,效率低下

    • 第二代线程非安全集合类

      ArrrayLIst、HashMap

      线程不安全但是性能好,用来代替Vector、Hashtable

      使用ArrayList、HashMap,需要线程安全时

      使用Collections.synchronizedList(list); Collections.synchronizedMap(map)

      底层使用synchronized代码块锁 锁在方法里面

    • 第三代线程安全集合类

      java.util.concurrent.*

      ConcurrentHashMap;

      CopyOnWriteArrayList;

      CopyOnWriteArraySet; //注意不是CopyOnWriteHashSet *

      底层大都采用Lock锁 (1.8的ConcurrentHashMap不适用Lock锁),保证线程安全的同时,性能也很高

posted @   ITboy搬砖人  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示