本章篇幅主要记录多线程编程相关的知识,如有纰漏,望指出。
话不多说,正式开启多线程之旅...
目录
一、多线程使用方式
A、Thread
B、Runnable(推荐)
C、Callable
二、线程的五个状态
三、线程停止
四、线程休眠sleep
五、线程礼让yield
六、线程插队join
七、线程优先级设置
八、守护线程deamon
九、线程同步
A、数据并发解决办法-synchronized
B、线程安全集合-CopyOnWriteArrayList
C、可重用锁-ReentrantLock
十、死锁
十一、线程池
一、多线程使用方式
A、Thread
使用方法:继承Thread类,不建议使用,避免OOP单继承局限性
示例:多线程下载网络图片
首先,百度搜索导入commonsio.jar,并将jar包导入项目中。
/**
* 封装下载器
*/
public class FileDownload {
public void download(String url, String fileName) {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
System.out.println("IO异常FileDownload");
}
}
}
/**
* 异步下载网络图片
*/
public class LessonThread1 extends Thread {
private String url;
private String fileName;
public LessonThread1(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}
@Override
public void run() {
FileDownload fileDownload = new FileDownload();
fileDownload.download(url, fileName);
System.out.println(fileName + "图片下载完成");
}
}
/**
* 执行下载
*/
public class Application {
public static void main(String[] args) {
new LessonThread1("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png", "D:\\code\\JavaProject\\Files\\1.jpg").start();
}
}
B、Runnable(推荐)
使用方法:实现Runnable接口,推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
示例:龟兔赛跑
public class Race implements Runnable {
private String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (Thread.currentThread().getName().contains("兔子") && i % 10 == 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if (gameOver(i)) {
break;
}
System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步");
}
}
private boolean gameOver(int i) {
if (winner != null) {
return true;
}
if (i >= 100) {
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
return false;
}
}
public class Application {
public static void main(String[] args) {
Race race = new Race();
Thread t1 = new Thread(race, "小兔子");
Thread t2 = new Thread(race, "乌龟");
t1.start();
t2.start();
}
}
C、Callable
使用方法略
二、线程的五个状态
三、线程停止
1、建议线程正常停止--->利用次数,不建议死循环;
2、建议使用标志位进行终止变量 ,当flag= false时终止线程运行--->设置一个标志位;
3、建议使用Thread.interrupt()标志位,原理同方法2;
4、不要使用stop或者destroy等过时API;
public class StopThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run thread num --->" + i++);
}
}
private void stop() {
flag = false;
}
public static void main(String[] args) {
StopThread stopThread = new StopThread();
new Thread(stopThread).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main thread num --->" + i);
if (i == 900) {
stopThread.stop();
System.out.println("StopThread线程该停止了");
}
}
}
}
四、线程休眠sleep
public class SleepThread {
public static void main(String[] args) {
try {
sleep();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static void sleep() throws InterruptedException {
int i = 10;
while (true) {
System.out.println(i--);
Thread.sleep(1_000);
if (i <= 0) {
break;
}
}
}
五、线程礼让yield
暂停自己的线程,和其他的线程重新竞争cpu资源
六、线程插队join
插队的线程优先执行,且插队线程执行完成后,其他线程才能继续执行。
七、线程优先级设置
Thread.setPriority(),范围从低到高是1-10
优先级越高,大概率会优先执行。
线程默认优先值为5
八、守护线程deamon
1、线程分为用户线程和守护线程;
2、虚拟机必须确保用户线程执行完毕,但不用等待守护线程执行完毕;
public class DaemonThread {
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread threadGod = new Thread(god, "上帝");
threadGod.setDaemon(true);//设置守护线程
threadGod.start();
new Thread(you, "你").start();
}
}
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30_000; i++) {
System.out.println("开心的活着");
}
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝保佑着你");
}
}
}
九、线程同步
多个线程操作同一个资源,就会造成数据混乱。
A、数据并发解决办法-synchronized
锁的对象:变化的量(并发时共同修改的那个变量)
有两种用法:synchronized方法和synchronized块
缺陷:如果方法加锁会大大影响效率
public class UnsafeList {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
//锁代码块,锁的对象是多线程要修改的公共变量
synchronized (strings) {
strings.add(Thread.currentThread().getName());
}
}
}.start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(strings.size());
}
}
B、线程安全集合-CopyOnWriteArrayList
public class TestJUC {
public static void main(String[] args) {
//位于java.util.concurrent包
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
strings.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(strings.size());
}
}
C、可重用锁-ReentrantLock
Lock只能锁代码块,synchronized可以锁代码块和方法
try{
lock.lock();
//代码逻辑
}finally{
lock.unLock();
}
十、死锁
定义:多个线程互相持有对方需要的资源,然后行程僵持的状态。
产生的条件:
1、一个资源每次只能被一个线程使用;
2、一个线程因请求资源而阻塞时,对已获得的资源保持不放;
3、线程已获得的资源,在未使用之前,不能强行剥夺;
4、若干线程之间形成一种头尾相接的循环等待资源关系;
public class DeadLock {
public static void main(String[] args) {
new Makeup(0, "灰姑凉").start();
new Makeup(1, "白雪公主").start();
}
}
class Lipstick {
//口红
}
class Mirror {
//镜子
}
class Makeup extends Thread {
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
private int choice;
private String girlName;
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
makeup();
}
private void makeup() {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(girlName + "持有口红锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (mirror) {
System.out.println(girlName + "持有镜子");
}
}
} else if (choice == 1) {
synchronized (mirror) {
System.out.println(girlName + "持有镜子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lipstick) {
System.out.println(girlName + "持有口红锁");
}
}
}
}
}
十一、线程池
ExecutorService和Executors
ExecutorService:真正的线程池接口
Executors:工具类、线程池的工厂类
end