Java多线程的具体介绍与使用笔记小结

一、基本概念:线程、进程

1.1、进程与线程的具体介绍

线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间à它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

进程(process),是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

  • 运行中的QQ,运行中的MP3播放器
  • 程序是静态的,进程是动态的进程作为资源分配的单位,系统在运行时会为每个
  • 进程分配不同的内存区域

1.2、对于CPU而言的理解

单核CPU和多核CPU的理解

  • 单核CPU,其实是因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是一种假的多线程,收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
  • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
  • 一个 Java 应用程序 java.exe,其实至少有三个线程:main() 主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

  • 并行:多个 CPU 同时执行多个任务。比如:多个人同时做不同的事。
  • 并发:一个 CPU (采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

1.3、为什么要使用多线程

  • 背景:

以单核CPU 为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

多线程程序的优点:

  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  • 提高计算机系统 CPU 的利用率
  • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

二、线程的创建与使用

2.1、如何去创建和启动一个线程

  • Java语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来体现。
  • Thread 类的特性:

每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的,经常把 run() 方法的主体称为线程体通过该 Thread 对象的 start() 方法来启动这个线程,而非直接调用 run()

2.2、Thread类的具体分析

构造器:

Thread():创建新的 Thread 对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了 Runnable 接口中的 run 方法
Thread(Runnable target, String name):创建新的 Thread 对象
创建线程的两种方式:
JDK1.5 之前创建新执行线程有两种方法:
继承 Thread 类的方式
实现 Runnable 接口的方式
方式一:继承 Thread 类
定义子类继承 Thread 类。
子类中重写 Thread 类中的 run 方法。
创建 Thread 子类对象,即创建了线程对象。
调用线程对象 start 方法:启动线程,调用 run 方法。

代码演示:

* 多线程的创建,方式一:继承于Thread类
 * 1. 创建一个继承于Thread类的子类
 * 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
 * 3. 创建Thread类的子类的对象
 * 4. 通过此对象调用start()
 * 例子:遍历100以内的所有的偶数

//1、创建一个继承于Thread类的子类
class Thread01 extends Thread {
    //2、重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3、创建Thread类的子类的对象
        Thread01 thread01 = new Thread01();
        //4.通过此对象调用start()
        thread01.start();

        //创建第二个线程
        Thread01 thread02 = new Thread01();

        //注意:这里我们不能直接手动调用 run()方法
        //thread01.run();

        //注意:当我们再次调用start()时会直接报错:IllegalThreadStateException,所以一个线程只能用一次
        //thread01.start();

        thread02.start();

        //当前操作仍然是在main线程中执行
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i + "主线程被执行了");
            }

        }
    }
}

方式二:实现 Runnable 接口

  • 定义子类,实现 Runnable 接口。
  • 子类中重写 Runnable 接口中的 run 方法。
  • 通过 Thread 类含参构造器创建线程对象。
  • 将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造器中。
  • 调用 Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法。

2.3、两种实现方式的联系与区别

联系:

Thread 内部其实也是实现了 Runnable 接口

区别:

  • 继承 Thread: 线程代码存放 Thread子类 run 方法中。
  • 实现 Runnable: 线程代码存在接口的子类的 run 方法。

实现方式的好处:

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

2.4、注意事项:

  • 如果自己手动调用 run() 方法,那么就只是普通方法,没有启动多线程模式。
  • run() 方法由 JVM 调用,什么时候调用,执行的过程控制都有操作系统的 CPU 调度决定。
  • 想要启动多线程,必须调用 start 方法。
  • 一个线程对象只能调用一次 start() 方法启动,如果重复调用了,则将抛出以上的异常 IllegalThreadStateException。

2.5、代码测试

需求:创建两个分线程,让其中一个线程输出1-100之间的偶数,另一个线程输出1-100之间的奇数。

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

        //线程一:偶数
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + "线程一偶数的执行数:" + i);
                    }
                }
            }
        }.start();

        //线程二:奇数
        new Thread() {
            @Override
            public void run() {

                for (int i = 0; i < 100; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + "线程二奇数的执行数:" + i);
                    }
                }
            }
        }.start();
    }
}

2.6、Thread类的相关方法

void start(): 启动线程,并执行对象的 run() 方法

run(): 线程在被调度时执行的操作String

getName(): 返回线程的名称

void setName(String name): 设置该线程名称

