多线程
GitHub - SoftLeaderGy/StartThread
一、线程的相关简介
二、线程的创建(Thread、Runnable)
(Thread、Runnable、Callable)
1、方式一(继承Thread类)
- 步骤:
- 继承Thread类
- 重写run()方法
- 主线程调用start()方法开启线程
- 总结:
- 线程开启不一定立即执行,由cpu就行调度
- 代码:
java
/**
* 创建线程类
* 重写run()方法
* 调用start开启线程
*/
public class TestThread01 extends Thread{
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码--" + i);
}
}
public static void main(String[] args) {
// 创建一个线程对象
TestThread01 testThread01 = new TestThread01();
// 调用start方法开启线程
testThread01.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习--" + i);
}
}
}2、方式二(实现Runnable)
- 步骤:
- 实现Runnable接口具有多线程的能力
- 启动线程:传入目标对象 + Thread对象.start()
- 代码
package com.yang.demo01;
/**
* 实现Runnable接口,实现多线程
* 实现Runnable接口、重写run方法、执行线程需要丢入Runnable接口的实现类、调用start方法
*/
public class TestThread03 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我在看书 -->" + i);
}
}
public static void main(String[] args) {
TestThread03 testThread03 = new TestThread03();
// 开启线程
new Thread(testThread03).start();
for (int i = 0; i < 100; i++) {
System.out.println("我在学习 -->" + i);
}
}
}- 总结
- 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
三、初识并发问题
- 买票案例(10张车票,三个人同时买票)
- 代码:
java
package com.yang.demo01;
public class TestThread04 implements Runnable{
// 定义车票一共100张票
private int TICKET = 10;
@Override
public void run() {
while (true) {
if(TICKET <= 0){
break;
}
// 电脑速度过快,添加延时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第--->"+ TICKET-- + "张票");
}
}
public static void main(String[] args) {
TestThread04 testThread04 = new TestThread04();
new Thread(testThread04,"小明").start();
new Thread(testThread04,"小李").start();
new Thread(testThread04,"小王").start();
}
}- 发现问题
- 多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
四、线程创建(Callable)
- 步骤
- 实现Callable接口,需要返回值类型
- 重写Callable接口,需要抛出异常
- 创建实现Callable接口的目标对象
- 创建执行服务:
- // 创建服务 Executors.newFixedThreadPool(3); 创建线程数为3个
- ExecutorService executorService = Executors.newFixedThreadPool(3);
- 提交执行
- // 提交执行任务,c1为实现Callable接口实现类的对象
- Future s1 = executorService.submit(c1);
- 获取结果
- // 获取执行线程返回结果
- System.out.println(s1.get());
- 关闭服务
- executorService.shutdownNow();
- 代码
java
package com.yang.demo02;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable01 implements Callable<Boolean> {
public TestCallable01(String url, String name) {
this.url = url;
this.name = name;
}
private String url;
private String name;
@Override
public Boolean call() throws Exception {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载文件完成,文件名为--"+ name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable01 c1 = new TestCallable01("https://lmg.jj20.com/up/allimg/4k/s/02/2109242332225H9-0-lp.jpg", "1.jpg");
TestCallable01 c2 = new TestCallable01("https://lmg.jj20.com/up/allimg/tp01/1ZZQ20QJS6-0-lp.jpg","2.jpg");
TestCallable01 c3 = new TestCallable01("https://lmg.jj20.com/up/allimg/tp04/1Z92G92I25110-0-lp.jpg","3.jpg");
// 创建服务 Executors.newFixedThreadPool(3); 创建线程数为3个
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交执行任务
Future<Boolean> s1 = executorService.submit(c1);
Future<Boolean> s2 = executorService.submit(c2);
Future<Boolean> s3 = executorService.submit(c3);
// 获取执行线程返回结果
System.out.println(s1.get());
System.out.println(s2.get());
System.out.println(s3.get());
// 关闭服务
executorService.shutdownNow();
}
}
/**
* 下载器
*/
@Slf4j
class WebDownloader {
public void downloader(String url, String name) {
try {
// commons.io包 下载文件工具包
// 通过URL下载图片
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
log.info("IO异常,downloader方法出现异常!");
}
}
}五、守护线程(daemon)
- 线程
- 用户线程(main)
- 守护线程(gc)
- 虚拟机必须要确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等。
- 测试代码
java
package com.yang.state;
/**
* @Description: 测试守护线程
* @Author: Guo.Yang
* @Date: 2022/02/24/23:01
*/
public class TestDaemon {
public static void main(String[] args) {
You you = new You();
God god = new God();
// 开启上帝线程
Thread thread = new Thread(god);
// 设置上帝线程为守护线程
thread.setDaemon(true);
// 开启用户线程
thread.start();
//开启用户线程
new Thread(you).start();
}
}
// 你(用户进程)
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("开心的活着");
}
System.out.println("good bye");
}
}
// 上帝线程(守护线程)
class God implements Runnable {
@Override
public void run() {
// 正常的线程 应该为上帝线程会一直执行下去
// 但由于是守护线程,也就是用户线程停止后,守护线程会自动结束
while (true) {
System.out.println("上帝守护着你");
}
}
}- God线程在不设置守护线程的情况下会是一直执行下去的进程
- 但是设置成守护线程后,用户线程执行完毕后会自动结束线程
测试截图
六、线程不安全性
1、测试List集合
- 测试List集合线程不安全性
- 众所周知List集合不安全分析下原因
- 测试代码
java
/**
* @Description: 测试List 的线程不安全性
* @Author: Guo.Yang
* @Date: 2022/02/25/21:05
*/
public class TestList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
// 输出结果:9999预计输出情况:10000 但是在运行后,有时后出现 小于 10000
- 原因
- 当两个线程同时操作list的同一个位置的时候,会覆盖掉上一个线程添加的元素
2、测试买票
- 测试代码
java
import lombok.SneakyThrows;
/**
* @Description:
* @Author: Guo.Yang
* @Date: 2022/02/25/21:48
*/
public class BuyTicket{
public static void main(String[] args) {
A buyticket = new A();
new Thread(buyticket, "张").start();
new Thread(buyticket, "王").start();
new Thread(buyticket,"李").start();
}
}
class A implements Runnable{
private int ticketNums = 10;
private Boolean flag = true;
@SneakyThrows
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() throws InterruptedException {
if(ticketNums <= 0){
flag = false;
return;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "拿到了"+ ticketNums--);
}
}- 结果输出
- 数据出现问题
java
// 张拿到了10
// 王拿到了9
// 李拿到了9
// 李拿到了8- 解决方案
- 同步方法
- synchronized方法
- synchronized方法控制“对象”的方法,每个对象对应一把锁,每个synchronized凡法规都必须获得到该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后边被阻塞的线程才能获得这个锁,继续执行
- 缺陷;若将一个大的方法申明为synchronized将会影响效率
- synchronized块
- synchronized(obj){}
- obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需执行同步监视器,因为同步方法的同步监视器就是this,就是对像本身,活着是class
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程防伪,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没锁锁,然后锁定并访问
- synchronized方法
- synchronized方法
- 测试代码
- 同步方法
java
import lombok.SneakyThrows;
/**
* @Description:
* @Author: Guo.Yang
* @Date: 2022/02/25/21:48
*/
public class BuyTicket{
public static void main(String[] args) {
A buyticket = new A();
new Thread(buyticket, "张").start();
new Thread(buyticket, "王").start();
new Thread(buyticket,"李").start();
}
}
class A implements Runnable{
private int ticketNums = 10;
private Boolean flag = true;
@SneakyThrows
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() throws InterruptedException {
if(ticketNums <= 0){
flag = false;
return;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "拿到了"+ ticketNums--);
}
}* synchronized块
* 代码块锁对象(一定要锁共享资源的变量,谁变锁谁)
* 测试代码java
/**
* @Description: 测试List 的线程不安全性
* @Author: Guo.Yang
* @Date: 2022/02/25/21:05
*/
public class TestList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
Thread.sleep(1);
}
System.out.println(list.size());
}
}七、线程池
- 线程池的API:ExecutorService 和 Executors
- ExecutorService:真正的线程池接口。常见的子类ThreadPoolExecutor
- void execute(Runnable command); 执行任务、命令没有返回值,一般用来执行Runable
- Future submit(Callable task); 执行任务,有返回值,一般用来执行Callable
- void shutdown(); 关闭链接
- Excutors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池