Java第十三天(实训学习整理资料(十二)Java多线程)

目录

一、定义

二、相关概念

1、操作系统

2、进程

3、线程

三、Java中的线程

四、线程运行状态

1、创建 NEW

2、就绪、运行 RUNNING

3、线程终止 TERMINATION

4、阻塞 BLOCKING

5、限时等待 WAITTING TIMEOUT

6、等待 WAITTING

五、多线程并发安全问题

1、定义

2、如何处理并发安全问题

六、死锁

七、关于线程的其他细节

1、守护线程

2、线程加入

3、线程退让

4、线程执行的优先级

八、volatile关键字

1、作用

2、特点

3、volatile与synchronized的区别

九、ThreadPoolExecutor

1、构造方法

2、执行流程

3、拒绝策略

4、线程池设置

5、使用Executors创建线程池:


一、定义

Java程序本身就是多线程的。

二、相关概念

1、操作系统

内核实现其他资源的管理和调度。

2、进程

一个进程就是操作系统中运行的一个应用程序,每个进程都有进程ID,标志其唯一性。

3、线程

线程是计算机进行调度的最小资源,线程运行一般也包含在进程中。

三、Java中的线程

在Java中Thread就表示线程

注意:

1.启动线程不能直接调用run方法,而是调用start启动。

2.虽然在Thread中提供了stop终止线程,但是可能会出现不可控问题,不推荐使用;应该通过程序逻辑控制线程的终止条件。

3.如果线程运行过程中出现异常也会导致线程异常终止。

1--java中创建线程的方法

1)通过一个类继承Thread类。调用创建对象的start方法启动线程。

案例代码:

public class ThreadDemo01 {
			public static void main(String[] args) {
				MyThread myThread = new MyThread();
				//此处不应使用run方法运行线程,应该使用start启动线程
				myThread.start();
			}
		}
		class MyThread extends Thread{
			@Override
			public void run() {
				//当调用start方法时,启动线程运行的逻辑代码
				System.out.println(1);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(2);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(3);

			}
		}

注意:这种创建线程的方法不推荐使用,因为java支持的是单继承,这里会导致类的扩展性降低。

2)使用类实现接口Runnable,并实现run方法。将类创建的对象传递给Thread构造。

案例:

public class ThreadDemo02 {
			public static void main(String[] args) {
				MyThead1 myThead1 = new MyThead1();
				Thread thread = new Thread(myThead1);

				thread.start();
			}
		}
		class MyThead1 implements Runnable{
			@Override
			public void run() {
				System.out.println(1);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(2);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(3);
			}
		}

3)创建一个类实现JUC(Java并发工具包)下的Callable<V>接口,泛型V表示线程运行结束后返回的结果类型。

案例代码:

public class ThreadDemo04 {
				public static void main(String[] args) throws Exception {
					int question = 50;

					AStudent aStudent = new AStudent(question);
					FutureTask<String> stringFutureTask = new FutureTask<>(aStudent);
					Thread thread = new Thread(stringFutureTask);
					thread.start();

					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					//通过FutureTask获取执行的结果
					String result = stringFutureTask.get();
					System.out.println(result);

				}
			}

			class AStudent implements Callable<String> {
				private int question;
				public AStudent(int question){
					this.question = question;
				}

				@Override
				public String call() throws Exception {
					while (this.question>0){
						System.out.println("A同学当前回答的问题是:"+this.question--);
					}
					return "A同学回答完毕!";
				}
			}

2--线程类Thread相关方法

构造方法:

Thread()

Thread(Runnable target)

常用方法:

Thread.currentThread()//获取当前线程

String getName() //获取线程的名字

void setName(String name) //设置线程的名称

Long getId() //获取线程ID

final void setDaemon(boolean on) //是否设置为守护线程

Thread.activeCount() //当前活动的线程数

Thread.sleep(tm) //设置睡眠时间

3--Object类中关于线程状态相关方法

final native void notify() //通知

final native void notifyAll(); //通知所有

final native void wait(long timeout) //等待,timeout表示等待超时时间

final void wait() //等待

final void stop() //终止线程(不推荐使用)

四、线程运行状态