static Thread currentThread(): 返回当前线程。在 Thread 子类中就是 this,通常用于主线程和 Runnable 实现类static void yield(): 线程让步

暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程若队列中没有同优先级的线程,忽略此方法

join() : 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止

低优先级的线程也可以获得执行

static void sleep(long millis):(指定时间:毫秒)

令当前活动线程在指定时间段内放弃对 CPU 控制,使其他线程有机会被执行,时间到后重排队。

抛出 InterruptedException 异常

stop(): 强制线程生命期结束,不推荐使用

boolean isAlive(): 返回 boolean,判断线程是否还活着

2.7、测试以上方法:

* @description: 多线程具体方法的使用
 * @date 2021/4/16 18:32
 */

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

                //设置线程睡眠时间
               /* try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            //当满足当前条件时,终止线程
            /*if (i % 20 == 0) {
                yield();
            }*/

        }

    }
}

public class ThreadMethodTest {
    public static void main(String[] args) {
        MyThread thread = new MyThread();

        thread.start();
        //给主线程命名
        thread.setName("我是线程一号");
        //将分线程优先级设置成最大
        thread.setPriority(Thread.MAX_PRIORITY);

        Thread.currentThread().setName("我是主线程");
        //将主线程的优先级设置成最小
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }
            //join()表示:线程A中调用线程B的join()方法,那么此时线程A进入阻塞状态,直到线程B完全执行完以后,线程A才
            //结束阻塞状态。
            /*if (i == 20) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }*/

        }
        //isAlive():判断当前线程是否存活
//        System.out.println("当前线程是否存活:" + thread.isAlive());

    }
}

结果:

使用 sleep() 方法时的结果

使用 Join方法时的结果

三、线程的生命周期

JDK中用Thread.State类定义了线程的几种状态:

要想实现多线程,必须在主线程中创建新的线程对象。Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

新建: 当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

就绪: 处于新建状态的线程被 start() 后,将进入线程队列等待 CPU 时间片,此时它已具备了运行的条件,只是没分配到 CPU 资源

运行: 当就绪的线程被调度并获得 CPU 资源时,便进入运行状态, run() 方法定义了线程的操作和功能

阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态

死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

生命周期结构图

四、线程的同步

4.1、为什么要线程同步

  • 多个线程执行的不确定性引起执行结果的不稳定
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据

4.2、举例说明:

创建三个窗口卖票,总票数为100张.使用继承Thread类的方式

class Window extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(getName() + "买票编码号为:" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

public class TicketWindowTest {
    public static void main(String[] args) {
        //创建线程
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        //设置线程名字
        w1.setName("窗口一:");
        w2.setName("窗口二:");
        w3.setName("窗口三:");

        //开启线程
        w1.start();
        w2.start();
        w3.start();
    }
}

正常情况下的效果:

不正常的情况下的效果:

上段代码出现的漏洞:

问题: 卖票过程中,出现了重票、错票,出现了线程的安全问题

原因: 当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票

解决: 当一个线程 a 在操作 ticket 的时候,其他线程不能参与进来。直到线程 a 操作完 ticket 时,其他线程才可以开始操作 ticket。这种情况即使线程a出现了阻塞,也不能被改变

4.3、Synchronized的使用方法

Java对于多线程的安全问题提供了专业的解决方式:同步机制

 1、同步代码块:
    synchronized (对象){
          // 需要被同步的代码;
    }

2、synchronized还可以放在方法声明中,表示整个方法为同步方法。
	例如:
    public synchronized void show (String name){
            ….
   }

4.3、 同步机制中的锁

同步锁机制:

在《Thinking in Java》中,是这么说的: 对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

synchronized的锁是什么?

  • 任意对象都可以作为同步锁,所有对象都自动含有单一的锁(监视器)
  • 同步方法的锁: 静态方法(类名.class)、非静态方法(this)
  • 同步代码块: 自己指定,很多时候也是指定为this或类名.class

注意:

  • 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
  • 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

4.4、同步的范围

如何找问题,即代码是否存在线程安全?(非常重要)

  • 明确哪些代码是多线程运行的代码
  • 明确多个线程是否有共享数据
  • 明确多线程运行代码中是否有多条语句操作共享数据

如何解决呢?(非常重要)

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中

注意:

  • 范围太小: 没锁住所有有安全问题的代码
  • 范围太大: 没发挥多线程的功能。

对于购票代码的 bug 改进

class Windows02 implements Runnable {

    private int ticke = 100;

    @Override
    public void run() {
        while (true) {
            //使用同步代码方式来解决线程安全问题,this:表示当前对象:【Windows02】
//            synchronized (this) {
//                if (ticke > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName() + "购票号码为:" + ticke);
//                    ticke--;
//                } else {
//                    break;
//                }
//            }

            show();
        }
    }

    //这里我们直接使用【同步方法】的方式来处理线程安全问题
    //在方法中加入:synchronized的效果等同上面的this,因为指代的都是当前对象,只是在同步方法中帮我们做了隐试操作。
    private synchronized void show() {
        if (ticke > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "购票号码为:" + ticke);
            ticke--;
        }
    }
}

