JAVA 多线程
1.什么是进程
狭义定义:进程就是一段程序的执行过程。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
2.什么是线程
线程:线程是进程中的⼀个执行单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。与其他线程可以独立运行,是cpu调度的单位,多线程是为了充分利用cpu资源,提高效率。
⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序。简单来说,⼀个程序运⾏后⾄少有⼀个进程,⼀个进程中可以包含多个线程
3.java实现多线程的方式
第一种: 继承Thread类并重写run方法。
第二种: 实现Runnable接口
第三种: 实现Callable接口
3.1继承Thread类并重写run方法
步骤:
①创建一个线程类,然后继承Thread;
②重写run方法,可以在run方法中写业务代码;
③在主函数创建线程对象,开启线程;(代码如下)
获取当前线程的名称:
①通过父类Thread中的getName()可以获取线程名称。必须为Thread的子类
② 通过Thread类中的静态方法currentThread获取当前线程,getName()获取线程名。任意处获取线程名。
为线程设置名称
①通过setName()为线程起名 ---->线程对象.setName(“名称”)
②通过构造函数起名
![在这里插入图片描述](https://img-blog.csdnimg.cn/8edff99f9d96441aabb8135468525bc2.png
package com.aaa.demo01;
import com.aaa.demo01.MyThread;
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread("线程A");
// myThread.setName("线程A");
myThread.start();
MyThread myThread1 = new MyThread("线程B");
// myThread1.setName("线程B");
myThread1.start();
for (int i = 0; i <20; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~"+i);
}
}
}
package com.aaa.demo01;
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//System.out.println(this.getName()+"~~~~~~~~"+i);
System.out.println(Thread.currentThread().getName()+"~~~~~~~~"+i);
}
}
}
3.1.1案例
public class TicketTask extends Thread{
private static int ticket=100;
@Override
public void run() {
while(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖出一张票;剩余"+ticket);
}
}
}
public class Test02 {
public static void main(String[] args) {
TicketTask t1 = new TicketTask();
t1.setName("窗口A");
TicketTask t2 = new TicketTask();
t2.setName("窗口B");
TicketTask t3 = new TicketTask();
t3.setName("窗口C");
TicketTask t4 = new TicketTask();
t4.setName("窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
3.2实现Runnable接口
public class Test03 {
public static void main(String[] args) {
MyThread m1 = new MyThread();
Thread thread = new Thread(m1,"线程A");
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~"+i);
}
}
}
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~"+i);
}
}
注意:能实现接口的就不要继承父类。因为java中只允许单继承,但是可以实现多个接口,一次选择继承扩展性比较差。
3.2.1案例
public class TicketTask implements Runnable {
private int ticket=100;
@Override
public void run() {
while(true) {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "售出一张票;剩余票数:" + ticket);
} else {
break;
}
}
}
}
public class Test04 {
public static void main(String[] args) {
TicketTask ticketTask = new TicketTask();
Thread t1 = new Thread(ticketTask,"窗口A");
Thread t2 = new Thread(ticketTask,"窗口B");
Thread t3 = new Thread(ticketTask,"窗口C");
Thread t4 = new Thread(ticketTask,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
此时会发生线程安全问题从而发生超卖和重卖的情况,会在下面(5.1中解决线程安全问题中)解决。
4.Thread类中常见的方法
4.1休眠(sleep)
public class Test {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
4.2 放弃(yield)
public class Test {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
4.3 加入(join)
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
m1.join();
m2.join();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
4.4设置优先级(setPriority())
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setPriority(10);
m2.setPriority(1);
m1.start();
m2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
4.5 守护线程(setDaemon(true))
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread m1 = new MyThread();
m1.setDaemon(true);
m1.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
}
}
}
5. 线程安全问题
5.1案例一 (卖票问题)
卖票案例:
当多个线程共享一个资源时,可能会出现线程安全问题,此时我们需要用锁来解决线成安全问题,凡是用锁 锁定的代码都是原子操作。
①自动锁:synchronized
public class TicketTask implements Runnable {
private int ticket=100;
@Override
public void run() {
while(true) {
//自动锁
synchronized (this) {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "售出一张票;剩余票数:" + ticket);
} else {
break;
}
}
}
}
}
public class Test04 {
public static void main(String[] args) {
TicketTask ticketTask = new TicketTask();
Thread t1 = new Thread(ticketTask,"窗口A");
Thread t2 = new Thread(ticketTask,"窗口B");
Thread t3 = new Thread(ticketTask,"窗口C");
Thread t4 = new Thread(ticketTask,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
②手动锁:Lock
public class TicketTask implements Runnable {
private int ticket=100;
private Lock l=new ReentrantLock();
@Override
public void run() {
while(true) {
//手动上锁
l.lock();
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "售出一张票;剩余票数:" + ticket);
} else {
break;
}
//手动释放锁
l.unlock();
}
}
}
public class Test04 {
public static void main(String[] args) {
TicketTask ticketTask = new TicketTask();
Thread t1 = new Thread(ticketTask,"窗口A");
Thread t2 = new Thread(ticketTask,"窗口B");
Thread t3 = new Thread(ticketTask,"窗口C");
Thread t4 = new Thread(ticketTask,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
5.2案例二 (往数组中存放元素)
public class Test06 {
private static String[] arr = new String[2];
private static int index=0;
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object){
if (arr[index]==null){
arr[index]="hello";
index++;
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object){
if (arr[index]==null){
arr[index]="world";
index++;
}
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.toString(arr));
}
}
6.死锁以及如何避免死锁
①死锁
- 当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,称之为死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,因此可能会造成死锁。
②如何避免死锁
- 尽量使用tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock, ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
- 尽量使用Java.util.concurrent下的并发类 代替自己手写锁。
- 尽量降低锁的使用力度,尽量不要几个功能用同一把锁。
- 尽量减少同步的代码块的嵌套。
public class BoyTask extends Thread{
@Override
public void run() {
synchronized (LockObject.lockA){
System.out.println("男孩获得画板");
synchronized (LockObject.lockB){
System.out.println("男孩获得画笔");
System.out.println("男孩可以画画了");
}
}
}
}
public class GirlTask extends Thread{
@Override
public void run() {
synchronized (LockObject.lockB){
System.out.println("女孩获得画笔");
synchronized (LockObject.lockA){
System.out.println("女孩获得画板");
System.out.println("女孩可以画画了");
}
}
}
}
public class LockObject {
public static Object lockA = new Object();
public static Object lockB = new Object();
}
public class Test07 {
public static void main(String[] args) {
BoyTask boyTask = new BoyTask();
GirlTask girlTask = new GirlTask();
boyTask.start();
girlTask.start();
}
}
7.线程通信
sleep()和wait()的区别:
- 来自不同的类:wait来自于Object类中,sleep来自Thread中。
- 作用位置不同:wait必须放在同步代码块中,而sleep可以放在任意位置。
- 是否会释放锁资源:wait会释放锁资源,sleep不会释放锁资源。
- 用法:wait需要notify或notifyAll唤醒,而sleep到时间自动唤醒。
notify()和notifyAll()的区别:
- notify随机唤醒等待对队列中的一个线程,notifyAll会唤醒等待队列中的所有线程。
- notifyAll()调用后,会将全部想成由等待池移到锁池,然后参与锁的竞争,竞争成功后则继续执行,如果不成功则留在锁池中等待被释放后再次参与竞争。而notify()只会唤醒一个线程,具体唤醒哪一个线程一虚拟机控制。
public class BankCard {
private int balance;
//当flag为true时表示卡中有钱,为false时表示卡中没钱
private boolean flag;
//存钱
public synchronized void save(int money){
if (flag){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance+=money;
System.out.println(Thread.currentThread().getName()+"往卡中存了"+money+"元,卡中剩余"+balance+"元");
flag=true;
notify();
}
//取钱
public synchronized void take(int money){
if(flag==false){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance-=money;
System.out.println(Thread.currentThread().getName()+"从卡中取了"+money+"元,卡中剩余"+balance+"元");
flag=false;
notify();
}
}
public class BankCard {
private int balance;
//当flag为true时表示卡中有钱,为false时表示卡中没钱
private boolean flag;
//存钱
public synchronized void save(int money){
if (flag){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance+=money;
System.out.println(Thread.currentThread().getName()+"往卡中存了"+money+"元,卡中剩余"+balance+"元");
flag=true;
notify();
}
//取钱
public synchronized void take(int money){
if(flag==false){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance-=money;
System.out.println(Thread.currentThread().getName()+"从卡中取了"+money+"元,卡中剩余"+balance+"元");
flag=false;
notify();
}
}
public class SaveTask implements Runnable{
private BankCard bankCard;
public SaveTask(BankCard bc){
bankCard=bc;
}
@Override
public void run() {
for (int i = 0; i <5; i++) {
bankCard.save(798);
}
}
}
public class TakeTask implements Runnable{
private BankCard bankCard;
public TakeTask(BankCard bc){
bankCard=bc;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
bankCard.take(798);
}
}
}
public class Test08 {
public static void main(String[] args) {
BankCard bankCard = new BankCard();
SaveTask saveTask = new SaveTask(bankCard);
TakeTask takeTask = new TakeTask(bankCard);
Thread t1 = new Thread(saveTask,"father");
Thread t2 = new Thread(takeTask,"son");
t1.start();
t2.start();
}
}
8.线程的状态
新建(New):当线程对象被创建但尚未启动时处于新建状态。
可运行/就绪(Runnable):线程对象调用了status()方法后,线程处于可运行状态。在可运行状态下,线程可能正在执行,也可能正在等待系统资源(如处理器时间片)。
运行(Running):线程获得了处理器时间片正在执行其任务。
阻塞(Blocked):线程被阻塞并暂时停止执行,通常是因为等待某个操作的完成(如等待I/O操作、等待获取锁、等待某个条件满足等)。
等待(Waiting):线程等待某个特定条件的发生,需要其他线程显式地唤醒(如通过wait()方法)。
超时等待(Timed Waiting):线程等待一段特定时间,超过时间后会自动唤醒。
终止(Terminated):线程执行完毕或出现异常导致终止,不再可运行。
9. 线程池
线程池的原理
9.1如何创建线程池
(1)线程池的种类
- 固定长度的线程池
- 单一线程池
- 可变线程池
- 可变线程池
- 延迟线程池
(2)线程池中参数的意思
- core: 核心线程数
- max: 最大线程池数
- long: 等待时常
- unit: 等待单位
- BlockQueue: 等待列对象
(3)Executor: 它是线程池的跟接口:
- void execute(Runnable command):执行Runnable类型的任务
(4)ExecutorService: Executor的子接口
- void shutdown(): 关闭线程池.需要等任务执行完毕
- shutdownNow(); 立即关闭线程池. 不会再接受新的任务
-isShutdown(): 判断线程池是否终止. 表示线程池中的任务都执行完毕,线程池关闭- submit(Callable< T > task): 提交任务,可以提交Callable.
-submit(Runnable task): 提交任务, 可以提交Runnable.(5)Executors:线程池的工具类,该类提供了创建线程池的一些静态方法.
以下是通过通过Executors工具类创建线程池:
9.1.1固定长度线程池
public class Test09 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~");
}
});
}
executorService.shutdown();
// executorService.shutdownNow();
}
}
9.1.2单一线程池
class Test10{
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~");
}
});
}
}
}
9.1.3可变线程池
class Test11{
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~");
}
});
}
executorService.shutdown();
}
}
9.1.4延迟线程池
class Test12{
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~");
}
},5,TimeUnit.SECONDS);
}
executorService.shutdown();
}
}
9.2原生模式创建线程池
class Test13{
public static void main(String[] args) {
//capacity:5 最多有五个任务在等待
BlockingQueue<Runnable> workQueye=new ArrayBlockingQueue<>(5);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, workQueye);
for (int i = 0; i < 20; i++) {
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~");
}
});
}
}
}
10.补充(实现Callable接口创建线程 )
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//①创建线程对象
MyCallable myCallable = new MyCallable();
//把线程任务封装到FuntureTask类中,该类可以获取线程任务执行后的结果.
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(integerFutureTask);
thread.start();
System.out.println(integerFutureTask.get());
//②使用线程池来执行任务
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> submit = executorService.submit(new MyCallable());
System.out.println(submit.get());
executorService.shutdown();
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
return sum;
}
}