Java多线程
一、什么是多线程?
线程是操作系统能够进行运算调度的最小单位。被包含在进程之中,是进程中的实际运作单位。
进程是程序基本执行实体。
而Java多线程则是指在一个Java程序中同时运行多个线程,每个线程可以独立执行不同的任务。多线程允许程序在同一时间内处理多个操作,从而提高程序的效率和响应速度。在Java中,线程是程序执行的最小单位,多线程编程可以充分利用多核处理器的计算能力,实现并发执行。
二、并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
三、Java中三种实现多线程的方法
1.自己定义一个类集成Thread类,重写run方法,创建子类的对象,并启动线程;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "HelloWorld");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 1. 自己定义一个类继承Thread
* 2. 重写run方法
* 3. 创建子类的对象,并启动线程
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1--->");
t2.setName("线程2--->");
//start()方法表示开启线程
t1.start();
t2.start();
}
}
2.自己定义一个类实现runnable接口,重写run方法,创建自己的类对象,创建一个Thread类的对象,并开启线程
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() + "HelloWorld!");*/
System.out.println(Thread.currentThread().getName() + "HelloWorld!");
}
}
}
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();
}
}
3.创建一个类MyCallable实现Callable接口,重写call (是有返回值的,表示多线程运行的结果),创建MyCallable的对象(表示多线程要执行的任务),创建FutureTask的对象(作用管理多线程运行的结果),创建Thread类的对象,并启动(表示线程)。
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1~100之间的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
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);
}
}
四、多线程中常见成员方法和其他线程
常见成员方法如下:
/*
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
static Thread currentThread() 获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程当中
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒
1 秒= 1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
*/
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
*/
关于其他线程
守护线程:将线程2设置守护线程来守护线程1。
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
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();
}
}
礼让线程(不常用):
public class MyThread extends Thread{
@Override
public void run() {//"飞机" "坦克"
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
//表示出让当前CPU的执行权
Thread.yield();
}
}
}
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();
}
}
插队线程:
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
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:土豆
//当前线程: main线程
t.join();
//执行在main线程当中的
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
五、线程安全和线程的生命周期
1.线程安全:例如,当有这样一个需求时,
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
可能发生线程安全问题,导致电影票超卖问题
解决办法:利用同步代码块,即synchronized关键字/利用lock锁,让程序串行化执行,防止出现超卖问题。
解决思路:加锁(注意死锁问题)
利用synchronized关键字解决如下
public class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
//1.循环
while (true) {
//2.同步代码块(同步方法)
if (method()) break;
}
}
//this
private synchronized boolean method() {
//3.判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100) {
return true;
} else {
//4.判断共享数据是否到了末尾,如果没有到末尾
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
}
return false;
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
利用同步方法完成
技巧:同步代码块
*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
利用lock加锁实现如下
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
//1.循环
while(true){
//2.同步代码块
//synchronized (MyThread.class){
lock.lock(); //2 //3
try {
//3.判断
if(ticket == 100){
break;
//4.判断
}else{
Thread.sleep(10);
ticket++;
System.out.println(getName() + "在卖第" + ticket + "张票!!!");
}
// }
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
用JDK5的lock实现
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
线程的生命周期
1.新建
2.就绪
3.运行
4.阻塞
5.死亡
注意:当线程执行sleep方法后苏醒时,并不会立即执行下一行代码,而是进入就绪状态,抢夺CPU的执行权
在API帮助文档中关于多线程状态描述如下:
线程池
一、什么叫线程池
线程池是一种多线程处理形式,用于在处理任务时优化线程的创建和管理。它预先创建一组线程,并将这些线程放入池中等待执行任务。当有任务需要执行时,线程池会分配一个空闲线程来执行该任务,任务完成后线程会返回池中等待下一个任务。
不管是继承Thread类还是实现Rannable接口创建的线程,都有一个缺点,即用完即销毁,需要时创建,极大的增加了内存占用和资源的损耗,而线程池则很好的解决了这一问题。
二、Java中的线程池
Java中的线程池主要分为两种:
1.无上限的线程池->无限创建线程执行任务
2.有限的线程池->有限个线程,当任务到达时,若无空闲线程,则任务等待
代码如下:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class MyThreadPoolDemo {
/*
public static ExecutorService newCachedThreadPool(); 创建一个无上限的线程池
public static ExecutorService newFixedThreadPool(); 创建一个有限的线程池
*/
public static void main(String[] args) {
//获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
//提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
}
}
创建有限个线程的线程池
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class MyThreadPoolDemo {
/*
public static ExecutorService newCachedThreadPool(); 创建一个无上限的线程池
public static ExecutorService newFixedThreadPool(); 创建一个有限的线程池
*/
public static void main(String[] args) {
//获取线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3);
//提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
}
}
三、自定义线程池
自定义线程首先要明确以下变量:
1.核心线程数(不能小于0)
2.线程池中最大线程的数量(核心线程数+临时线程数,最大数量>=核心线程数量)
3.空闲时间(临时线程空闲销毁时间:值)(不能小于0)
4.空闲时间(临时线程空闲销毁时间:单位)(用TimeUnit指定)
5.阻塞队列 (不为null)
6.创建线程的方式 (不为null)
7.要执行任务很多时的解决方案(不为null)
注意:当核心线程都在使用,且阻塞队列已经排满时,创建临时线程继续处理任务,而且先提交的任务不一定先完成。当所有核心线程和临时线程都在使用,且阻塞队列都排满的情况下,触发任务拒绝策略(默认丢弃任务)
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
import java.util.concurrent.*;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool=new ThreadPoolExecutor(
3, //核心线程数(不能小于0)
6, //线程池中最大线程的数量(核心线程数+临时线程数,最大数量>=核心线程数量)
60,//空闲时间(临时线程空闲销毁时间:值)(不能小于0)
TimeUnit.SECONDS, //空闲时间(临时线程空闲销毁时间:单位)(用TimeUnit指定)
new ArrayBlockingQueue<>(3), //阻塞队列 (不为null)
Executors.defaultThreadFactory(), //创建线程的方式 (不为null)
new ThreadPoolExecutor.AbortPolicy() //任务拒绝策略(不为null)
);
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
}
}