public class TickeWindowTest02 {
    public static void main(String[] args) {
        Windows02 windows = new Windows02();

        Thread thread = new Thread(windows);
        Thread thread01 = new Thread(windows);
        Thread thread02 = new Thread(windows);

        thread.setName("窗口一:");
        thread01.setName("窗口二:");
        thread02.setName("窗口三:");

        thread.start();
        thread01.start();
        thread02.start();

    }
}

测试结果:

4.5、释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到 break、return 终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁。

4.6、不释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()、Thread.yield() 方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁(同步监视器)。

应尽量避免使用 suspend() 和 resume() 来控制线程

4.7、线程的死锁的问题

1、 什么事死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

代码演示:

//死锁的演示
class A {
    public synchronized void foo(B b) { //同步监视器:A类的对象:a
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 进入了A实例的foo方法"); // ①
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException ex) {
//			ex.printStackTrace();
//		}
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 企图调用B实例的last方法"); // ③
        b.last();
    }

    public synchronized void last() {//同步监视器:A类的对象:a
        System.out.println("进入了A类的last方法内部");
    }
}

class B {
    public synchronized void bar(A a) {//同步监视器:b
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 进入了B实例的bar方法"); // ②
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException ex) {
//			ex.printStackTrace();
//		}
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 企图调用A实例的last方法"); // ④
        a.last();
    }

    public synchronized void last() {//同步监视器:b
        System.out.println("进入了B类的last方法内部");
    }
}

public class DeadLock implements Runnable {
    A a = new A();
    B b = new B();

    public void init() {
        Thread.currentThread().setName("主线程");
        // 调用a对象的foo方法
        a.foo(b);
        System.out.println("进入了主线程之后");
    }

    public void run() {
        Thread.currentThread().setName("副线程");
        // 调用b对象的bar方法
        b.bar(a);
        System.out.println("进入了副线程之后");
    }

    public static void main(String[] args) {
        DeadLock dl = new DeadLock();
        new Thread(dl).start();
        dl.init();
    }
}

测试结果:概率性的出现

2、解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义尽量
  • 避免嵌套同步

3、什么是Lock锁

  • 从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用 Lock 对象充当。
  • java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁、释放锁。

4、具体如何使用:

5、synchronized 与 Lock 锁有何区别

Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized 是隐式锁,出了作用域自动释放

Lock 只有代码块锁,synchronized 有代码块锁和方法锁

使用 Lock 锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

Lock →同步代码块(已经进入了方法体,分配了相应资源) → 同步方法(在方法体之外)

五、线程的通信

5.1、方法介绍与注意事项:

wait() 与 notify() 和 notifyAll()

wait(): 令当前线程挂起并放弃 CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用 notify()或notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

notify(): 唤醒正在排队等待同步资源的线程中优先级最高者结束等待

notifyAll (): 唤醒正在排队等待资源的所有线程结束等待.

注意事项:

这三个方法只有在 synchronized 方法或 synchronized 代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException 异常。

因为这三个方法必须有锁对象调用,而任意对象都可以作为 synchronized 的同步锁,因此这三个方法只能在 Object 类中声明。

sleep() 和 wait() 有何不同之处:

相同点: 一旦执行方法,都可以使得当前的线程进入阻塞状态。

不同点:

两个方法声明的位置不同: Thread 类中声明 sleep() , Object 类中声明 wait()

调用的要求不同: sleep() 可以在任何需要的场景下调用。 wait() 必须使用在同步代码块或同步方法中

关于是否释放同步监视器: 如果两个方法都使用在同步代码块或同步方法中,sleep() 不会释放锁,wait()会释放锁。

案例一:

使用两个线程打印 1-100。线程1, 线程2 交替打印

