1.概念
线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程中的实际运作单位。是应用软件中相互独立,可以同时运行的功能。
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时进行
2.多线程实现方式
1.继承Thread类的方式进行实现
1.自己定义一个类继承Thread
2.重写run方法
3.创建子类的对象,并启动线程
package com.hzj.a01threadcase1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 1.自己定义一个类继承Thread
* 2.重写run方法
* 3.创建子类的对象,并启动线程
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程一");
t2.setName("线程二");
// 开启线程
t1.start();
t2.start();
}
}
package com.hzj.a01threadcase1;
public class MyThread extends Thread{
@Override
public void run() {
// 书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "Hello World");
}
}
}
2.实现Runnable接口的方式进行实现
1.自己定义一个类实现Runnable接口
2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程
package com.hzj.a02threadcase2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程第二次启动方式:
* 1.自己定义一个类实现Runnable接口
* 2.重写里面的run方法
* 3.创建自己的类的对象
* 4.创建一个Thread类的对象,并开启线程
* */
// 创建MyRun的对象
// 表示多线程要执行的任务
MyRun mr = new MyRun();
// 创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("线程1");
t2.setName("线程2");
// 开启线程
t1.start();
t2.start();
}
}
package com.hzj.a02threadcase2;
public class MyRun implements Runnable{
@Override
public void run() {
// 书写线程要执行的代码
for (int i = 0; i < 100; i++) {
// 获取到当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName() + "Hello World");
}
}
}
3.利用Callable接口和Future接口方式实现
package com.hzj.a03Threadcase3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式:
* 特点:可以获取到多线程运行的结果
* 1.创建一个类MyCallable实现Callable接口
* 2.重写call(是有返回值的,表示多线程运行的结果)
* 3.创建MyCallable的对象(表示多线程要执行的任务)
* 4.创建FutureTask的对象(作用管理多线程运行的结果)
* 5.创建Thread类的对象,并启动(表示线程)
* */
// 创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
// 创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
// 创建线程对象
Thread t1 = new Thread(ft);
// 启动线程
t1.start();
// 获取多线程运行的结果
Integer result = ft.get();
System.out.println("result = " + result);
}
}
package com.hzj.a03Threadcase3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 求1~100之间的和
int sum = 0;
for (int i = 0; i < 100; i++) {
sum = sum + i;
}
return sum;
}
}
3.常见的成员方法
方法名称 | 说明 |
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 1-10 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
基础方法
package com.hzj.a04threadmethod;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
* String getName()
* 如果没用给线程设置名称,线程有默认的 格式: Thread-X (从0开始)
* void setName(String name)
* 构造方法也可以设置名字,需要继承Thread的构造方法
* static Thread currentThread()
* 当JVM虚拟机启动之后,会自动的启动多条线程
* 其中有一条线程就叫做main线程
* 它的作用就是取调用main方法,并执行里面的代码
*
* static void sleep(long time)
* 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
* 方法的参数就表示睡眠的时间,单位毫秒
* 当时间到了之后,线程会自动的醒来,继续执行下面的代码
* */
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");
t1.start();
t2.start();
// 哪条线程执行到这个方法,此时获取的就是哪条线程的对象
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);
System.out.println(321);
Thread.sleep(10000);
System.out.println(123);
}
}
优先级设置
package com.hzj.a05threadmethod2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* setPriority(int newPriority) 默认是5 数字越大,优先级越高
* final int getPriority()
*
* */
// 创建线程要执行的参数对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread t1 = new Thread(mr, "飞机");
Thread t2 = new Thread(mr, "坦克");
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
守护线程
package com.hzj.a06threadmethod3;
public class ThreadDemo {
public static void main(String[] args) {
/*
* final void setDaemon(boolean on) 设置为守护线程
* 当其它的非守护线程执行完毕之后,守护线程会陆续结束,即便线程内部的代码没用执行完也会停止,但不是立即停止
*
* */
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("神子");
t2.setName("只狼");
// 把第二个线程设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
礼让线程
package com.hzj.a07threadmethod4;
public class ThreadDemo {
public static void main(String[] args) {
/*
* public static void yield 出让线程/礼让线程 可以让结果尽可能的均匀
*
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飞机");
t2.setName("坦克");
t1.start();
t2.start();
}
}
package com.hzj.a07threadmethod4;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " " + i);
// 表示出让当前CPU的执行权
Thread.yield();
}
}
}
插入线程
package com.hzj.a08threadmethod5;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
* public final void join() 插入线程/插队线程
*
* */
MyThread t = new MyThread();
t.setName("土豆");
t.start();
// 把t线程插入到当前线程之前
t.join();
//执行在main线程当中的
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
4.线程生命周期
5.同步代码块
把操作共享数据的代码锁起来
特点:
1.锁默认打开,有应该线程进去了,锁自动关闭
2.里面的代码全部执行完毕,线程出来,锁自动打开
public class MyThread extends Thread{
// 表示这个类所有的对象,都共享ticket数据
static int ticket = 0;
// 锁对象,必须是唯一的
static Object object = new Object();
@Override
public void run() {
while (true) {
// 同步代码块
synchronized (object){
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!");
} else {
break;
}
}
}
}
}
6.同步方法
就是把synchronized关键字加到方法上
格式: 修饰符 synchronized 返回值类型 方法名(方法参数) {...}
特点:
1.同步方法是锁住方法里面所有的代码
2.锁对象不能自己指定 当方法是非静态是,锁对象是this,即调用者对象。当方法是静态时,锁对象是当前类的字节码文件对象。
public class MyRunnable implements Runnable{
int ticket = 0;
@Override
public void run() {
// 循环
// 同步代码块
while (true) {
if (method()) {
break;
}
}
}
private synchronized boolean method() {
if (ticket == 100) {
return true;
} else {
ticket++;
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!");
}
return false;
}
}
7.Lock锁
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,采用它的实现类ReentrantLock来实例化
ReentrantLock():创建一个ReentrantLock的实例
public class MyThread extends Thread{
// 表示这个类所有的对象,都共享ticket数据
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
lock.lock();
try {
// 同步代码块
if (ticket < 100) {
Thread.sleep(100);
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!");
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
}
}
8.等待唤醒机制
1.常用方式
public class Cook extends Thread{
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
if (Desk.foodFlag == 1) {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("厨师做了一碗面条");
Desk.foodFlag = 1;
Desk.lock.notifyAll();
}
}
}
}
}
}
public class Foodie extends Thread{
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 判断桌子上是否有食物
if (Desk.foodFlag == 0) {
// 如果没有,就等待
try {
Desk.lock.wait(); // 让当前线程和锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 吃的总数-1
Desk.count--;
// 如果有,就吃
System.out.println("正在吃,还能吃" + Desk.count + "碗!!!");
// 吃完之后,唤醒厨师
Desk.lock.notifyAll(); // 唤醒和这把锁绑定的所有线程
// 修改桌子状态
Desk.foodFlag = 0;
}
}
}
}
}
}
public class Desk {
/* 控制生产者和消费者的执行 */
// 是否有食物 0:没有 1:有
public static int foodFlag = 0;
// 总个数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
public class ThreadDemo {
public static void main(String[] args) {
Cook c = new Cook();
Foodie f = new Foodie();
c.setName("厨师");
f.setName("食客");
c.start();
f.start();
}
}
2.阻塞队列方式
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
queue.put("食物");
System.out.println("厨师放了一份食物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
String take = queue.take();
System.out.println(take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 生产者和消费者必须使用同一个阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
c.setName("厨师");
f.setName("食客");
c.start();
f.start();
}
}
9.线程池
1.原理
1.创建一个池子,池子中是空的
2.提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要在创建新的线程,直接复用已有的线程即可。
3.但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
4.当核心线程满时,再提交任务就会排队,当队伍也满时,会创建临时线程,当临时线程也满时,会触发任务拒绝策略。
2.代码实现
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
newCachedThreadPool():创建一个没有上限的线程池(int类型的最大数量,21亿)
newFixedThreadPool(int nThreads):创建有上限的线程池
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 获取线程池对象
// ExecutorService pool1 = Executors.newCachedThreadPool(); // 无上限
ExecutorService pool1 = Executors.newFixedThreadPool(3);
// 提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
// 销毁线程池
pool1.shutdown();
}
}
3.拒绝策略
当核心线程,临时线程,等待队列全部占满之后会触发拒绝策略
任务拒绝策略 | 说明 |
ThreadPoolExecuter.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecuter.DiscardPolicy | 丢弃任务,但是不抛出异常,不推荐 |
ThreadPoolExecuter.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecuter.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
4.自定义线程池
public class MyThreadPoolDemo1 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数,不能小于0
6, // 最大线程数,大于等于核心线程数
60, // 空闲线程最大存活时间 值
TimeUnit.SECONDS, // 空闲线程最大存活时间 单位
new ArrayBlockingQueue<>(3), // 任务队列,不能为null
Executors.defaultThreadFactory(), // 创建线程工厂 不能为null
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
pool.submit(new MyCallable());
}
}