Java多线程

概念区别

并行和并发

并行并发
物理上的同时,指的是同一个时间点,同一时刻逻辑上的同时,指的是同一个时间段内

进程和线程

进程线程
进程是一个程序的执行过程一个进程中包含很多个任务,线程就是其中单个的任务

多进程与多线程

多进程多线程
现代计算机系统都是多进程的指可以同时运行多个程序多个线程多个线程之间在互相抢占资源,具有随机性

计算机的多进程实际是并发的,并不是同时执行多个进程而是在极短的Cpu时间片段内高速的在多个进程之间切换执行。

jvm是多线程的么?

是多线程的至少有2条线程。 主线程main和垃圾回收线程。在程序运行前主线程必须先被执行,执行主线程后才会去执行主线程中的子线程。

多线程的三种实现方式

  1. 继承Thread
  2. 实现Runnable接口
  3. 实现Callable< V >接口

Thread类

自定义类通过继承Thread类来实现多线程

实现多线程程序的步骤:

  1. 将类声明为 Thread 的子类
  2. 在该子类中重写 Thread 类的 run 方法
  3. 在主线程进行该自定义的线程类的对象的创建

构造方法

Thread(String name)

给线程起名字,创建线程类对象的时候参数为名字。

  • 如果实现多线程是以自定义类继承Thread类的方式,通过有参构造起名字,自定义类中需要定义一个有参构造,参数为String类型,构造方法中需要用super返回这个String。
  • 如果是通过实现Runnable接口的方式实现多线程就可以直接通过构造方法给线程起名字,不需要自己定义。

方法

run()

在继承Thread接口的类中重写该方法,方法中写需要执行的耗时操作。

setName(String name)

给线程起名称

getName() 

获取线程名称

start()

开始执行该线程,在主线程中创建线程类的对象然后执行该方法,多个线程类对象执行该方法此时会并发执行run中的代码。

/以继承Thread类的方式实现多线程
class MyYhread extends Thread{

    public MyYhread() {
        super();
    }
    //自定义类通过有参构造给线程起名字需要手动添加有参构造
    public MyYhread(String name) {
        super(name);
    }

    @Override
    //run方法中应该为耗时操作
    public void run() {
        for (int i = 0; i < 5; i++) {
            //getName返回当前线程的名字
            System.out.println(getName()+":"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //通过有参构造起名字
        MyYhread m1=new MyYhread("张三");
        //通过setName方法起名字
        MyYhread m2=new MyYhread();
        m2.setName("李四");
        //启动线程
        m1.start();
        m2.start();
    }
}
/*
张三:0
李四:0
张三:1
李四:1
张三:2
李四:2
张三:3
张三:4
李四:3
李四:4
*/
currentThread()

返回对当前正在执行的线程的引用对象,是一个静态方法。如果在main方法中执行该方法返回的就是main主线程。

join() 

等待这个线程死亡。 线程类对象执行该方法后,则程序一直等待该线程执行完毕后才执行其他的线程。

class MyYhread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyYhread m1=new MyYhread();
        MyYhread m2=new MyYhread();
        m1.setName("张三");
        m2.setName("李四");
        m1.start();
        try {
            //join等待当前线程执行完毕
            m1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        m2.start();
        //currentThread返回当前线程的引用
        String name = Thread.currentThread().getName();
        System.out.println(name);//main

    }
}
/*
张三:0
张三:1
张三:2
张三:3
张三:4
main
李四:0
李四:1
李四:2
李四:3
李四:4
*/
yield()

静态方法,暂停当前正在执行的线程并执行其他线程。一般在run方法内使用。

setDaemon(boolean on)

参数为true时将线程设置为守护线程,必须在线程开始前执行该方法。守护线程守护的是主线程,当主线程结束时,守护线程会再执行一点时间然后就退出了。

class MyYhread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyYhread m1=new MyYhread();
        MyYhread m2=new MyYhread();
        m1.setName("张三");
        m2.setName("李四");
        //setDaemon设置线程为守护线程
        m1.setDaemon(true);
        m2.setDaemon(true);
        m1.start();
        m2.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
        //主线程结束后守护线程会再执行一段时间后关闭JVM
    }
}
/*
0
1
2
3
4
李四:0
李四:1
李四:2
李四:3
李四:4
李四:5
张三:0
李四:6
李四:7
张三:1
张三:2
*/
getPriority()