1、创建 NEW

实例化线程对象时(初始)

2、就绪、运行 RUNNING

就绪: 实现化线程对象并调用了start方法(等待分配cpu运行资源) ​

运行:线程分配到了运行资源,只有就绪状态的线程才可以进入运行状态

3、线程终止 TERMINATION

stop(),线程正常执行结束,运行时出现异常导致线程终止。

4、阻塞 BLOCKING

当线程遇到synchronized并且没有抢占到锁资源时

5、限时等待 WAITTING TIMEOUT

线程调用了类似于wait(timeout),join()线程加入时会进入限时等待。

当等待超时后线程会重新进入到就绪状态。

6、等待 WAITTING

调用了wait()进入等待,在等待期间会释放锁资源并且不抢占cpu资源。

只有被其他线程通知notify()时才会被唤醒。

wait()线程等待,sleep()睡眠: 被设置为wait()的线程,不会争抢CPU资源,而且还会释放锁 sleep()睡眠,不会争抢CPU,而且不会释放锁

五、多线程并发安全问题

1、定义

如果多个线程在对同一个资源进行修改操作时,就可能会产生并发安全问题。

2、如何处理并发安全问题

1-使用同步代码块

synchronized(对象-用来承载锁的对象){
//将需要同步的代码包裹起来

}

注意:

用作承载锁的对象必须是所有的线程都能看到的同一个对象

同步代码块一次只有一个线程在其中执行,会降低效率,所以在设

计同步代码块的时候,应该在能包裹住会造成线程并发安全问题代码的前提下尽量少的包裹其他代码,减少在同步中执行的时间,可以整体的提升效率

同步方法:

方法中所有代码都需要上锁,可以将当前方法设置为同步方法;在方法前加上synchronized。

如果是同步的非静态方法,锁对象选择的就是this。

同步的静态方法,锁对象是当前类的class字节码。

六、死锁

锁之间互相等待无法进行下去的状态就称为产生了死锁。

如何解决死锁?

1、其中锁占用一方强制退出

2、等待超时机制

案例:打印机和扫描仪相互抢占导致死锁

public class Thread08 {
		public static void main(String[] args) {
			Thread p1 = new Thread(new Person1());
			Thread p2 = new Thread(new Person2());
			p1.start();
			p2.start();
		}
	}
	//打印机
	class Printers {

	}

	//扫描仪
	class Scanners {

	}

	class Person1 implements Runnable{
		public void run() {
			synchronized (Printers.class) {
				System.out.println("Person1 正在使用打印机...");
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (Scanners.class) {
					System.out.println("Person1 正在使用扫描仪...");
				}
			}
		}
	}
class Person2 implements Runnable{
		public void run() {
			synchronized (Scanners.class) {
				System.out.println("Person2 正在使用扫描仪...");
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				for(int i=0; i<10; i++) {
					if(i==6) {
						throw new RuntimeException("no");
					}
				}
				synchronized (Printers.class) {
					System.out.println("Person2 正在使用打印机...");
				}
			}
		}
	}

七、关于线程的其他细节

1、守护线程

线程可以分为前台线程和后台线程。

如果一个线程被设置为守护线程后,当前台没有线程时,哪怕没有执行完也会自动退出。

设置守护线程的方式: 在运行线程前调用setDaemon方法

方法:

void setDaemon(boolean on) //默认为false,true表示设置为守护(后台)线程

注意:设置守护线程的方法,必须要在调用start方法之前执行。

案例代码:

public class ThreadDemo07 {
			public static void main(String[] args) {
				Person person = new Person();
				Thread thread = new Thread(person);
				thread.setDaemon(true);
				thread.start();

				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"线程已经执行结束");
			}
		}
class Person implements Runnable{
			@Override
			public void run() {
				for (int i=1; i<=10; i++){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("当前执行的编号为:"+i);
				}
			}
		}
2、线程加入

当正在执行的线程执行到了join(),就会将执行权让给调用此方法的线程,自己转入冻结状态,

直到调用此方法的线程结束,当前线程才会从冻结状态醒来,接着执行。

方法:

final void join()

案例代码:炒菜

