什么是多线程?
进程就是一个运行的软件,线程就相当于是一个软件中的多个功能,他们相互独立,但是又可以同时运行
在进行一些耗时操作的时候,cpu会切换到其他方法上进行操作,这样可以节约运行时间,这样的操作就是多线程的优点
并发,并行
并发:在同一时刻,有多个操作在cpu上交替进行
并行:在同一时刻,有多个操作在cpu上同时进行
并发和并行可以同时存在,假设线程数超出计算机可执行的线程数,那么就会有多个线程同时运行,并且多个线程同时切换
多线程的实现方式
继承Thread类的方式进行实现
package Thread;
public class MyThread extends Thread{
//继承Thread,重写run方法
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println(getName()+"Hello World");
}
}
}
package Thread;
public class Main {
public static void main(String[] args){
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
//创建实现类,start开始线程
myThread1.setName("线程1:");
myThread2.setName("线程2:");
myThread1.start();
myThread2.start();
}
}
实现Runnable接口的方式实现
package Thread1;
public class MyRun implements Runnable{
@Override
public void run() {
for(int i = 0 ; i < 100 ;i++){
System.out.println(Thread.currentThread().getName()+"Hello World");
//Thread.currentThread()就是获取当前执行线程的名字
}
}
}
package Thread1;
public class Main {
public static void main(String[] args){
//表示多线程要执行的任务
MyRun myRun = new MyRun();
//创建线程的对象
Thread thread1 = new Thread(myRun);
Thread thread2 = new Thread(myRun);
//给线程命名
thread1.setName("线程1:");
thread2.setName("线程2:");
//让线程开始
thread1.start();
thread2.start();
}
}
利用Callable接口和Future接口实现
package Thread2;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyCallable的对象,表示多线程要执行的任务
MyCallable myCallable = new MyCallable();
//创建FutureTask的对象,管理多线程运行的结果
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
//创建线程对象
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//获取线程执行的结果
Integer result = futureTask.get();
System.out.println(result);
}
}
package Thread2;
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 0 ; i < 100 ; i++){
sum += i;
}
return sum;
}
//这个返回值的类型是可以修改的
}
多线程常用方法
守护线程又叫备胎线程!
-
细节1:
setName没有操作时,线程也会有原始的名字就是Thread-X
X就是序号,从零开始,然后创建一个对象序号就+1
-
细节2:
构造方法无法被继承虽然Thread类里面有有参构造的方法,但是我们创建的子类,并不会继承他的方法,所以我们要想使用就要自己书写构造方法
package Thread;
public class Main {
public static void main(String[] args){
MyThread myThread1 = new MyThread("线程1:");
MyThread myThread2 = new MyThread("线程2:");
myThread1.start();
myThread2.start();
}
}
package Thread;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println(getName()+"Hello World");
}
}
}
package Thread;
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread("线程1:");
MyThread myThread2 = new MyThread("线程2:");
myThread1.start();
myThread2.start();
//那条线程执行到这个方法,获取的就是对应线程的对象
Thread thread = Thread.currentThread();
//获取线程的名字
String name = thread.getName();
//设置睡眠时间,其实就是设置停止运行时间
System.out.println(name);
Thread.sleep(5000);
System.out.println(name);
}
}
package Thread;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println(getName()+"Hello World");
}
}
}
线程的优先级
线程优先级分为抢占式调度还有非抢占式调度,抢占式调度可以设置优先级,最小是1,最大是10,优先级越高,抢占的概率越大,非抢占式调度就是轮流来实现
没有设置的时候默认值就是5
MyThread myThread1 = new MyThread("线程1:");
MyThread myThread2 = new MyThread("线程2:");
myThread1.setPriority(1);
myThread2.setPriority(10);
myThread1.start();
myThread2.start();
守护线程
守护线程就是当非守护线程结束后,守护线程就会陆续结束,假设守护线程要输出100次,但是非守护线程结束后,守护线程就会提前结束
MyThread myThread1 = new MyThread("线程1:");
MyThread myThread2 = new MyThread("线程2:");
myThread2.setDaemon(true);
myThread1.start();
myThread2.start();
礼让线程
package Thread;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println(getName()+"Hello World");
//当这个输出语句执行完之后,原本应该是继续执行,但是现在让出cpu,重新进行争夺
Thread.yield();
}
}
}
插入线程:就是让某一个线程插入,当这个线程执行完之后,再去执行下一个线程
线程的生命周期
线程的安全问题
假设多个线程同时执行一个任务,就会出现数据重复或者超出范围的情况
因为多个线程同时抢同一个任务,当一个线程抢到任务时,其他线程也会抢到,因此tacket++后就会出现三个线程打印相同tacket的操作,当tacket==99的时候也会出现这样的效果,因此就会出现超出范围的情况
同步代码块
为了解决线程安全,会把任务锁起来,然后一个线程执行完之后,下一个线程才会去执行
public class Main {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
myThread1.setName("第一个窗口");
myThread2.setName("第二个窗口");
myThread3.setName("第三个窗口");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
package Thread3;
public class MyThread extends Thread{
static int ticket = 0 ;
@Override
public void run() {
while(true){
//细节1:synchronized这个要写在循环的里面,因为假如一个线程进来后,锁就会关闭,其他的线程就无法进入,只有循环执行完才会开锁,那么任务就会被一个线程执行完
synchronized (MyThread.class){
//细节2:锁只能有一把,如果是多个锁,那么就相当于不存在,假设两个线程两把锁,那么就相当于每个锁对于他的线程都是开着的
if(ticket < 100){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() +"卖出第" + ticket + "张票");
ticket++;
}else{
break;
}
}
}
}
}
同步方法
package Thread3;
public class MyThread extends Thread{
static int ticket = 0 ;
@Override
public void run() {
while(true){
synchronized (MyThread.class){
if (extracted()) break;
}
}
}
private boolean extracted() {
if(ticket < 100){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() +"卖出第" + ticket + "张票");
ticket++;
}else{
return true;
}
return false;
}
}
Alt+Ctrl+m快捷键提取方法
Lock锁
Lock提供了更多的方法,可以人为的控制锁的开关
package Thread3;
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() {
while(true){
lock.lock();
try {
if(ticket < 100){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() +"卖出第" + ticket + "张票");
ticket++;
}else{
break;
}
} catch (RuntimeException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
}
ctrl+alt+t生成try catch方法
死锁
就是AB锁都被占用,等对方释放,但是都没有释放,所以就会产生死锁
写的代码不要把锁嵌套起来
多线程等待唤醒机制
package Thread4;
public class Main {
public static void main(String[] args) {
Food f = new Food();
Cook c = new Cook();
f.start();
c.start();
}
}
package Thread4;
public class Cook extends Thread{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.number == 1){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
Desk.number = 1;
System.out.println("qqq");
Desk.lock.notifyAll();
}
}
}
}
}
}
package Thread4;
public class Desk extends Thread{
public static int number = 0;
public static int count = 10;
public static Object lock = new Object();
}
package Thread4;
public class Food extends Thread{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.number == 0){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
Desk.count--;
System.out.println(Desk.count);
Desk.lock.notifyAll();
Desk.number = 0;
}
}
}
}
}
}
阻塞队列实现多线程唤醒
package Thread5;
import java.util.concurrent.ArrayBlockingQueue;
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) {
throw new RuntimeException(e);
}
}
}
}
package Thread5;
import java.util.concurrent.ArrayBlockingQueue;
public class Food extends Thread{
ArrayBlockingQueue<String> queue;
public Food(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run() {
while(true){
String food = null;
try {
//从阻塞队列中获取
food = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(food);
}
}
}
package Thread5;
import Thread4.Cook;
import Thread4.Food;
import java.util.concurrent.ArrayBlockingQueue;
public class Main {
public static void main(String[] args) {
//创建阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//创建对象
Cook cook = new Cook();
Food food = new Food();
//开启线程
cook.start();
food.start();
}
}
多线程的六种状态
当线程抢夺到cpu的时候就会将控制权交出去
案例1
package Thread6;
public class Main {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
MyThread myThread4 = new MyThread();
MyThread myThread5 = new MyThread();
myThread1.setName("a");
myThread2.setName("b");
myThread3.setName("c");
myThread4.setName("d");
myThread5.setName("e");
myThread1.start();
myThread2.start();
myThread3.start();
myThread4.start();
myThread5.start();
}
}
package Thread6;
import java.util.Random;
public class MyThread extends Thread{
//红包的数量默认是三个
public static int count = 3;
//红包默认金额是100元
public static int money = 100;
//默认最小金额是1元
public static final int MIN = 1;
@Override
public void run() {
synchronized (MyThread.class) {
if (count == 0) {
//判断还有没有红包了
System.out.println(getName() + "对不起,您没有抢到红包");
} else {
int price = 0;
if (count == 1) {
//话有一个的时候有多少抢多少
price = money;
} else {
//随机抢到红包
Random r = new Random();
int bounds = count - 1;
price = r.nextInt(money - bounds);
if (price < 0) {
price = MIN;
}
}
//每次抢完都减少总金额
money = money - price;
//减少总数量
count--;
//输出qiang'da的金额
System.out.println(getName() + "抢到了" + price);
}
}
}
}
案例2
package Thread7;
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
Collections.addAll(arrayList,5,10,20,30,40,50,60,70,80,90,100);
MyThread myThread1 = new MyThread(arrayList);
MyThread myThread2 = new MyThread(arrayList);
myThread1.setName("aaa");
myThread2.setName("bbb");
myThread1.start();
myThread2.start();
}
}
Collections.addAll(arrayList,5,10,20,30,40,50,60,70,80,90,100)方法是将指定的元素添加到ArrayList集合中。但是,添加的顺序并不是按照参数的顺序添加的
ArrayList是一个动态数组,它内部使用数组来存储元素。当添加元素时,ArrayList会根据当前数组的大小和容量来确定新元素的位置。如果当前数组的容量不足以容纳新元素,ArrayList会进行扩容操作,创建一个更大的数组,并将原有元素复制到新数组中。
在扩容和复制元素的过程中,ArrayList会重新排列元素的顺序,以保证元素在数组中的位置是连续的。因此,添加进去的数据在ArrayList中的顺序可能与参数的顺序不一致。
package Thread7;
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread{
ArrayList<Integer> arrayList = new ArrayList<>();
public MyThread(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
@Override
public void run() {
while(true){
synchronized (MyThread.class){
//如果集合的内容为空,那么就结束进程
if(arrayList.size() == 0){
break;
}else{
Collections.shuffle(arrayList);
Integer remove = arrayList.remove(0);
System.out.println(getName()+"抽到了"+remove+"元");
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程栈
package Thread7;
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread{
ArrayList<Integer> arrayList;
public MyThread(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
@Override
public void run() {
//在main里面创建两个Thread对象的时候,里面的集合就会被创建作为一个部分,同时创建共用集合,生成两个栈,栈里面运行的就是Thread里面的代码,看谁抢到就会运行谁的线程
ArrayList<Integer> list = new ArrayList<>();
while(true){
synchronized (MyThread.class){
if(arrayList.isEmpty()){
System.out.println(getName() + list);
break;
}else{
Collections.shuffle(arrayList);
Integer remove = arrayList.remove(0);
list.add(remove);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
案例三
package Thread7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ArrayList<Integer> arrayList = new ArrayList<>();
Collections.addAll(arrayList,5,10,20,30,40,50,60,70,80,90,100);
//1.创建多线程运行要使用的参数对象
MyCallable1 myCallable = new MyCallable1(arrayList);
//2.创建多线程运行要使用的返回值管理对象
FutureTask<Integer> futureTask1 = new FutureTask<>(myCallable);
FutureTask<Integer> futureTask2 = new FutureTask<>(myCallable);
//3.创建多线程对象
Thread thread1 = new Thread(futureTask1);
Thread thread2 = new Thread(futureTask2);
//4.修改多线程名字
thread1.setName("多线程1");
thread2.setName("多线程2");
//5.开启线程
thread1.start();
thread2.start();
//6.获取最大值
Integer i = futureTask1.get();
Integer j = futureTask2.get();
if(i > j){
System.out.println(i);
}else {
System.out.println(j);
}
}
}
package Thread7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class MyCallable1 implements Callable<Integer> {
ArrayList<Integer> arrayList;
public MyCallable1(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> list = new ArrayList<>();
while(true){
synchronized (MyCallable1.class){
if(arrayList.isEmpty()){
System.out.println(Thread.currentThread().getName() + list);
break;
}else{
Collections.shuffle(arrayList);
Integer remove = arrayList.remove(0);
list.add(remove);
}
}
Thread.sleep(10);
}
if(list.isEmpty()){
//若当前线程的集合为空,那么就直接返回null
return null;
}else{
//集合不为空的时候,就判断返回最大值
return Collections.max(list);
}
}
}
线程池
1.创建一个空的线程池
2.当提交任务的时候,就会创建线程,并且完成任务之后,线程会重新放回线程池,下次用的时候可以重新取出
3.当线程池内没有线程,并且无法创建新的线程时,任务就会排队等待
-
创建线程池
ExecutorService exe = Executors.newCachedThreadPool();
-
提交任务
exe.submit(new MyRunnable());
exe.submit(new MyRunnable());
exe.submit(new MyRunnable());
-
当所有任务都执行完毕之后,就会关闭线程池
exe.shutdown();
package ThreadPool;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
这个方法是默认数量的线程池,然后输出语句里面不加“—>”这个符号时,就会从11开始打印,在你的代码中,可能出现线程池中的某个线程先于其他线程执行run方法。假设第一个线程执行run方法时,它会打印出"Thread-1 1",然后其他线程开始执行run方法时,它们会继续打印从1到100的数字
所以可能是10条线程在抢夺cpu但是抢完之后i已经增加到10了,所以就会从11开始打印
这个方法创建的线程池,他的数量是已经规定好的,假设你提交了五次,但是仍是三条线程
ExecutorService exe = Executors.newFixedThreadPool(3);
自定义线程池
-
当任务数量小于核心线程池时,就会创建任务数量的线程
-
当任务数量大于核心线程数量,但是小于核心线程+临时线程的总数时,就会创建核心线程数量的线程,然后其他任务排队
-
当任务数量大于核心线程+临时线程总数的时候,就会创建临时线程,临时线程会处理多出来的任务,所以任务执行的顺序是不固定的,上面的从11开始的问题就有解释了
-
当任务数量大于三者相加之和的时候。剩余的任务就被拒绝了
-
默认的方法是第一种,其他三种知道就行
package ThreadPool1;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3,//核心任务数量,不能小于0
6,//最大线程数,大于等于核心任务数
60,//空闲线程最大存活时间,不能小于0
TimeUnit.SECONDS,//单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//创建拒绝策略
);
}
}