返回当前线程的优先级,默认为5,最大10,最小1。

setPriority(int newPriority)

设置当前线程的优先级。

sleep(long millis)

指定一个时间毫秒值让当前线程睡眠。不会释放锁。包含该方法的同步方法和同步代码块只能被第一个访问的线程执行,其他线程会处于阻塞状态。

wait(long timeout)

让当前线程等待,立即释放同步锁,是Object类的方法,可以在参数中指定一个时间毫秒值。如果指定为0或者不写则一直等待,直到被中断或者唤醒。
注意:在同步的多线程中一定要用同步锁锁定的对象来调用wait()方法,不然会抛出IllegalMonitorStateException异常

class MyYhread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(getName()+":"+i);
            //设置睡眠1秒
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //这样的话执行的时候每个线程进来会先打印一句话然后再等待一秒
        }
    }
}
stop()

强制停止当前线程,存在不安全性,已弃用。用作区别


interrupt()

该方法相当于给线程设置中断标记,注意只是标记,并不会中断当前线程,当线程调用sleep, wait, join其中任意一种方法使线程处于阻塞状态时,调用interrupt会抛出InterruptedException异常然后从阻塞状态退出。对于一个正在运行的线程执行该方法将不起作用,直到线程阻塞时才抛出异常。
当A线程处于阻塞状态时,需要在其他正在运行的线程中执行interrupt,然后A线程会退出阻塞状态。

interrupted() 

静态方法,判断当前线线程是否被标记中断,并清除中断标记,该方法的返回值判断的是当前线程,就是说在哪个线程中执行该方法判断的就是哪个线程。如A线程被标记中断,在主线程中中过A线程的线程类对象调用该方法返回的是false,因为判断的是主线程。

isInterrupted()

判断当前线程是否被标记中断,和interrupted的区别是该方法非静态,并且不会清除中断状态。

class MyYhread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 7; i++) {
            if (i==2||i==4) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    //被标记中断的线程当进入阻塞时会抛出异常并继续执行。
                    System.err.println("睡眠被终止了");
                }
            }
            System.out.println(i);
        }
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        MyYhread m=new MyYhread();
        m.start();
        m.interrupt();//设置中断标记
    }
}
//阻塞状态被中断一次后中断标记会被清除,下次再进入阻塞时不会被打断
/*
0
1
睡眠被终止了  //这里直接打印这句话没有停顿
2
3
4   //这里停顿了一秒
5
6
 */

class MyYhread extends Thread {
    @Override
    public void run() {
        //设置中断标记
        Thread.currentThread().interrupt();
        //判断是否处于中断,并清除
        System.out.println(interrupted());//true
        System.out.println(interrupted());//false
        //因为第一次调用interrupted时中断已经被清除所以第二次为false
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        MyYhread m=new MyYhread();
        m.start();
    }
}

Runnable接口

通过实现Runnable接口来实现多线程:

构造方法

Thread(Runnable target,String name)

target:实现了Runnable 接口的类的对象,也叫做资源类对象
name:给线程起名字

特点

通过实现Runnable来实现多线程的方法中,可以通过只创建一个资源类对象然后通过构造方法创建多个线程类的方式来让多个线程共享资源。如下:

public static void main(String[] args){
        //创建资源类对象
        MyYhread m = new MyYhread();
        //用同一个资源类对象创建多个线程,实现资源的共享
        //被共享的变量需要声明在资源类的成员位置
        Thread t1 = new Thread(m, "窗口A");
        Thread t2 = new Thread(m, "窗口B");
        Thread t3 = new Thread(m, "窗口C");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }

