//测试类
/**
* 1、让子类继承Thread线程类
*/
public class ThreadTest1 extends Thread {
//2、子类必须重写Thread类的run方法
@Override
public void run() {
for (int i = 1;i <=5;i++){
System.out.println("myThread线程输出" + i);
}
}
}
//主线程main函数
public class Main {
//main方法是由一条默认的主线程负责执行的
public static void main(String[] args) {
//3、创建一个自己定义的线程对象
Thread t = new ThreadTest1();
//4、启动线程
//注意是调用start方法而不是run方法,调用start方法是告诉系统要把t对象单独开一条线程
//如果调用run方法则是调用一个普通对象的一个方法,不会另开一条线程
t.start();
for (int i = 1;i <=5;i++){
System.out.println("主线程main输出" + i);
}
}
}
主线程main输出1
myThread线程输出1
主线程main输出2
myThread线程输出2
主线程main输出3
myThread线程输出3
主线程main输出4
myThread线程输出4
主线程main输出5
myThread线程输出5
进程已结束,退出代码0
结果:此时程序有两条线程main和t,这两条线程交替执行。
优点:编码简单。
缺点:由于该方式要继承Thread类,所以就不能继承其他类,不利于功能扩展,不灵活。
//任务类
/**
* 1、定义一个任务类,实现Runnable接口
*/
public class MyRunnable implements Runnable{
//2、重写Runnable的run方法
@Override
public void run() {
for (int i = 1;i <=5;i++){
System.out.println("MyRunnable==》" + i);
}
}
}
@Test
public void RunnableTest(){
//3、创建任务对象
//注意这个不是一个线程对象,而是任务对象
Runnable runnable = new MyRunnable();
//4、把任务对象交给一个线程对象处理
//thread有一个Runnable类型的有参构造:public Thread(Runnable target)
//thread接受到参数后会自动创建一个匿名线程
new Thread(runnable).start();
for (int i = 1;i <=5;i++){
System.out.println("主线程main==》" + i);
}
}
MyRunnable==》1
MyRunnable==》2
主线程main==》1
MyRunnable==》3
主线程main==》2
MyRunnable==》4
主线程main==》3
MyRunnable==》5
主线程main==》4
主线程main==》5
进程已结束,退出代码0
优点:任务类只是实现接口,还可以继续继承其他类,实现其他接口,扩展性强。
缺点:需要多个Runnable对象。
扩展
由于每次创建线程都要创建一个实现接口的类,非常麻烦,可以用匿名内部类或Lambda表达式代替
@Test
public void RunnableTest2(){
//匿名内部类写法1
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 1;i <=5;i++){
System.out.println("MyRunnable==》" + i);
}
}
};
new Thread(runnable).start();
for (int i = 1;i <=5;i++){
System.out.println("主线程main==》" + i);
}
}
@Test
public void RunnableTest2(){
//匿名内部类写法2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1;i <=5;i++){
System.out.println("MyRunnable==》" + i);
}
}
}).start();
for (int i = 1;i <=5;i++){
System.out.println("主线程main==》" + i);
}
}
@Test
public void RunnableTest2(){
//Lambda 表达式的写法
new Thread(()->{
for (int i = 1;i <=5;i++){
System.out.println("子线程==》" + i);
}
}).start();
for (int i = 1;i <=5;i++){
System.out.println("主线程main==》" + i);
}
}
前面几种方法创建的线程均没有返回值,假如线程执行完毕后需要返回,可以用这种方式。
/**
* 1、定义一个实现Callable接口的类,指定返回数据的类型
*/
public class MyCallable implements Callable<String> {
int n;
public MyCallable(int n){
this.n = n;
}
// 2、重写call方法,指定返回类型
@Override
public String call() throws Exception {
int sum = 0;
for(int i = 1; i < this.n; i++){
sum += i;
}
return "1 - " + n + "的和为:" + sum;
}
}
@Test
public void CallableTest() throws ExecutionException, InterruptedException {
// 3、创建一个Callable对象,注意该对象不是还不是任务对象
Callable<String> callable= new MyCallable(10);
// 4、把Callable类型的对象封装成FutureTask类型的对象
// futureTask才是线程任务对象
FutureTask<String> futureTask = new FutureTask<>(callable);
// 5、把任务对象交给Thread对象
new Thread(futureTask).start();
// 6、获取线程执行完毕后返回的结果
// 注意:如果上面线程还没执行完毕就执行到该条语句,则主线程将会进入阻塞状态,
// 等待该子线程执行完毕才开始运行
String s1 = futureTask.get();
System.out.println(s1);
}
1 - 10的和为:45
进程已结束,退出代码0
优点:线程任务类只是实现接口,可以继续继承类和实现接口,可扩展性强,
可以在线程执行完毕就去获取线程执行的结果。
缺点:代码复杂一点。
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
public void setName(String name) | 为线程设置名称 |
public static Thread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后再执行 |
public final void join() | 让调用当前这个方法的线程先执行玩 |
Thread提供的常见构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象为线程对象 |
public Thread(Runnable target, String name) | 封装Runnable对象为线程对象,并指定线程名称 |
什么是线程安全问题?
答:多线程同时操作同一临界资源,导致结果存在的不准确性。
例题:当两个人同时对同一账号取钱,其其结果导致第一个人把前全部取走,二第二个人还可以继续取钱。
/**
* 共享资源:账户余额
*/
public class Accoud {
private double accound;
public Accoud(double accound) {
this.accound = accound;
}
//取钱操作
public void drowMoney(double i) {
// 获取当前取钱人的名字
String name = Thread.currentThread().getName();
if(accound < i){
System.out.println(name + "来取钱,余额不足");
}
else{
System.out.println(name + "取出额度为:" + i);
accound -= i;
System.out.println(name + "取钱后剩余余额为:" + accound);
}
}
}
public class ThreadTest2 extends Thread{
Accoud accoud;
public ThreadTest2(Accoud accoud,String name) {
super(name);
this.accoud = accoud;
}
@Override
public void run() {
//取钱
accoud.drowMoney(1000);
}
}
public class Main {
public static void main(String[] args) {
//创建临界资源
Accoud accoud = new Accoud(1000);
//创建两个线程,同时访问accoud资源
new ThreadTest2(accoud,"小红").start();
new ThreadTest2(accoud,"小蓝").start();
}
}
小红取出额度为:1000.0
小蓝取出额度为:1000.0
小红取钱后剩余余额为:0.0
小蓝取钱后剩余余额为:-1000.0
进程已结束,退出代码0
什么是线程同步?
答:线程同步是指一个或多个线程必须等待某个或多个线程执行完某些操作后才能继续往下执行,常用于多线程同时操作同一临界资源问题,但某线程在操作某一临界资源时,其他线程必须等待。
实现线程同步有三种方法:同步代码块、同步方法、lock锁
作用:把访问共享资源的核心代码给上锁,但此线程执行完后会自动解锁,以此保证线程安全。
synchronized(同步锁){
访问共享资源的核心代码
}
对之前取钱操作加同步锁
public void drowMoney(double i) {
// 获取当前取钱人的名字
String name = Thread.currentThread().getName();
synchronized ("这是同步锁") {
if(accound < i){
System.out.println(name + "来取钱,余额不足");
}
else{
System.out.println(name + "取出额度为:" + i);
accound -= i;
System.out.println(name + "取钱后剩余余额为:" + accound);
}
}
}
小红取出额度为:1000.0
小红取钱后剩余余额为:0.0
小蓝来取钱,余额不足
**注意:**这个锁其实是一个对象,这个对象被引用的时候会被标记,解锁后才会消除标记
所以就有一个问题,如果有小红、小蓝、小黑、小白,四个线程,而业务只需要小红小蓝同步,小黑小白同步,如果锁对象是唯一的,则所有的线程都用同一把锁,则红蓝黑白都会被同步,明明两组不相干的操作却要一起同步等待,就会影响效率,所以要想把两组同步分开,就要加不同的锁,用 this
public class Main {
public static void main(String[] args) {
Accoud accoud1 = new Accoud(1000);
new ThreadTest2(accoud1,"小红").start();
new ThreadTest2(accoud1,"小蓝").start();
Accoud accoud2 = new Accoud(1000);
new ThreadTest2(accoud2,"小黑").start();
new ThreadTest2(accoud2,"小白").start();
}
}
public void drowMoney(double i) {
// 获取当前取钱人的名字
String name = Thread.currentThread().getName(
//如果小红小蓝线程调用这个方法,则这里的this是指accoud1对象
//如果小黑小白线程调用这个方法,则这里的this是指accoud2对象
synchronized (this) {
if(accound < i){
System.out.println(name + "来取钱,余额不足");
}
else{
System.out.println(name + "取出额度为:" + i);
accound -= i;
System.out.println(name + "取钱后剩余余额为:" + accound);
}
}
}
小白取出额度为:1000.0
小蓝取出额度为:1000.0
小白取钱后剩余余额为:0.0
小蓝取钱后剩余余额为:0.0
小黑来取钱,余额不足
小红来取钱,余额不足
用this可以识别调用不同对象的线程,但如果要在静态方法里上锁,因为静态方法是直接用类名调用的,不需要创建对象,这时可以用这个类的唯一标识当锁,而这个类的唯一标识是字节码(类名.class)
public static void drowMoney(double i) {
// 获取当前取钱人的名字
String name = Thread.currentThread().getName();
//静态方法用类的字节码标识
synchronized (Accoud.class) {
if(accound < i){
System.out.println(name + "来取钱,余额不足");
}
else{
System.out.println(name + "取出额度为:" + i);
accound -= i;
System.out.println(name + "取钱后剩余余额为:" + accound);
}
}
}
锁对象的使用规范
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
修饰符 synchronized 返回值类型 方法名(形参列表){
操作共享资源的代码
}
同步方法底层原理
public synchronized void drowMoney(double i) {
// 获取当前取钱人的名字
String name = Thread.currentThread().getName();
if(accound < i){
System.out.println(name + "来取钱,余额不足");
}
else{
System.out.println(name + "取出额度为:" + i);
accound -= i;
System.out.println(name + "取钱后剩余余额为:" + accound);
}
}
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
Lock常用方法
变量类型 | 方法名称 | 说明 |
---|---|---|
void | lock() | 获得锁。 |
void | lockInterruptibly () | 除非当前线程是interrupted,否则获取锁定。 |
Condition | newCondition() | 返回一个新Condition绑定到该实例Lock实例。 |
boolean | tryLock () | 只有在调用时他是空闲的才能获取锁。 |
boolean | tryLock (long time,TimeUnit unit) | 如果锁在给定的等待时间内是空闲的并且当前线程不是interrupted,则获取锁。 |
void | unlock () | 释放锁定。 |
什么是线程池?
答:线程池就是一个可以复用线程的技术。
不使用线程池的问题
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
谁代表线程池?
答:JDK5.0起提供了代表线程池的接口:ExecutorService。
如何得到线程池对象?
public ThreadPoolExecutor(int corePoolsize, //指定线程池的核心线程的数量
int maximumPoolsize,//指定线程池的最大线程数量
long keepAliveTime,//指定临时线程的存活时间。
TimeUnit unit, //指定临时线程存活的时间单位(秒、分、时、天)
BlockingQueue<Runnable>workQueue,//指定线程池的任务队列
ThreadFactory threadFactory,//指定线程池的线程工厂
RejectedExecutionHandler handler)//指定线程池的任务拒绝策略
//(线程都在忙,任务队列也满了的时 //候,新任务来了该怎么处理
public class Main {
public static void main(String[] args) {
//1、通过TreadPoolExecutor创建一个线程池对象,
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),//使用默认的线程池创建方法
new ThreadPoolExecutor.AbortPolicy());
}
}
1、临时线程什么时候创建?
答:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2、什么时候会开始拒绝新任务?
答:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
方法名称 | 说明 |
---|---|
void execute(Runnable comand) | 执行 Runnable 任务 |
Future submit(Callable task) | 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果 |
void shutdown() | 等全部任务执行完毕后,再关闭线程池 |
List shutdownNow | 立即关闭线程池,停止正在执行的任务,并返回队列中未执行任务 |
public class MyRunable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
//1、通过TreadPoolExecutor创建一个线程池对象,
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),//使用默认的线程池创建方法
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunable();
// 线程池最开始初始化时是没有线程的
// 当某线程任务结束后线程池不会自动销毁线程
// 当线程池接受到新任务后如果没有空闲线程会自动创建新线程处理这个任务
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
}
}
pool-1-thread-3正在工作
pool-1-thread-2正在工作
pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-2正在工作
//线程一旦创建线程池就不会自动关闭,所以整个程序会一直处于运行状态不会停
public class Main {
public static void main(String[] args) {
//1、通过TreadPoolExecutor创建一个线程池对象,
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),//使用默认的线程池创建方法
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunable();
// 线程池最开始初始化时是没有线程的
// 当某线程任务结束后线程池不会自动销毁线程
// 当线程池接受到新任务后如果没有空闲线程会自动创建新线程处理这个任务
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.shutdown();//等全部任务执行完毕后,再关闭线程池
// pool.shutdownNow();//立即关闭线程池,停止正在执行的任务,并返回队列中未执行任务
}
}
pool-1-thread-2正在工作
pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-2正在工作
pool-1-thread-1正在工作
进程已结束,退出代码0
public class Main {
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),//使用默认的线程池创建方法
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunable();
//模拟临时线程创建时机
//3个核心线程在忙
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 4个任务队列满了
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//如果还有任务请求,则开始创建临时线程
// pool.execute(target);
pool.shutdown();//等全部任务执行完毕后,再关闭线程池
// pool.shutdownNow();//立即关闭线程池,停止正在执行的任务,并返回队列中未执行任务
}
}
pool-1-thread-3正在工作
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-3正在工作
pool-1-thread-1正在工作
进程已结束,退出代码0
package org.example;
import org.example.ExecutorService.MyRunable;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
//1、通过TreadPoolExecutor创建一个线程池对象,
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),//使用默认的线程池创建方法
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunable();
//模拟临时线程创建时机
//3个核心线程在忙
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 4个任务队列满了
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//如果还有任务请求,则开始创建临时线程
pool.execute(target);
pool.execute(target);
pool.shutdown();//等全部任务执行完毕后,再关闭线程池
// pool.shutdownNow();//立即关闭线程池,停止正在执行的任务,并返回队列中未执行任务
}
}
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-3正在工作
pool-1-thread-4正在工作//创建了4、5的临时线程
pool-1-thread-5正在工作
pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-4正在工作
pool-1-thread-2正在工作
进程已结束,退出代码0
策略 | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException.异常。是默认的策略 |
ThreadPoolExecutor.DiscardPolicy: | 丢弃任务,但是不抛出异常这是不推荐的做法 |
ThreadPoolExecutor.DiscardoldestPolicy | 抛弃队列中等待最久的任务然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的ru0方法从而绕过线程池直接执行 |
public class Main {
public static void main(String[] args) {
//1、通过TreadPoolExecutor创建一个线程池对象,
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),//使用默认的线程池创建方法
new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略
Runnable target = new MyRunable();
//模拟临时线程创建时机
//3个核心线程在忙
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 4个任务队列满了
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//如果还有任务请求,则开始创建临时线程
pool.execute(target);
pool.execute(target);
//还有任务请求会自动调用指定的拒绝策略
pool.execute(target);
pool.shutdown();//等全部任务执行完毕后,再关闭线程池
// pool.shutdownNow();//立即关闭线程池,停止正在执行的任务,并返回队列中未执行任务
}
}
//调用CallerRunsPolicy拒绝策略
pool-1-thread-2正在工作
main正在工作
pool-1-thread-1正在工作
pool-1-thread-5正在工作
pool-1-thread-3正在工作
pool-1-thread-4正在工作
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-4正在工作
pool-1-thread-3正在工作
进程已结束,退出代码0
public class MyCallable implements Callable<String> {
int n;
public MyCallable(int n){
this.n = n;
}
// 2、重写call方法,指定返回类型
@Override
public String call() throws Exception {
int sum = 0;
for(int i = 1; i <= this.n; i++){
sum += i;
}
return Thread.currentThread().getName() + "求出》1-" + n + "的和为:" + sum;
}
}
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
//使用线程处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
pool.shutdown();//等全部任务执行完毕后,再关闭线程池
}
}
pool-1-thread-1求出》1-100的和为:5050
pool-1-thread-2求出》1-200的和为:20100
pool-1-thread-3求出》1-300的和为:45150
pool-1-thread-2求出》1-400的和为:80200
进程已结束,退出代码0
方法名称 | 说明 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newsingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了6s则会被回收掉。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolsize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
Java总共定义了6种状态。
6种状态都定义在Thread类的内部枚举类中
public class Thread{
...
public enum State{
NEW, //新建状态
RUNNABLE, //运行状态
BLOCKED, //阻塞状态
WAITING, //计时等待状态
TIMED WAITING, //无限等待状态
TERMINATED; //终止状态
}
}
线程状态 | 说明 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。 |
Runnable(可运行) | 线程已经调用了start(),等待CPU调度。 |
B1 ocked(锁阻塞) | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态。 |
Waiting(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态。 |
Teminated(被终止) | 因为ru方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
注意得到锁的线程调用 sleep() 进入等待状态,期间不会释放锁,而 wait() 会释放锁。