class Number implements Runnable {
    private int number = 1;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                //因为现在使用的是当前对象,所以前面省略this.
                //如果使用的是其他对象,那么就用对象.的方式去调用该方法
                notify();
                if (number <= 100) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " :" + "打印数为" + number);
                    number++;
                    try {
                        //调用该方法时,线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread thread = new Thread(number);
        Thread thread01 = new Thread(number);
        thread.setName("线程一");
        thread01.setName("线程二");

        thread.start();
        thread01.start();
    }
}

执行结果:

六、JDK5.0新增的线程创建方式

新增方式一:实现Callable接口

与使用 Runnable 相比, Callable 功能更强大些

相比 run() 方法,可以有返回值

方法可以抛出异常

支持泛型的返回值需要

借助 FutureTask 类,比如获取返回结果

  • Future 接口

可以对具体 Runnable、Callable 任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask 是 Futrue 接口的唯一的实现类
FutureTask 同时实现了 Runnable, Future 接口。它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值

  • 新增方式二:使用线程池

没使用线程池: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
使用线程池后: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用,类似生活中的公共交通工具。

  • 好处:

提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize: 核心池的大小
maximumPoolSize: 最大线程数
keepAliveTime: 线程没有任务时最多保持多长时间后会终止

  • 线程池相关的API

JDK 5.0 起提供了线程池相关 API:ExecutorService 和 Executors
ExecutorService: 真正的线程池接口。常见子类 ThreadPoolExecutor
void execute(Runnable command) : 执行任务命令,没有返回值,一般用来执行 Runnable
Future submit(Callable task): 执行任务,有返回值,一般又来执行 Callable

  • void shutdown() : 关闭连接池

Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n): 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() : 创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n): 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

  • 代码演示:

创建线程的方式三:使用 Callable 接口

//1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer> {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Integer sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

创建线程方式四:使用线程池技术

class NumberThread implements Runnable{

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

class NumberThread1 implements Runnable{

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

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

到此这篇关于Java多线程的具体介绍与使用笔记小结的文章就介绍到这了,更多相关java多线程介绍与使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程atomic包介绍及使用方法

    引言 Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作.原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞. Atomic包介绍 在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段.Atomic包里的类基本都是使用Unsafe实现的包装类. 原子

  • Java 高并发二:多线程基础详细介绍

    本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍 1.什么是线程 2.线程的基本操作 3.守护线程 4.线程优先级 5.基本的线程同步操作 1. 什么是线程 线程是进程内的执行单元 某个进程当中都有若干个线程. 线程是进程内的执行单元. 使用线程的原因是,进程的切换是非常重量级的操作,非常消耗资源.如果使用多进程,那么并发数相对来说不会很高.而线程是更细小的调度单元,更加轻量级,所以线程会较为广泛的用于并发设计. 在Java当中线程的概念和操作系统级别线程的概念是类似的.事

  • java使用多线程读取超大文件

    接上次写的"JAVA读取超大文件".在读取超过10G的文件时会发现一次读一行的速度实在是不能接受,想到使用多线程+FileChannel来做一个使用多线程版本. 基本思路如下: 1.计算出文件总大小 2.分段处理,计算出每个线程读取文件的开始与结束位置 (文件大小/线程数)*N,N是指第几个线程,这样能得到每个线程在读该文件的大概起始位置 使用"大概起始位置",作为读文件的开始偏移量(fileChannel.position("大概起始位置"))

  • 使用java的HttpClient实现多线程并发