实例:

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

class MyYhread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                //等待一秒,因为没有继承Thread,所以要通过Thread类来调用方法
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //getName方法同样不能直接使用,因为是非静态方法所以需要使用Thread.currentThread()来调用
            System.out.print(Thread.currentThread().getName()+"=");
            System.out.println(new SimpleDateFormat("hh:mm:ss").format(new Date()));
        }
    }

}
public class Test {
    public static void main(String[] args) throws Exception {
        MyYhread m=new MyYhread();
        Thread t1=new Thread(m, "当前时间");
        t1.start();

    } 
}
/*
当前时间=05:58:18
当前时间=05:58:19
当前时间=05:58:20
当前时间=05:58:21
当前时间=05:58:22
*/

多线程的安全问题

多线程安全的隐患条件

  • 程序是多线程环境
  • 有共享数据
  • 多条语句对共享数据进行操作

先看如下代码:

class MyYhread implements Runnable {
    private int i=3;
    @Override
    public void run() {
        System.out.print("开始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print(" *"+i--+"* ");
        System.out.println("结束");
    }
}

public class Test {
    public static void main(String[] args){
        MyYhread m = new MyYhread();
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        Thread t3 = new Thread(m);
        t1.start();
        t2.start();
        t3.start();
    }
}

程序设计中理想的输出结果应该是:

开始 *3* 结束
开始 *2* 结束
开始 *1* 结束

但是实际运行确是这样的:

开始开始开始 *3* 结束
 *2* 结束
 *2* 结束

因为多条线程同时访问代码的原因导致输出相同的数值并且开始和结束不连贯,于是我们需要给代码加上同步机制来保证不会出现多条线程同时访问代码的情况发生。

synchronized同步锁

同步代码块

在资源类中对容易发生问题的代码使用如下格式

synchronized (Object o ) {
            //代码段
        }

被synchronized括起来的部分会被加同步锁,当一个线程执行该段代码时会将该段代码锁住防止其他线程访问,当执行完毕后再解锁代码让下一个线程进来。
需要注意:

