多线程
1.线程简介
程序、进程、线程
程序:指令和数据的有序集合,静态概念
进程:执行程序的一次执行过程,动态概念
一个进程中可以包含若干个线程
线程:进程中默认的线程main( )用户线程、GC守护线程
2.线程创建
2.1继承Thread类【重点】
- 自定义线程类继承Thread类
- 重写run( )方法,编写线程执行体
- 创建线程对象,调用start( )方法启动线程
【注意】:线程开启不一定立即执行,由cpu调度执行
java
package thread;
public class TestThread extends Thread{
@Override
public void run(){
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("run方法线程体执行中....");
}
}
public static void main(String[] args) {
// main线程,主线程
// 创建一个线程对象
TestThread thread = new TestThread();
// 调用start( )方法开启线程
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("主线程正在执行中....");
}
}
}
练习thread,实现多线程同步下载图片
java
package com.yehuda.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread02 extends Thread{
private String url;
private String name;
public TestThread02(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件:"+name);
}
public static void main(String[] args) {
TestThread02 t1 = new TestThread02("https://mp-fb49de55f308fef4a804.cdn.bspapp.com/121.jpg","1.jpg");
TestThread02 t2 = new TestThread02("https://mp-fb49de55f308fef4a804.cdn.bspapp.com/121.jpg","2.jpg");
TestThread02 t3 = new TestThread02("https://mp-fb49de55f308fef4a804.cdn.bspapp.com/121.jpg","3.jpg");
t1.start();
t2.start();
t3.start();
}
class WebDownloader{
public void downloader(String url ,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader出现问题!");
}
}
}
}
【构造方法】
2.2实现Runnable接口【重点*】
java
package com.yehuda.thread;
//多个线程同时操作一个对象
public class TestThread04 implements Runnable {
private int tickerNums = 10;
@Override
public void run() {
while (true){
if (tickerNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ tickerNums-- +"张票!");
}
}
public static void main(String[] args) {
TestThread04 thread04 = new TestThread04();
new Thread(thread04,"小明").start();
new Thread(thread04,"老师").start();
new Thread(thread04,"黄牛").start();
}
}
//结果中会有两条线程抢同一张票
龟兔赛跑练习
java
package com.yehuda.thread;
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 1000; i++) {
if (Thread.currentThread().getName().equals("乌龟")&&i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
private boolean gameOver(int steps){
if (winner!=null){
return true;
}else if (steps>=1000){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}else {
return false;
}
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
2.3实现Callable接口【了解】
- 可以定义返回值
- 可以返回异常
java
package com.yehuda.thread02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url,String name){
this.url = url;
this.name = name;
}
@Override
public Boolean call(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建对象
TestCallable t1 = new TestCallable("https://mp-fb49de55-5118-456c-af62-f308fef4a804.cdn.bspapp.com/121.jpg","1.jpg");
TestCallable t2 = new TestCallable("https://mp-fb49de55-5118-456c-af62-f308fef4a804.cdn.bspapp.com/121.jpg","2.jpg");
TestCallable t3 = new TestCallable("https://mp-fb49de55-5118-456c-af62-f308fef4a804.cdn.bspapp.com/121.jpg","3.jpg");
// 1.创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 2.提交执行服务
Future<Boolean> r1 = executorService.submit(t1);
Future<Boolean> r2 = executorService.submit(t2);
Future<Boolean> r3 = executorService.submit(t3);
// 3.获取结果
boolean res1 = r1.get();
boolean res2 = r2.get();
boolean res3 = r3.get();
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
// 关闭服务
executorService.shutdownNow();
}
public class WebDownloader{
public void downloader(String url ,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader出现问题!");
}
}
}
}
3.静态代理
java
package com.yehuda.staticPeoxy;
//静态代理模式总结:
//真实对象和代理对象都实现了同一个接口
//代理对象要代理真实对象
//好处:
//代理对象可以做很多真实对象做不了的事情
//真是对象专注做自己的事情
public class StaticProxy {
public static void main(String[] args) {
You you = new You();
new WeddingCompany(you).HappyMarry();
// new Thread(target).start();
}
}
interface Marry{
void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("进大厂,结婚!");
}
}
// 代理角色,协助你结婚
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany (Marry target){
this.target=target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("结婚前,布置现场!!");
}
private void after() {
System.out.println("结婚之后,收尾款!!!");
}
}
4.Lamda表达式
函数式接口:任何接口,只包含唯一一个抽象方法
对于函数式接口,我们可以通过lamda表达式来创建接口对象
4.1为什么要用lamda表达式
- 避免匿名内部类定义过多
- 让代码看起来简洁
- 去掉没有意义的代码,留下核心的逻辑
一步一步简化过程
java
package com.yehuda.lamda;
public class TestLamda {
// 3.静态内部类
static class Like02 implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lamda();
like = new Like02();
like.lamda();
// 4.局部内部类
class Like3 implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda3");
}
}
like = new Like3();
like.lamda();
// 5.匿名内部类,没有类的名称,必须借助接口或则父类
like = new ILike() {
@Override
public void lamda() {
System.out.println("i like lamda4");
}
};
like.lamda();
// 6.lamda表达式
like = ()->{
System.out.println("i like lamda5");
};
like.lamda();
}
}
//1.定义一个函数式接口
interface ILike{
void lamda();
}
//2.实现类
class Like implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda1");
}
}
Lamda表达式简化
- lamda表达式,只有一行可以简化成一行代码,否则需要代码块包裹
- 前提是接口为函数式接口,一个抽象函数
- 多个参数也可以去掉参数类型,要么全去掉,要加括号,ILove love04 = (a,b,c)-> System.out.println(a+b+c);
java
//lamda表达式简化
package com.yehuda.lamda;
public class TestLamda02 {
public static void main(String[] args) {
ILove love = (int a)-> {
System.out.println(a);
};
love.love(520);
// 1.简化参数类型
ILove love02 = (a)-> {
System.out.println(a);
};
love02.love(521);
// 2.简化括号
ILove love03 = a-> {
System.out.println(a);
};
love03.love(522);
// 3.去掉花括号
ILove love04 = a-> System.out.println(a);
love04.love(523);
// 总结:
// lamda表达式,只有一行可以简化成一行代码,否则需要代码块包裹
// 前提是接口为函数式接口,一个抽象函数
// 多个参数也可以去掉参数类型,要么全去掉,要加括号ILove love04 = (a,b,c)-> System.out.println(a+b+c);
}
}
interface ILove{
void love(int a);
}
5.线程状态
五个状态:新生、就绪、运行、阻塞、死亡
5.1线程停止
- 不推荐使用 JDK 提供的stop( ) 、destroy( )方法
- 推荐线程自己停下来,使用一个标志位进行终止变量,当flag=false,则终止线程运行
java
package com.yehuda.state;
//测试STOP线程
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或destory等过时或JDK不建议使用的方法
public class TestStop implements Runnable{
// 1.设置标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run.....Thread..." + i++);
}
}
// 2.设置一个公开方法终止线程,转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main线程"+i);
if (i==900){
// 调用stop方法,是线程停止
testStop.stop();
System.out.println("线程停止..");
}
}
System.out.println("main线程停止...");
}
}
5.2线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态;
- sleep可以模拟网络延迟、倒计时等;
- 【每一个对象多有一个锁,sleep不会释放锁】
模拟网络延时
java
package com.yehuda.state;
import com.yehuda.thread01.TestThread04;
//模拟网络延时:放大问题的发生性
public class TestSleep implements Runnable{
private int tickerNums = 10;
@Override
public void run() {
while (true){
if (tickerNums<=0){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ tickerNums-- +"张票!");
}
}
public static void main(String[] args) {
TestThread04 thread04 = new TestThread04();
new Thread(thread04,"小明").start();
new Thread(thread04,"老师").start();
new Thread(thread04,"黄牛").start();
}
}
设置倒计时/打印当前时间
java
package com.yehuda.state;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟倒计时
public class TestStop02 {
public static void main(String[] args) {
// 倒计时
// try {
// tenDown();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// 打印当前时间
// 获取当前系统时间
Date date = new Date(System.currentTimeMillis());
while (true){
try {
Thread.sleep(1000);
//时间格式化
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());//更新时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static void tenDown() throws InterruptedException {
int ten = 10;
while (true){
Thread.sleep(1000);
System.out.println(ten--);
if (ten<=0){
break;
}
}
}
}
5.3线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转换为就绪状态
- 让cpu重新调度,礼让不一定成功!看CPU调度
java
package com.yehuda.state;
//线程礼让不一定成功
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始....");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止....");
}
}
5.4线程插队
- 其他线程阻塞,执行插队线程
- 一开始CPU调度main线程和VIP线程,VIP线程join插队后,CPU只调度VIP线程
java
package com.yehuda.state;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("VIP线程插队来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin join = new TestJoin();
Thread thread = new Thread(join);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main线程"+i);
if (i == 200){
thread.join();//VIP线程插队
}
}
}
}
5.5线程状态
java
package com.yehuda.state;
//观察测试线程状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("...............");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state); //RUN
while (state != Thread.State.TERMINATED){
Thread.sleep(1000);
state = thread.getState();
System.out.println(state);
}
}
}
5.6线程优先级
- 先设置优先级再执行
- 优先级低只意味着获得CPU调度的概率低
java
package com.yehuda.state;
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread01 = new Thread(myPriority);
Thread thread02 = new Thread(myPriority);
Thread thread03 = new Thread(myPriority);
Thread thread04 = new Thread(myPriority);
thread01.start();
thread02.setPriority(10);
thread02.start();
thread03.setPriority(7);
thread03.start();
thread04.setPriority(3);
thread04.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
5.7守护进程
- 线程分为用户进程和守护进程
- 虚拟机必需确保用户进程执行完毕
- 虚拟机不必等待守护进程执行完毕
- 守护进程,后台记录操作日志、监控日志、垃圾回收等待...
java
package com.yehuda.state;
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);
thread.start();
new Thread(you).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝线程永存!!!");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("人活36500天,第"+i+"天!!");
}
System.out.println("goodBuy World!!!");
}
}
6.线程同步
- 解决线程安全性,Synchronized
- 线程同步其实就是排队、一个等待机制
- 多个需要同时访问此对象的线程进入这个对象的等待池
- 保证安全,会损失性能
- 优先级高的线程等待优先级低的线程释放锁,会引起性能导致问题
形成条件:队列+锁
同步方法:控制对"对象的访问",方法一旦执行,就独占锁synchronized默认锁的是this,及方法所在的方法
同步块:synchronized(Obj){},Obj为同步监视器,可以为任何对象
锁的对象要是增删改的对象
案例:
- 演唱会卖票
- 银行取钱
- ArrayList-->CopyOnwriteArrayList(线程安全)
7.死锁
- 多个线程各自占有部分共享资源,且互相等待其他线程释放资源才能运行,的停止执行的场景。【多个线程互相抱着对方需要的资源,然后形成僵持】
8.Lock锁
对比Synchronized
- Lock是显示锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
- lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM花较少时间来调度线程,性能更好,并具有更好的扩展性(提供更多的子类,ReentrantLock)
- 使用顺序
- Lock>同步代码块>同步方法
9.线程协作
生产者消费者模式(非设计模式)
多线程之间通信
- 线程同步问题
- 生产者和消费者共享一个资源
- 两者互为依赖、互为条件
Producer---->数据缓存区----->Consumer
线程通信提供方法:
- wait( ) :通知线程一直等待,直到其他线程通知,与sleep不同,会释放锁
- notify( ) :唤醒一个处于等待状态的线程
- 均是Object类的方法,都只能在同步方法和同步代码块中使用,否则抛出IIIegalMonitorStateException
-
管程法:利用缓存区解决
- 生产者
- 构造器(缓存区)
- 重写run方法,生产产品
- 消费者
- 产品
- id
- 缓冲区
- 容器大小,设置容器计数器
- 生产者放入
- 容器满了,通知消费者消费
- 没有满
- 消费者取出
- 判断能否消费,没有则等待生产者生产
- 如果可以则消费
javapackage com.yehuda.ProCen; //管程法 public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Producer(container).start(); new Consumer(container).start(); } } //生产者 class Producer extends Thread{ SynContainer container; public Producer(SynContainer container){ this.container = container; } // 生产 @Override public void run() { for (int i = 1; i <= 100; i++) { container.push(new Chicken(i)); System.out.println("生产了第"+i+"个鸡"); } } } //消费者 class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container){ this.container = container; } @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println("消费了第"+container.pop().c_id+"只鸡"); } } } class Chicken { public Chicken(int c_id) { this.c_id = c_id; } public int c_id; } class SynContainer { // 容器大小 Chicken[] chickens = new Chicken[10]; // 计数器 int count = 0; // 生产者放入 public synchronized void push(Chicken chicken){ if (count == chickens.length){ // 容器满了 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 容器美满 chickens[count] = chicken; count++; // 通知消费者消费 this.notifyAll(); } // 消费者消费 public synchronized Chicken pop(){ if (count == 0){ // 等待生产者生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 消费 Chicken chicken = chickens[--count]; this.notifyAll(); return chicken; } }
- 生产者
-
信号灯法:标志位法
java
package com.yehuda.ProCen;
//信号灯法
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%3==0){
this.tv.play("快乐大本营第"+i+"集");
}else {
this.tv.play("相亲大作战");
}
}
}
}
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
class TV{
String voice;
boolean flag = true;
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演:"+voice);
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看:" + voice);
this.notifyAll();
this.flag = !this.flag;
}
}
10.线程池
避免频繁创建、销毁线程
- 提高响应速度
- 降低资源消耗
- 便于线程管理
- JDK5.0开始提供了线程池相关的API:ExecutorService和Executors
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable;Futuresubmit(Callabletask):执行任务,有返回值,一般又来执行Callable
- 关闭连接池
service.shutdownNow()
:- 调用
shutdownNow()
方法会立即关闭线程池,即停止所有正在执行的任务,并返回尚未执行的任务列表。 - 该方法会尝试中断正在执行的任务,如果任务能够响应中断并正确处理,则任务会被中断,否则任务可能会继续执行。
- 该方法返回一个List,包含尚未执行的任务列表。
- 调用
service.shutdown()
:- 调用
shutdown()
方法会平缓地关闭线程池,即不再接受新的任务,但会等待已经提交的任务执行完毕。 - 该方法不会中断正在执行的任务,而是等待它们执行完成。
- 一旦所有任务执行完毕,线程池会被关闭
- 调用