    说明:以下的代码基于httpclient4.5.2实现. 我们要使用java的HttpClient实现get请求抓取网页是一件比较容易实现的工作: public static String get(String url) { CloseableHttpResponseresponse = null; BufferedReader in = null; String result = ""; try { CloseableHttpClienthttpclient = HttpClient

  • JAVA多线程的使用场景与注意事项总结

    前言 我曾经对自己的小弟说,如果你实在搞不清楚什么时候用HashMap,什么时候用ConcurrentHashMap,那么就用后者,你的代码bug会很少. 他问我:ConcurrentHashMap是什么? -.- 编程不是炫技.大多数情况下,怎么把代码写简单,才是能力. 多线程生来就是复杂的,也是容易出错的.一些难以理解的概念,要规避.本文不讲基础知识,因为你手里就有jdk的源码. 线程 Thread 第一类就是Thread类.大家都知道有两种实现方式.第一可以继承Thread覆盖它的run方

  • Java Web项目中使用Socket通信多线程、长连接的方法

    很多时候在javaweb项目中我们需要用到Socket通信来实现功能,在web中使用Socket我们需要建立一个监听程序,在程序启动时,启动socket监听.我们的应用场景是在java项目中,需要外接如一个硬件设备,通过tcp通信,获取设备传上来的数据,并对数据做回应. 先看一下web的监听代码: import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class

  • Java多线程ThreadAPI详细介绍

    1.Thread的构造方法 package threadAPI; public class CreateThread { public static void main(String[] args) { Thread t1 = new Thread(); Thread t2 = new Thread(); t1.start(); t2.start(); System.out.println(t1.getName()); System.out.println(t2.getName()); } }

  • java向多线程中传递参数的三种方法详细介绍

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果.但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别.由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据.本文就以上原因介绍了几种用于向线程传递数据的方法,在下一篇文章中将介绍从线程中返回数据的方法. 欲先取之,必先予之.一般在使用线程时都需要有一些初始化数据,然后线程利用这些数据进行加工处理,并

  • Java多线程的具体介绍与使用笔记小结

    一.基本概念:线程.进程 1.1.进程与线程的具体介绍 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径. 若一个进程同一时间并行执行多个线程,就是支持多线程的 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小 一个进程中的多个线程共享相同的内存单元/内存地址空间à它们从同一堆中分配对象,可以访问相同的变量和对象.这就使得线程间通信更简便.高效.但多个线程操作共享的系统资源可能就会带来安全的隐患. 进程(process),是程序的

  • Java多线程Atomic包操作原子变量与原子类详解

    在阅读这篇文章之前,大家可以先看下<Java多线程atomic包介绍及使用方法>,了解atomic包的相关内容. 一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的.通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成) 在x86平台上,CPU提供了在指令执行期间对总线加锁的手段.

  • Java多线程学习笔记

    目录 多任务.多线程 程序.进程.线程 学着看jdk文档 线程的创建 1.继承Thread类 2.实现Runable接口 理解并发的场景 龟兔赛跑场景 实现callable接口 理解函数式接口 理解线程的状态 线程停止 线程休眠sleep 1.网路延迟 2.倒计时等 线程礼让yield 线程强制执行 观察线程状态 线程的优先级 守护线程 线程同步机制 1.synchronized 同步方法 2.同步块synchronized(Obj){} lock synchronized与lock 多任务.多

  • Java多线程编程安全退出线程方法介绍

    线程停止 Thread提供了一个stop()方法,但是stop()方法是一个被废弃的方法.为什么stop()方法被废弃而不被使用呢?原因是stop()方法太过于暴力,会强行把执行一半的线程终止.这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下 那我们该使用什么来停止线程呢 Thread.interrupt(),我们可以用他来停止线程,他是安全的,可是使用他的时候并不会真的停止了线程,只是会给线程打上了一个记号,至于这个记号有什么用呢

  • Java多线程的用法详细介绍

    Java多线程的用法详细介绍 最全面的Java多线程用法解析,如果你对Java的多线程机制并没有深入的研究,那么本文可以帮助你更透彻地理解Java多线程的原理以及使用方法. 1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( ); publi

  • Java多线程饥饿与公平介绍及代码示例

    如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为"饥饿".而该线程被"饥饿致死"正是因为它得不到CPU运行时间的机会.解决饥饿的方案被称之为"公平性" – 即所有线程均能公平地获得运行机会. 下面是本文讨论的主题: Java中导致饥饿的原因 在Java中,下面三个常见的原因会导致线程饥饿: 高优先级线程吞噬所有的低优先级线程的CPU时间. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续

  • Java多线程中不同条件下编写生产消费者模型方法介绍

    简介: 生产者.消费者模型是多线程编程的常见问题,最简单的一个生产者.一个消费者线程模型大多数人都能够写出来,但是一旦条件发生变化,我们就很容易掉进多线程的bug中.这篇文章主要讲解了生产者和消费者的数量,商品缓存位置数量,商品数量等多个条件的不同组合下,写出正确的生产者消费者模型的方法. 欢迎探讨,如有错误敬请指正 生产消费者模型 生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品.生产消费者模式

  • Java多线程Condition接口原理介绍

    Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的 Condition接口详解 Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁.Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的. Lock lock = new ReentrantL

随机推荐