  1. synchronized 的参数中需要锁同一个对象,可以在资源类的成员位置创建一个Object对象,或直接使用this锁定当前类对象,通常也可以使用 当前类.class 锁定当前类的class对象。只要是同一个对象就可以,可以使用任意类的对象。
  2. 不能使用匿名对象,那样相当于每条线程进来都创建了一个新的对象,锁定的就不是同一个对象了。
  3. 使用同步代码块的时候应尽可能少的将需要同步的代码写入其中,因为当一个线程进来锁住代码段的时候,其他线程会处于阻塞状态。这样代码执行的效率就会降低。
class MyYhread implements Runnable {
    private int i=3;
    @Override
    public void run() {
        //添加同步锁
        synchronized (MyYhread.class) {
            System.out.print("开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print(" *"+i--+"* ");
            System.out.println("结束");
        }
    }
}

public class Test {
    public static void main(String[] args){
        MyYhread m = new MyYhread();
        //循环创建三个线程并启动
        for (Integer i = 0; i < 3; i++) {
            new Thread(m).start();;
        }
    }
}
/*
开始 *3* 结束
开始 *2* 结束
开始 *1* 结束
*/

同步方法

synchronized可以加在方法上如下:

public synchronized void run()

此时方法内的代码都会被加锁。

但是需要注意,同步方法只能给同一个资源类对象创建的线程加锁。如果创建线程时是不同的资源类对象创建的线程则不会起作用。

class MyYhread implements Runnable {
    @Override
    //同步方法
    public synchronized void run() {
        System.out.print(Thread.currentThread().getName()+":开始 ");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":结束 ");
    }
}

public class Test {
    public static void main(String[] args){
        MyYhread m = new MyYhread();
        //使用同步方法时需要注意必须是同一资源类对象创建的多个线程才会有同步作用
        Thread t1 = new Thread(m, "A");
        Thread t2 = new Thread(m, "B");
        Thread t3 = new Thread(m, "C");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}
/*
A:开始 A:结束 
C:开始 C:结束 
B:开始 B:结束 
*/

静态同步方法

静态方法是相对于类的所以静态同步方法中synchronized 锁住的是当前类的class对象

class MyYhread implements Runnable {
    private static int i=3;
    @Override
    public  void run() {
        show();
    }
    //静态同步方法
    private synchronized static void show() {
        System.out.print("开始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print(" *"+i--+"* ");
        System.out.println("结束");

    }
}

集合中的同步

集合的工具类Collections中提供了同步方法可以给线程不安全的集合添加同步锁。

线程安全的类
StringBuffer
Vector
Hashtable

Collections中提供的同步方法(都是静态方法)
方法名返回值(集合)
synchronizedCollection(Collection< T > c)Collection< T >
synchronizedList(List< T > list)List< T >
synchronizedSet(Set< T > s)Set< T >
synchronizedMap(Map< K,V > m)Map< K,V >

Lock锁

Lock是个接口,提供了更为广泛的锁定操作

ReentrantLock

Lock锁的一个实现类,提供加锁lock()和释放锁unlock()的操作

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

class MyYhread implements Runnable {
    private static int i = 3;
    //加锁前需要创建一个Lock锁对象
    private Lock lock=new ReentrantLock();
    //ReentrantLock是Lock接口的一个实现类
    @Override
    public void run() {
        try {
            lock.lock();//加锁
            System.out.print("开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print(" *" + i-- + "* ");
            System.out.println("结束");
        } finally {
            if(lock!=null) {
                lock.unlock();//释放锁
                //这一步必须有,不然其他线程会一直处于阻塞状态
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyYhread m = new MyYhread();
        // 循环创建三个线程并启动
        for (Integer i = 0; i < 3; i++) {
            new Thread(m).start();
            ;
        }
    }
}
/*
开始 *3* 结束
开始 *2* 结束
开始 *1* 结束
 */

注意:在Lock锁中不能使用wait(long timeout) 会抛出IllegalMonitorStateException异常。


案例

模拟电影院的售票系统

问题分析

多个线程出售100张票,票的总量应该是固定的。

如果通过继承Thread类来实现多线程则需要将总票数在成员位置设置为静态的:

private static int tickets = 100 ; 

如果是通过实现Runnable接口来实现多线程则只需创建一个资源类的对象然后用同一对象创建多个线程,不需要设置静态。

为了更加真实给售票加入延迟操作

但是出现同一张票被出售多次和0票的情况,这是因为CPU处理问题的原子性操作+多线程的随机性导致的。在同一时刻代码被多个线程执行导致了出现数据异常的情况,程序缺乏安全性,需要解决安全性问题。

class MyYhread implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
            }
            if (tickets<=0) {
                break;
            } 
        }
    }

}

public class Test {
    public static void main(String[] args) throws Exception {
        MyYhread m = new MyYhread();
        // 对同一个自定义类进行操作
        Thread t1 = new Thread(m, "窗口A");
        Thread t2 = new Thread(m, "窗口B");
        Thread t3 = new Thread(m, "窗口C");
        t1.start();
        t2.start();
        t3.start();
    }

}
/*
窗口C正在出售第99张票
窗口B正在出售第100张票
窗口A正在出售第100张票   //出现同票的情况
窗口C正在出售第98张票
窗口B正在出售第97张票
窗口A正在出售第97张票
窗口C正在出售第96张票
.......
窗口A正在出售第2张票
窗口B正在出售第0张票
窗口C正在出售第1张票     //出现0票的情况
*/

解决安全问题添加同步锁

class MyYhread2 implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //同步代码块
            synchronized (this) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                }
                if (tickets <= 0) {
                    break;
                }
            }
        }
    }
}

public class Test2 {
    public static void main(String[] args) throws Exception {
        MyYhread2 m = new MyYhread2();
        // 对同一个自定义类进行操作
        Thread t1 = new Thread(m, "窗口A");
        Thread t2 = new Thread(m, "窗口B");
        Thread t3 = new Thread(m, "窗口C");
        t1.start();
        t2.start();
        t3.start();
    }

}

