多线程详解
java.Thread
1 线程简介
- 任务
- 进程,在操作系统中运行的程序就是进程,比如你的QQ,播放器,游戏,IDEA等等…
- 线程,一个进程中可以有多个线程,如视频中同时有声音,图像,弹幕等等…
- 多线程
Process与Thread
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 通常在一个进程中可以包含若干个线程,当然一个进程至少由一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
- 注意,很多线程是模拟出来的,真正的多线程是指由多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以由同时执行的错觉。
核心概念
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称为主线程,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为的干预。
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制;
- 线程会带来额外的开销,如CPU调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
2 线程实现(重点)
2.1 三种创建方式
- Thead.class 继承Thread类(重点)
- Runnable接口 实现Runnable接口(超重点)
- Callable 接口 实现Callable 接口(了解)
2.2 Thread
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
//多线程的实现方法1,继承Thread类,重写run方法,调用start开启线程
public class TestThreadDemo01 extends Thread{
@Override
public void run() {
//run方法线程体
super.run();
for (int i = 0; i < 20; i++) {
System.out.println("我再休息--"+i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThreadDemo01 testThreadDemo01 = new TestThreadDemo01();
//调用start()方法开启线程
testThreadDemo01.start();
//main线程,主线程
for (int i = 0; i < 2000; i++) {
System.out.println("我是主程序"+i);
}
}
}
- 注意,线程开启并不一定立即执行,由CPU调度执行
2.3 网图下载
此处首先要下载commons io包,直接去官网下载即可:http://commons.apache.org/proper/commons-io/download_io.cgi 下载bin包即可
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程下载图片
public class TestThreadDemo02 extends Thread{
private String url;
private String name;
public TestThreadDemo02(String url,String name) {
this.url = url;//下载图片的网址
this.name = name;//团片下载之后的名称
}
@Override
//下载图片执行体
public void run() {
WebDownloader downloader = new WebDownloader();
downloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
}
public static void main(String[] args) {
TestThreadDemo02 t1 = new TestThreadDemo02("http://p1.music.126.net/VeTFYQOCjME8SVU5wIxp0A==/109951166364721628.jpg", "1.jpg");
TestThreadDemo02 t2 = new TestThreadDemo02("https://p1.music.126.net/BMIIFM4kLUsq61x3_zFrLw==/109951166364373859.jpg", "2.jpg");
TestThreadDemo02 t3 = new TestThreadDemo02("https://p1.music.126.net/t0mf6zEnNF4vUp-i1Hb0qQ==/109951166365516036.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方法出现问题");
}
}
}
- 注意,三个线程并不一定是1、2、3的顺序执行
2.4 实现Runnable接口
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象(代理类对象),调用start()方法启动线程
//创建线程方法二,实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口的实现类,调用start()方法
public class TestThreadDemo03 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在学习呢=========="+i);
}
}
public static void main(String[] args) {
TestThreadDemo03 threadDemo03 = new TestThreadDemo03();
new Thread(threadDemo03).start();
for (int i = 0; i < 2000; i++) {
System.out.println("我在摸鱼"+i);
}
}
}
小结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
2.5 初识并发问题
- 多个线程擦欧总同一对象
- 多个线程操作同一资源时,线程不安全,数据紊乱。
//多个线程操作同一个对象。
//买火车票的例子、
//当同个线程操作同一资源时,线程不安全,数据紊乱
public class TestThreadDemo04 implements Runnable{
private int tickets = 10;
public void run() {
while (true){
if(tickets <=0){
break;
}
//模拟延迟,当有sleep的时候,有重复数据出现
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->买到了第"+(tickets--)+"张票");
}
}
public static void main(String[] args) {
TestThreadDemo04 t4 = new TestThreadDemo04();
new Thread(t4,"小明").start();
new Thread(t4,"小李").start();
new Thread(t4,"黄牛党").start();
}
}
2.6 龟兔赛跑
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中是乌龟赢的,兔子需要睡觉,所以我们需要模拟兔子睡觉
- 终于,乌龟赢得比赛。
//模拟龟兔赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; 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 step){
//判断是否有胜利者
if(winner != null){//已经存在胜利者了
return true;
}
if(step == 100){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
2.7 实现Callable接口(了解即可)
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1)
- 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//下载图片
//线程创建方式三,实现Callable接口
/*
Callable
1.可以定义返回值
2.可以抛出异常
*/
public class TestCallableDemo01 implements Callable<Boolean> {
private String url;
private String name;
public TestCallableDemo01(String url,String name) {
this.url = url;//下载图片的网址
this.name = name;//团片下载之后的名称
}
public Boolean call() throws Exception {
WebDownloader1 downloader = new WebDownloader1();
downloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestThreadDemo02 t1 = new TestThreadDemo02("http://p1.music.126.net/VeTFYQOCjME8SVU5wIxp0A==/109951166364721628.jpg", "1.jpg");
TestThreadDemo02 t2 = new TestThreadDemo02("https://p1.music.126.net/BMIIFM4kLUsq61x3_zFrLw==/109951166364373859.jpg", "2.jpg");
TestThreadDemo02 t3 = new TestThreadDemo02("https://p1.music.126.net/t0mf6zEnNF4vUp-i1Hb0qQ==/109951166365516036.jpg?", "3.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//2. 提交执行:
Future<Boolean> result1 = (Future<Boolean>) ser.submit(t1);
Future<Boolean> result2 = (Future<Boolean>) ser.submit(t2);
Future<Boolean> result3 = (Future<Boolean>) ser.submit(t3);
//3. 获取结果:
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
//4. 关闭服务:
ser.shutdownNow();
}
}
//下载器
class WebDownloader1{
//下载方法
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.8 实现静态代理对比Thread
静态代理总结
- 真实对象和代理对象都需要实现同一个接口
- 代理对象要代理真实角色
- 优点
- 代理对象可以做很多真实角色做不了的事情
- 真实对象专注做自己的事情
public class TestStaticProxyDemo01 {
public static void main(String[] args) {
//真实对象调用,只需要专注做自己的事情
You you = new You();//你要上学
you.happySchool();
//lamda表达式
new Thread(()-> System.out.println("虽然我不想学习啦,但是我还是要学!")).start();
//代理实现
Fullcareschool fullcareschool = new Fullcareschool(new You());
fullcareschool.happySchool();
//压缩实现
new Fullcareschool(new You()).happySchool();
}
}
//共同的接口
interface AttendSchool{
void happySchool();
}
class You implements AttendSchool{
@Override
public void happySchool() {
System.out.println("我要去上幼儿园啦,好开心!");
}
}
//代理角色,帮助你上学
class Fullcareschool implements AttendSchool{
//代理谁==》真实目标角色
private AttendSchool target;
public Fullcareschool(AttendSchool target) {
this.target = target;//这就是真实对象
}
@Override
public void happySchool() {
before();
this.target.happySchool();
after();
}
public void after(){
System.out.println("终于上学啦!");
}
public void before(){
System.out.println("准备开学用品");
}
}
2.9 lambda 表达式
-
λ希腊字母表中排序第十一位的字母,英文名称为Lambda
-
避免匿名内部类定义过多
-
其实质属于函数式编程的概念
- (params)->expression[表达式]
- (params)->statement[语句]
- (params)->{statements}
-
为什么要使用lambda表达式
- 避免匿名内部类定义过多
- 可以让你的代码看起来很简洁
- 去掉了一堆没有意义的代码,只留下核心的逻辑。
-
理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在。
-
函数式接口的定义:
-
对于接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
public interface Runnable{ public abstract void run(); }
-
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
-
/*推导lambda表达式 */public class TestLambdaDemo01 { //3. 静态内部类 static class Like2 implements ILike{ @Override public void lambda() { System.out.println("I like lambda22222!"); } } public static void main(String[] args) { System.out.println("===========类实现接口==========="); ILike like = new Like(); like.lambda(); System.out.println("===========静态内部类==========="); like = new Like2(); like.lambda(); //4.局部内部类 System.out.println("===========局部内部类==========="); class Like3 implements ILike{ @Override public void lambda() { System.out.println("I like lambda33333!"); } } like = new Like3(); like.lambda(); //5. 匿名内部类,没有类名,必须借助接口或者父类 System.out.println("===========匿名内部类==========="); like = new ILike(){ @Override public void lambda() { System.out.println("I like lambda4444444!"); } }; like.lambda(); //6. 用lambda简化 System.out.println("===========用lambda简化==========="); like = () -> { System.out.println("I like lambda555555!"); }; like.lambda(); }}//1.定义一个函数式接口interface ILike{ void lambda();}//2. 实现类class Like implements ILike{ @Override public void lambda() { System.out.println("I like lambda!"); }}
//带参数的函数式接口public class TestLambdaDemo02 { //静态内部类 static class Love2 implements ILove{ @Override public void love(int a) { System.out.println("我的幸运数字是==>"+a); } } public static void main(String[] args) { ILove l = new Love(); l.love(7); l = new Love2(); l.love(6); //局部内部类 class Love3 implements ILove{ @Override public void love(int a) { System.out.println("我的幸运数字是==>"+a); } } l = new Love3(); l.love(9); //匿名内部类 l = new ILove() { @Override public void love(int a) { System.out.println("我的幸运数字是==>"+a); } }; l.love(1); //lambda表达类型 l = (int a) -> { System.out.println("我的幸运数字是==>"+a); }; l.love(2); //简化类型一,简化括号 l = (a) -> { System.out.println("我的幸运数字是==>"+a); }; l.love(3); //简化类型二,去掉花括号 ILove m = a -> System.out.println("我的幸运数字是==>"+a); m.love(8); /* 总结: 1. lambda 表达式只有一行的时候才可以简化花括号,其他时候不能简化花括号,需要代码块 2. 前提是接口是函数式接口 3. 多个参数也可以去掉参数类型,要去掉就都去掉。 */ }}//共同的接口interface ILove{ void love(int a);}//实现类class Love implements ILove{ @Override public void love(int a) { System.out.println("我的幸运数字是==>"+a); }}
总结
- lambda 表达式只有一行的时候才可以简化花括号,其他时候不能简化花括号,需要代码块
- 前提是接口是函数式接口
- 多个参数也可以去掉参数类型,要去掉就都去掉。
- Runnable接口只有一个方法run(),可以用lambda表达式简化
3 线程状态
3.1 线程的五大状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v2LDHqth-1631538814218)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210909213923214.png)]
-
创建状态
Thread t = new Thread();//线程对象一旦创建,就进入到新生状态
-
就绪状态
当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行。
-
运行状态
就绪状态的进程经调度进入运行状态,进入运行状态的线程,才真正进入执行线程体的代码块。
-
阻塞状态
但调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,则色时间解除后,重新进入就绪状态,等待CPU调度执行。
-
死亡状态
线程中断或者结束,一旦进入死亡状态就不能再次启动。
-
线程方法
方法 说明 setPriority(int newPriority) 更改线程的优先级 static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠 void join() 等待该线程终止 static void yield() 暂停当前正在执行的线程对象,并执行其他线程 void interrupt() 中断线程(已废弃) boolean isAlive() 测试线程是否处于活动状态 -
停止线程
- 不推荐使用JDK提供的stop()、destory()方法【已废弃】
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量。但flag= false,则终止线程运行。
3.2 线程停止
- 建议线程正常停止----》利用次数,不建议死循环。
- 建议使用标志位—》设置一个标志位
- 不要使用stop或者destroy等过时或者jdk不建议使用的方法
//测试停止线程public class TestThreadStopDemo01 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) { TestThreadStopDemo01 threadStopDemo01 = new TestThreadStopDemo01(); new Thread(threadStopDemo01).start(); for (int i = 0; i < 1000; i++) { System.out.println("main ==="+i); if(i == 900){ threadStopDemo01.stop(); System.out.println("线程该停止啦~!"); } } }}
3.3 线程休眠 sleep
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态;
- sleep可以模拟网络延时,倒计时等。
- 每一个对象都有一个锁,sleep不hi释放锁;
//模拟网络延时,放大问题的发生性public class TestThreadSleepDemo01 implements Runnable{ private int tickets = 10; public void run() { while (true){ if(tickets <=0){ break; } //模拟延迟,当有sleep的时候,有重复数据出现 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->买到了第"+(tickets--)+"张票"); } } public static void main(String[] args) { TestThreadDemo04 t4 = new TestThreadDemo04(); //多个线程操作同一个对象不安全 new Thread(t4,"小明").start(); new Thread(t4,"小李").start(); new Thread(t4,"黄牛党").start(); }}
package com.raylene.basic.thread;//模拟倒计时public class TestThreadSleepDemo02 { public static void tenCountDown() throws InterruptedException { int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if(num <= 0){ break; } } } public static void main(String[] args) { try { tenCountDown(); } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.raylene.basic.thread;import java.text.SimpleDateFormat;import java.util.Date;//模拟倒计时public class TestThreadSleepDemo02 { //模拟倒计时 public static void tenCountDown() throws InterruptedException { int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if(num <= 0){ break; } } } public static void main(String[] args) { //打印当前系统时间 Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间 while (true){ try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime = new Date(System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }}
3.4 线程礼让 yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功!看CPU心情
//测试礼让线程 //礼让不一定成功,看CPU心情public class TestThreadYieldDemo01 { public static void main(String[] args) { MyYield yield = new MyYield(); new Thread(yield,"aa").start(); new Thread(yield,"bbb").start(); }}class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName()+"线程停止执行"); }}
3. 5 线程强制执行 join
- join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可以想象成插队。
public class TestThreadJoinDemo01 implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("可恶的插队人又来了"); } } public static void main(String[] args) throws InterruptedException { //启动我们的线程 TestThreadJoinDemo01 demo01 = new TestThreadJoinDemo01(); Thread thread = new Thread(demo01); thread.start(); //启动主线程 for (int i = 0; i < 1000; i++) { System.out.println("mian"+i); if(i == 500){ thread.join(); } } }}
3.6 观测线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8iUyJMd-1631538814226)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210910100454321.png)]
public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
package com.raylene.basic.thread;//观察测试线程的状态public class TestThreadStateDemo01 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); //观察状态 Thread.State state = thread.getState(); System.out.println(state); //观察启动后 thread.start(); state = thread.getState(); System.out.println(state); //持续观察,直到线程死亡 while(state != Thread.State.TERMINATED){ Thread.sleep(100);//0.1s的间隔 state = thread.getState();//更新线程状态,只有更新状态才可能跳出循环体 System.out.println(state); } //一旦线程进入死亡状态,就不能再次启动 System.out.println(thread.getPriority()); }}
3.7 线程优先级
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
-
线程的优先级用数字表示,范围从1~10
-
Thread.MAX_PRIORITY = 10;Thread.MIN_PRIORITY = 1;Thread.NORM_PRIORITY = 5;
-
-
线程通过以下方式改变获取优先级
- setPriority(int xxx);
- getPriority();
public class TestThreadPriorityDemo01{ public static void main(String[] args) { //打印主线程的默认优先级 System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); MyThreadPriority priority = new MyThreadPriority(); Thread t1 = new Thread(priority); Thread t2 = new Thread(priority); Thread t3 = new Thread(priority); Thread t4 = new Thread(priority); Thread t5 = new Thread(priority); Thread t6 = new Thread(priority); //先设置优先级,再启动 t1.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.setPriority(7); t2.start(); t3.setPriority(Thread.MIN_PRIORITY); t3.start(); t4.setPriority(9); t4.start(); t5.setPriority(2); t5.start(); t6.setPriority(3); t6.start(); }}class MyThreadPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); }}
3.8 守护(deamon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等…
//测试守护线程//1.上帝守护你public class TestThreadDeamonDemo01 { public static void main(String[] args) { God god = new God(); You1 you1 = new You1(); Thread t1 = new Thread(god); t1.setDaemon(true);//设为守护线程,默认为false表示是用户线程,正常的线程都是用户线程 t1.start();//上帝线程启动 new Thread(new You1()).start();//你 用户线程启动 }}//上帝class God implements Runnable{ @Override public void run() { while (true){ System.out.println("上帝永远活着"); } }}//你class You1 implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("a happy day!"); } System.out.println("=====>goodbye,world!"); }}
4 线程同步(重点)
并发
- 并发:同一个对象被多个线程同时操作
线程同步
- 现实生活中,同一资源,多人都想使用的问题,最天然的解决办法就是排队,一个个来。
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的进程就如这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
队列和锁
线程同步
- 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁时,独占资源,其他线程必须等待,待其使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
4.1 不安全的实例
//不安全的买票//线程不安全,有负数public class UnSafetyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket,"小明").start(); new Thread(buyTicket,"张三").start(); new Thread(buyTicket,"Tom").start(); new Thread(buyTicket,"杰西卡").start(); }}class BuyTicket implements Runnable{ //票 private int ticketNums = 10; //标志位 boolean flag = true;//外部停止方式 @Override public void run() { //买票 while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void buy() throws InterruptedException { //判断是否有票 if(ticketNums <= 0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+"买到了第"+(ticketNums--)+"张票"); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iY66vIZU-1631538814227)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210910162627094.png)]
//不安全的取钱//两个人分别去银行在同一账户取钱public class UnSafetyBank { public static void main(String[] args) { Account account = new Account(100_000, "买房存款"); BankDrawing bankDrawing = new BankDrawing(account, 80_000, "tom"); new Thread(bankDrawing).start(); new Thread(new BankDrawing(account,30_000,"jerry")).start(); }}class Account{ int money = 50_000;//余额 String name;//卡的名字 public Account(int money, String name) { this.money = money; this.name = name; }}//银行 模拟取款class BankDrawing extends Thread{ Account account;//账户 int drawingMoney;//取了多少钱 int nowMoney;//现在手里有多少钱 public BankDrawing(Account account,int drawingMoney, String name ){ super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 @Override public void run() { //判断有没有钱 if(account.money-drawingMoney < 0){ System.out.println(this.getName()+"取不了钱,余额不足!"); return; } //模拟延时 //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额更新 account.money -= drawingMoney; System.out.println(account.name+"余额为"+account.money); System.out.println(this.getName()+"取了"+drawingMoney+"块钱"); //this.getName() == Thread.currentThread().getName() }}
import java.util.ArrayList;public class UnSafetyList { public static void main(String[] args) throws InterruptedException { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } //放大问题的发生性 Thread.sleep(100); System.out.println(list.size()); }}
4.2 同步方法及同步块
同步方法
-
由于我们可以通过private 关键字来保证数据对象只能被方法访问,所以我们只需要针对方法一套机制,这套机制就是synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块
-
synchronized 方法控制对 “对象” 的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
- 缺点:若将一个大的方法申明为 synchronized 将会影响效率
-
同步方法的弊端
- 方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
//改造不安全的买票,加synchronized同步方法public class UnSafetyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket,"小明").start(); new Thread(buyTicket,"张三").start(); new Thread(buyTicket,"Tom").start(); new Thread(buyTicket,"杰西卡").start(); }}class BuyTicket implements Runnable{ //票 private int ticketNums = 10; //标志位 boolean flag = true;//外部停止方式 @Override public void run() { //买票 while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } //synchronized同步方法,锁的是this public synchronized void buy() throws InterruptedException { //判断是否有票 if(ticketNums <= 0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+"买到了第"+(ticketNums--)+"张票"); }}
同步块
- 同步块:synchronized( Obj ){ }
- Obj 称之为 同步监视器
- Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问。
- 第一个线程访问完毕,解锁同步监视器。
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
//不安全的取钱 加 synchronized 块public class UnSafetyBank { public static void main(String[] args) { //两个取钱的对象,所以synchronized方法锁不了两个对象,只能用同步块锁账户 Account account = new Account(100_000, "买房存款"); BankDrawing bankDrawing = new BankDrawing(account, 80_000, "tom"); new Thread(bankDrawing).start(); new Thread(new BankDrawing(account,30_000,"jerry")).start(); }}class Account{ int money = 50_000;//余额 String name;//卡的名字 public Account(int money, String name) { this.money = money; this.name = name; }}//银行 模拟取款class BankDrawing extends Thread{ Account account;//账户 int drawingMoney;//取了多少钱 int nowMoney;//现在手里有多少钱 public BankDrawing(Account account,int drawingMoney, String name ){ super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 //synchronized 默认锁的是this @Override public void run() { //锁的对象就是变化的量,需要增删改 synchronized (account){ //判断有没有钱 if(account.money-drawingMoney < 0){ System.out.println(this.getName()+"取不了钱,余额不足!"); return; } //模拟延时 //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额更新 account.money -= drawingMoney; System.out.println(account.name+"余额为"+account.money); System.out.println(this.getName()+"取了"+drawingMoney+"块钱"); //this.getName() == Thread.currentThread().getName() } }}
import java.util.ArrayList;public class UnSafetyList { public static void main(String[] args) throws InterruptedException { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ //同步块,同时只能有一个对象加入list synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } //放大问题的发生性 Thread.sleep(100); System.out.println(list.size()); }}
4.3 CopyOnWriterArrayList
import java.util.concurrent.CopyOnWriteArrayList;//测试JUC安全类型的集合public class TestJuc { public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(100); System.out.println(list.size()); }}
4.4 死锁
-
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一同步块同时拥有“两个以上对象的锁”时,就可能会发送"死锁"的问题。
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。public class DeadLock { public static void main(String[] args) { Makeup g1 = new Makeup(0, "灰姑凉"); Makeup g2 = new Makeup(1, "王冰冰"); g1.start(); g2.start(); }}//口红class Lipsticks{}//镜子class Mirror{}//化妆class Makeup extends Thread{ //需要的资源只有一份,用static来保证资源只有一份 static Lipsticks lipsticks = new Lipsticks(); static Mirror mirror = new Mirror(); int choice;//选择 String name;//使用化妆品的人名 Makeup(int choice, String name){ this.choice = choice; this.name = name; } @Override public void run() { //化妆 try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } //化妆 互相持有对方的锁, 就是需要拿到对方的资源 private void makeup() throws InterruptedException { if(choice == 0){ synchronized (lipsticks){ System.out.println(this.name+"获得了口红的锁"); Thread.sleep(1000); synchronized (mirror){ System.out.println(this.name+"获得了镜子的锁"); } } }else{ synchronized (mirror){ System.out.println(this.name+"获得了镜子的锁"); Thread.sleep(2000); synchronized (lipsticks){ System.out.println(this.name+"获得了口红的锁"); } } } }}
解锁
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。public class DeadLock { public static void main(String[] args) { Makeup g1 = new Makeup(0, "灰姑凉"); Makeup g2 = new Makeup(1, "王冰冰"); g1.start(); g2.start(); }}//口红class Lipsticks{}//镜子class Mirror{}//化妆class Makeup extends Thread{ //需要的资源只有一份,用static来保证资源只有一份 static Lipsticks lipsticks = new Lipsticks(); static Mirror mirror = new Mirror(); int choice;//选择 String name;//使用化妆品的人名 Makeup(int choice, String name){ this.choice = choice; this.name = name; } @Override public void run() { //化妆 try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } //化妆 互相持有对方的锁, 就是需要拿到对方的资源 private void makeup() throws InterruptedException { if(choice == 0){ //一个人不同时抱两把锁时,可以防止死锁 synchronized (lipsticks){ System.out.println(this.name+"获得了口红的锁"); Thread.sleep(1000); } synchronized (mirror){ System.out.println(this.name+"获得了镜子的锁"); } }else{ synchronized (mirror){ System.out.println(this.name+"获得了镜子的锁"); Thread.sleep(2000); } synchronized (lipsticks){ System.out.println(this.name+"获得了口红的锁"); } } }}
死锁避免的方法
- 产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
- 避免死锁的发生:只要避免其中任一或多个条件发生就可以避免死锁的发生。
- 产生死锁的四个必要条件:
4.5 Lock锁(可重入锁)
-
从 jdk 5.0 开始,Java提供了更强大的线程同步机制—通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
Java.util.concurrent.locks.Lock接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象
-
ReentrantLock 类实现了Lock , 它拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是RenntrantLock , 可以显式加锁、释放锁。
class A{private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock();//加锁, try{ //保持线程安全的代码块 }finally { lock.unlock();//解锁 //如果同步代码有异常,要将unlock写入finally语句块 } } }}
//未加锁import java.util.concurrent.locks.ReentrantLock;public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); }}class TestLock2 implements Runnable{ int ticketNums = 10; @Override public void run() { while (true){ if(ticketNums>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); }else { break; } } }}
//加显式锁import java.util.concurrent.locks.ReentrantLock;public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); }}class TestLock2 implements Runnable{ int ticketNums = 10; private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try{ lock.lock();//加锁,一般建议将代码块放入try..catch...finally中 if(ticketNums>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); }else { break; } }finally { lock.unlock();//解锁 } } }}
synchronized 与 Lock 的对比
- Lock是显式锁 (手动开启和关闭锁,别忘记关锁) synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized 有代码块锁和方法锁
- 使用Lock锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock -> 同步代码块(已经进入了方法体,分配了相应的资源) -> 同步方法(在方法体之外)
5 线程通信问题
5.1 生产者消费者问题
-
应用场景:生产者和消费者问题
- 假设仓库中只能存放意见产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
-
线程通信-分析
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,护卫条件。
- 对于生产者,没有生产产品之前,要通知消费者等待。二生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品一共消费。
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized 可阻止并发更新同一个共享资源,实现了同步。
- synchronized 不能用来实现不同线程之间的消息传递(通信)
-
线程通信
- Java 提供了几个方法解决线程之间的通信问题
方法名 作用 wait() 表示线程一直等大,直到其他线程通知,与sleep不同,会释放锁 wait(long timeout) 指定等待的毫秒数 notify() 唤醒ige处于等待状态的线程 notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程有限调度 - 注意:均是Object类,都只能在同步方法或者同步代码块 中使用,否则会抛出异常IllegalMonitorStateException
-
解决方式一
并发协作模式“生产者/消费者模式”---->管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
-
解决方式二
- 并发协作模式“生产者/消费者模式”—>信号灯法
5.2 管程法
//有问题、????????????????????package com.raylene.basic.thread;//测试生产者,消费者模型,利用缓冲区解决---管程法//生产者 消费者 产品 缓冲区public class TestPCDemo01 { public static void main(String[] args) { Syncontainer syncontainer = new Syncontainer(); new Producer(syncontainer).start(); new Consumer(syncontainer).start(); }}class Producer extends Thread{ Syncontainer syncontainer = new Syncontainer(); public Producer(Syncontainer syncontainer){ this.syncontainer = syncontainer; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { syncontainer.push(new Product(i)); System.out.println("生产了"+i+"只鸡!"); } }}class Consumer extends Thread{ Syncontainer syncontainer = new Syncontainer(); public Consumer(Syncontainer syncontainer){ this.syncontainer = syncontainer; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了"+syncontainer.pop().id+"只鸡"); syncontainer.pop(); } }}class Product extends Thread{ int id; public Product(int id){ this.id = id; }}//缓冲区class Syncontainer{ //需要一个缓冲区大小 Product[] products = new Product[10]; //容器计数器 int count = 0; //需要生产者生产产品放入其中 public synchronized void push(Product product){ //如果容器已满,就需要消费者消费 if(count == products.length){ //通知消费者消费,生产者进入生产等待状态 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,就需要丢入数据 if(count<100){ products[count] = product; count++; //可以通知消费者消费了 } this.notifyAll(); } //需要消费者从其中取出产品消耗 public synchronized Product pop(){ //判断能否消费 if(count == 0){ //等待生产者生产,消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费, count--; Product product = products[count]; //消费完成,可以通知生产者生产 this.notifyAll(); return product; }}
5.3 信号灯法
//测试生产者、消费者问题2:信号灯法,标志位解决public class TestPCDemo02 { public static void main(String[] args) { Tv tv = new Tv(); new Player(tv).start(); new Watcher(tv).start(); }}//生产者-----演员class Player extends Thread{ Tv tv = new Tv(); public Player(Tv tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 10; i++) { if(i%2 ==0){ this.tv.play("央视新闻"+i); }else{ this.tv.play("乔家的儿女"+i); } } }//消费者----观众class Watcher extends Thread{ Tv tv = new Tv(); public Watcher(Tv tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 10; i++) { this.tv.watch(); } }}//产品-----节目class Tv{ //演员表演时,观众等待 T //观众观看时,演员等待 F 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.flag = !this.flag; this.voice = voice; } //观看 public synchronized void watch(){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //通知观众观看 System.out.println("观众观看了"+voice); this.notifyAll(); this.flag = !this.flag; }}
5.4 线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完返回池中。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(…)
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAAliveTime:线程没有任务时最多保持多久后会终止。
使用线程池:
- JDK5.0起提供了线程池相关API: ExecutorService 和 Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;//测试线程池public class TestPool { public static void main(String[] args) { //1.创建服务,创建线程池 //newFixedThreadPool,参数为池子的大小 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭资源 service.shutdown(); }}class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); }}
6 高级主题总结
-
线程3种实现方式
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class TestThreadNew { public static void main(String[] args) { new MyThread1().start(); new Thread(new MyThread2()).start(); FutureTask<Integer> futureTask = new FutureTask<>(new MyThread3()); new Thread(futureTask).start(); try { Integer integer = futureTask.get(); System.out.println(integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }}//继承Thread类class MyThread1 extends Thread{ @Override public void run() { System.out.println("MyThread1"); }}//实现Runnable接口class MyThread2 implements Runnable{ @Override public void run() { System.out.println("MyThread2"); }}//实现Callable接口class MyThread3 implements Callable { @Override public Integer call(){ System.out.println("MyThread3"); return 100; }}
-
JUC
-
锁
-
同步synchronized