public class ThreadDemo08 {
			public static void main(String[] args) throws Exception {

				System.out.println("准备炒菜~~");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("菜没了~~");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				Buy buy = new Buy();
				Thread thread = new Thread(buy);
				thread.start();
				thread.join();

				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("继续炒菜~");
			}
		}
class Buy implements Runnable{

			@Override
			public void run() {
				System.out.println("准备出门~~~");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("买菜~~~");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("回家~~~");
			}
		}
3、线程退让

让当前线程暂停执行,可以执行其他线程

Thread.yield();

4、线程执行的优先级

修改线程的优先级 1 - 10,越大优先级越高,但是这仅仅是理论上的优先级,真正执行时,

谁执行谁不执行,仍然是概率的,只是概率高低有所不同。通常 1、5、10表现的效果比较明显。

线程默认优先级都是5。

方法:

void setPriority(int newPriority) //优先级:1~10

final int getPriority() //获取优先级

扩展:java的内存模型-》多线程(并发编程)

八、volatile关键字

1、作用

关键字volatile的主要作用是使变量在多个线程间可见。

2、特点

保证可见性:

也就是说某个线程修改了共享变量,线程在修改变量时不会把值缓存在寄存器或者其他地方,而 是会把值刷新回主内存。

当其它线程读取该共享变量时,会从主内存重新获取变量最新值,而不是从当前线程的工作内存中获取。

保证有序性(禁止指令重排序):

所谓指令重排序:为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。

3、volatile与synchronized的区别

-关键字volatile是线程同步的轻量级实现,所以volatile的性能略胜于synchronized,并且volatile只能修饰变量,而synchronized可以修饰方法、代码块等;

-多线程访问volatile不会发生阻塞,而synchronized会出现阻塞;

-volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和共有内存中的数据做同步;

-关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

九、ThreadPoolExecutor

1、构造方法

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize, l

ong keepAliveTime,

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler);

参数介绍:

int corePoolSize:核心线程大小,线程池最小的线程数量。

int maximumPoolSize:最大线程大小,线程池的最大的线程数量

long keepAliveTime:超过corePoolSize的线程在不活动的状态下存活的最大时间。

TimeUnit unit:keepAliveTime的时间单位。

BlockingQueue<Runnable> workQueue:任务队列。

ThreadFactory threadFactory:线程池工厂,用来创建线程。

RejectedExecutionHandler handler:拒绝策略。

2、执行流程


(1)当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

(2)当线程池超过corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行。

(3)当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。

(4)当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理。

(5)当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程。

(6)当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将被关闭。

3、拒绝策略

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出

RejectedExecutionException异常,这是默认的一种方式。

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务

ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

4、线程池设置

cpu密集型

cpu密集的意思是该任务需要大量的运算,而没有阻塞,cpu一直全速运行。

cpu密集型任务配置尽可能少的线程数量:以保证每个cpu高效的运行一个线程。

通常设置方式:

与cpu核心数相同的线程数或者加1

io密集型

io密集型,即该任务需要大量的io,即大量的阻塞。

在单线程上运行io密集型的任务会导致浪费大量的cpu运算能力浪费在等待。

所以在io密集型任务中使用多线程可以大大的加速程序运行,这种加速主要就是利用了被浪费掉的阻塞时间。

通常设置方式:

cpu核数 * 2个线程的线程池。

获取核心数

Runtime.getRuntime().availableProcessors();

5、使用Executors创建线程池:

1、使用Executors创建线程池:

是一个根据需要创建新线程的线程池,当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。

2、Executors.newSingleThreadExecutor

单线程线程池,只有一个核心线程,用唯一的一个共用线程执行任务,保证所有任务按指定顺序执行

3、Executors.newFixedThreadPool

定长线程池,核心线程数和最大线程数由用户传入,可以设置线程的最大并发数,超出在队列等待

4、Executors.newScheduledThreadPool

定长线程池,核心线程数由用户传入,支持定时和周期任务执行

总结:

FixedThreadPool和SingleThreadExecutor 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而引起OOM异常

CachedThreadPool 和newScheduledThreadPool允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而引起OOM异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值