此时程序就可以按预期执行了。

注意在需要进行循环操作的多线程中:

  1. synchronized 同步代码块在当前程序中不要将Thread.sleep(100); 包含在代码块中,sleep()方法不会释放锁,会导致包含sleep方法的同步代码块中进入的第一个线程难以被释放锁,其他的线程会一直处于阻塞状态。
  2. 如果要在同步代码块中包含睡眠操作可以用wait(long timeout) 代替sleep,wait方法会立即释放锁。

  3. 使用Lock锁的时候一样lock方法应该位于sleep方法之后执行,不然会出现一个线程长时间被锁住的情况。

  4. 在Lock锁中不能使用wait(long timeout) 会抛出IllegalMonitorStateException异常。

生产消费者模式

虽然通过同步锁解决了多线程的安全问题但是同时又出现了新的问题:
1. 程序的效率降低了。
2. 会产生死锁

死锁

两个或两个以上的线程,在执行的过程中出现互相等待的情况,就叫做死锁!


为了解决死锁,就需要在编程时使用生产消费者模式

生产者线程消费者线程
不断的产生数据不断的处理数据

注意事项

  1. 要保证生产者线程和消费者线程针对同一个对象进行操作,只创建一个资源类对象。
  2. 最好将资源类单独创建,然后生产者线程和消费者线程也单独建立类

等待唤醒机制

  为了解决,生产者线程还没有生产出线程,消费者线程就获取,获取到的都是null。和消费者线程执行太快,重复访问同样数据的问题使用等待唤醒机制。

notify()

唤醒一个正在等待该对象的线程。

实现等待唤醒机制的步骤:

  1. 在资源类中添加一个判断变量,当值为true时表示此时有数据,为false则没有数据。
  2. 在生产者线程中添加判断,如果有数据则wait() 等待消费者线程消费。如果没有数据则生产数据并将判断变量置于true,然后将消费者线程唤醒notify()
  3. 在消费者线程中判断如果没有数据则wait() 等待生产者生产线程,如果有数据则消费线程并将判断变量置于false,然后唤醒生产者线程notify()

案例:

/**
 * 生产者线程
 */
class Producer implements Runnable {
    Student s;

    // 要保证生产者线程和消费者线程针对同一对象进行操作。
    public Producer(Student s) {
        super();
        this.s = s;
    }

    private int i = 0;

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                // 添加同步防止数据出错
                if (s.flag) {
                    // 如果有数据的话
                    try {
                        s.wait();// 等待
                        // 注意需要使用同步锁中的对象调用
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 如果没有数据则产生数据
                if (i % 3 == 0) {
                    s.setName("张三");
                    s.setAge(18);
                } else if (i % 3 == 1) {
                    s.setName("李四");
                    s.setAge(21);
                } else if (i % 3 == 2) {
                    s.setName("王五");
                    s.setAge(35);
                }
                i++;
                s.flag = true;// 将标记改为有数据
                s.notify();// 唤醒正在等待的消费者线程

            }
        }
    }
}

/**
 * 消费者线程
 */
class Consumer implements Runnable {
    Student s;

    public Consumer(Student s) {
        super();
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                // 添加同步,防止数据出错
                if (!s.flag) {// 如果没有数据
                    try {
                        s.wait();
                        // 注意需要使用同步锁中的对象调用
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 如果有数据则执行输出
                System.out.println(s.getName() + ":" + s.getAge());
                // 更改标记为没有数据
                s.flag = false;
                // 唤醒生产者线程
                s.notify();

            }

        }
    }
}

/**
 * 资源类
 */
class Student {
    private String name;
    private int age;
    boolean flag; // 标记变量

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

/**
 * 测试类
 */
public class Test3 {
    public static void main(String[] args) {
        // 创建统一的资源类对象
        Student s = new Student();
        // 创建线程类对象
        Producer p = new Producer(s);
        Consumer c = new Consumer(s);
        // 创建线程
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        // 启动线程
        t1.start();
        t2.start();
    }
}
/*
李四:21
王五:35
张三:18
李四:21
王五:35
张三:18
李四:21
王五:35
张三:18
李四:21
王五:35
张三:18
李四:21
王五:35
张三:18
李四:21...............
 */

ThreadGroup线程组

表示一个线程的集合。此外线程组也可以包含其他线程组

注意区别于线程池(线程池可以重复使用,而线程组执行完毕就会被回收)

方法

ThreadGroup(String name)

构造一个新的线程组并起一个名字,不起名字的话默认是main

Thread(ThreadGroup group, Runnable target, String name)

Thread的构造方法将线程添加到线程组

  • group:线程组对象
  • target:线程类对象
  • name:线程的名字

getThreadGroup()

Thread的方法返回当前线程所在的线程组

getName()

返回当前线程组的名字

所有线程默认的线程组名称都是main


ExecutorService线程池

是一个接口,可以执行异步任务,线程池可以重复使用。

Executors

一个线程池的工厂类提供创建线程池实例的方法

  方法

newFixedThreadPool(int nThreads)

创建一个包含指定个数线程的线程池。

submit(Runnable task)

将一个实现Runnable接口的线程类提交给线程池,提交的同时线程就开始执行了,该方法返回的是一个Future

submit(Callable<T> task) 

提交一个可执行的Callable多线程类。

shutdown()

关闭线程池

Future

表示异步计算的结果

get()

获取结果

案例

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

class MyRunnable implements Runnable {

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

}

public class Test4 {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService ex = Executors.newFixedThreadPool(2);
        //提交两条线程
        ex.submit(new MyRunnable());
        ex.submit(new MyRunnable());
        //关闭线程池
        ex.shutdown();
    }
}
/*
pool-1-thread-1 : 0
pool-1-thread-2 : 0
pool-1-thread-1 : 1
pool-1-thread-1 : 2
pool-1-thread-1 : 3
pool-1-thread-1 : 4
pool-1-thread-2 : 1
pool-1-thread-2 : 2
pool-1-thread-2 : 3
pool-1-thread-2 : 4
*/

线程池默认的名字

pool-1-thread-1
线程池-池数(一般只有一个)-线程类对象的描述-编号(从1开始)

Callable< V >接口

多线程的第三种实现方式

call()
  • 计算一个结果并返回,如果无法计算,就会抛出一个异常。
  • 需要在该方法中写入多线程中的耗时操作
  • Callable泛型的类型是call() 方法的返回值的类型。
  • 如果只是单纯执行多线程,不需要返回值则返回值可以为null。

案例:

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

class MyCallable implements Callable<Integer> {
    // 定义一个计算任意数阶乘的Callable多线程
    private int num;

    // 定义一个变量
    public MyCallable(int num) {
        super();
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        int factorial = 1;
        for (int i = 1; i <= num; i++) {
            factorial *= i;
        }
        return factorial;
    }

}

public class Test5 {

    public static void main(String[] args) throws Exception {
        // 创建线程组
        ExecutorService pool = Executors.newFixedThreadPool(2);
        // 提交线程任务
        Future<Integer> f1 = pool.submit(new MyCallable(10));
        Future<Integer> f2 = pool.submit(new MyCallable(4));
        // 获取结果
        int i1 = f1.get();
        int i2 = f2.get();
        System.out.println("10!:" + i1);
        System.out.println("4!:" + i2);
        // 关闭线程池
        pool.shutdown();
    }
}
/*
 * 10!:3628800 4!:24
 */

异常

llegalThreadStateException

非法状态异常,同一个线程只能被执行一次。

InterruptedException

中断异常,线程在等待,睡眠的时候如果等待和睡眠的状态被打断则抛出此异常。

IllegalMonitorStateException

在同步的代码中使用非同步的对象调用了wait()notify()notifyAll() 会抛出此异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值