Java 多线程。
文章目录
概念。
线程就是独立的执行路径。
在程序运行时,即使没有自己创建线程,后台也会有多个线程(eg. 主线程,gc 线程)。
main() 称之为主线程,为系统的入口,用于执行整个程序。
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。
线程会带来额外的开销,eg. CPU 调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程创建 ~ 继承 Thread 类。
-
自定义线程类继承 Thread 类。
-
重写 run(); 方法,编写线程执行体。
-
创建线程对象,调用 start(); 方法启动线程。
package com.geek;
/**
* 线程创建 ~ 继承 Thread 类。
* <p>
* - 自定义线程类继承 Thread 类。
* <p>
* - 重写 run(); 方法,编写线程执行体。
* <p>
* - 创建线程对象,调用 start(); 方法启动线程。
*/
// 线程开启不一定立即执行,由 CPU 调度执行。
public class ThreadDemo00 extends Thread {
public static void main(String[] args) {
// 创建一个线程对象。
ThreadDemo00 threadDemo00 = new ThreadDemo00();
// 调用 start(); 方法开启线程。
threadDemo00.start();
// main 线程,主线程。
for (int i = 0; i < 200; i++) {
System.out.println("主线程 ~ " + i);
}
}
@Override
public void run() {
// super.run();
for (int i = 0; i < 20; i++) {
System.out.println("~" + i);
}
}
}
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThreadDownload extends Thread {
private String url;// 网络图片地址。
private String name;// 保存的文件名。
public TestThreadDownload(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) {
TestThreadDownload t1 = new TestThreadDownload("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "1.jpg");
TestThreadDownload t2 = new TestThreadDownload("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "2.jpg");
TestThreadDownload t3 = new TestThreadDownload("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "3.jpg");
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了文件:" + name);
}
}
// 下载器。
class WebDownloader {
// 下载方法。
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程创建 ~ 实现 Runnable 接口。
package com.geek;
public class TestThread02 implements Runnable {
public static void main(String[] args) {
// 创建 Runnable 接口的实现类对象。
TestThread02 testThread02 = new TestThread02();
// 创建线程对象。通过线程对象来开启线程。代理。
Thread thread = new Thread(testThread02);
// 调用 start(); 方法开启线程。
thread.start();
// main 线程,主线程。
for (int i = 0; i < 200; i++) {
System.out.println("主线程 ~ " + i);
}
}
@Override
public void run() {
// run 方法线程体。
for (int i = 0; i < 200; i++) {
System.out.println(" ~ " + i);
}
}
}
小结。
继承 Thread 类。
- 子类继承 Thread 类具备多线程能力。
- 启动线程:子类对象.start();。
- 不建议使用。避免 OOP 单继承局限性。
实现 Runnable 接口。
- 实现接口 Runnable 实现多线程能力。
- 启动线程:传入目标对象 + Thread 对象.start();。
- 推荐使用。避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
线程创建 ~ 实现 Callable 接口。
- 实现 Callable 接口,需要泛型。
- 重写 call(); 方法,需要抛异常。
- 创建目标对象。
- // 创建执行服务。
ExecutorService executorService = Executors.newFixedThreadPool(3);
- // 提交执行。
Future booleanFuture = executorService.submit(t1);
try {
- // 获取结果。
Boolean r1 = booleanFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
- // 关闭服务。
executorService.shutdownNow();
package com.geek.demo02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* 线程实现方式 3 ~ 实现 Callable 接口。
*/
// 实现 Callable 接口,需要泛型。
// 可以定义返回值。
// 需要抛异常。
public class TestCallable implements Callable<Boolean> {
private String url;// 网络图片地址。
private String name;// 保存的文件名。
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) {
// 创建目标对象。
TestCallable t1 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "1.jpg");
TestCallable t2 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "2.jpg");
TestCallable t3 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "3.jpg");
// 创建执行服务。
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交执行。
Future<Boolean> booleanFuture1 = executorService.submit(t1);
Future<Boolean> booleanFuture2 = executorService.submit(t2);
Future<Boolean> booleanFuture3 = executorService.submit(t3);
try {
// 获取结果。
Boolean r1 = booleanFuture1.get();
System.out.println("r1 = " + r1);
Boolean r2 = booleanFuture2.get();
System.out.println("r2 = " + r2);
Boolean r3 = booleanFuture3.get();
System.out.println("r3 = " + r3);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 关闭服务。
executorService.shutdownNow();
}
// 重写 call(); 方法,需要抛异常。
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了文件:" + name);
return true;
}
}
// 下载器。
class WebDownloader {
// 下载方法。
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程安全问题。
package com.geek;
/**
* 多个线程同时操作同一个对象。
* 发现问题。多个线程操作同一资源情况下,线程不安全。
*/
public class TestThreadMulti implements Runnable {
// 票数。
private int ticketNum = 10;
public static void main(String[] args) {
TestThreadMulti testThreadMulti = new TestThreadMulti();
new Thread(testThreadMulti, "小明").start();
new Thread(testThreadMulti, "老师").start();
new Thread(testThreadMulti, "黄牛").start();
}
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " 拿到了第 " + ticketNum-- + " 票。");
}
}
}
Thread[老师,5,main] 拿到了第 9 票。
Thread[黄牛,5,main] 拿到了第 8 票。
Thread[小明,5,main] 拿到了第 10 票。
Thread[老师,5,main] 拿到了第 7 票。
Thread[黄牛,5,main] 拿到了第 6 票。
Thread[小明,5,main] 拿到了第 5 票。
Thread[小明,5,main] 拿到了第 4 票。
Thread[老师,5,main] 拿到了第 3 票。
Thread[黄牛,5,main] 拿到了第 3 票。
Thread[黄牛,5,main] 拿到了第 2 票。
Thread[老师,5,main] 拿到了第 0 票。
Thread[小明,5,main] 拿到了第 1 票。
Process finished with exit code 0
龟兔赛跑。
package com.geek;
public class Race implements Runnable {
// 胜利者。
private static String winner;
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
@Override
public void run() {
for (int i = 1; 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 (null != winner) {// 已经存在胜利者。
return true;
}
{
if (step >= 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
}
}
return false;
}
}
静态代理模式。
结婚。
你:真实角色。
婚庆公司:代理你,帮你处理琐事。
结婚:实现结婚接口即可。
package com.geek.proxy;
interface Marry {
// 久旱逢甘雨
// 他乡遇故知
// 洞房花烛夜
// 金榜提名时
void happyMarry();
}
/**
* 静态代理模式。
* 真实对象和代理对象都要实现同一个接口。
* 代理对象要代理真实角色。
*
* 好处:
* // 代理对象可以做很多真实对象做不了的事情。
* // 真实对象专注做自己的事情。
*/
public class StaticProxy {
public static void main(String[] args) {
// 线程底层原理。(对比静态代理)。
// Thread ~ 代理角色。
// Runnable ~ 真实对象。重写要实现的方法。
new Thread(()-> System.out.println("我爱你")).start();
You you = new You();// 你要结婚,真实对象。
new WeddingCompany(you).happyMarry();
// WeddingCompany WeddingCompany = new WeddingCompany(you);
// WeddingCompany.happyMarry();
}
}
// 真实角色。你结婚。
class You implements Marry {
@Override
public void happyMarry() {
System.out.println("geek 要结婚了。。。");
}
}
// 代理角色。帮助你结婚。
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 after() {
System.out.println("结婚之后。");
}
private void before() {
System.out.println("结婚之前。");
}
}
Lambda 表达式。
- 避免匿名内部类定义过多。
- 让代码看起来更简洁。
- 去掉一堆没有意义的代码,只留下核心逻辑。
函数式接口 ~ Functional Interface。
任何接口,如果只包含一个抽象方法,ta 就是一个函数式接口。
public interface Runnable {
public abstract void run();
}
对于函数式接口,我们可以通过 Lambda 表达式来创建该接口的对象。
package com.geek.lambda;
// 定义一个函数式接口。
interface ILike {
void like();
}
public class TestLambda {
public static void main(String[] args) {
ILike like = new Like();
like.like();
}
}
// 实现类。
class Like implements ILike {
@Override
public void like() {
System.out.println("I like Lambda.");
}
}
package com.geek.lambda;
// 定义一个函数式接口。
interface ILike {
void like();
}
public class TestLambda {
public static void main(String[] args) {
// 函数式接口实现。(1)。
ILike like = new Like();
like.like();
// 简化 2 ~ 静态内部类。
ILike like2 = new Like2();
like2.like();
// 简化 3 ~ 局部内部类。
class Like3 implements ILike {
@Override
public void like() {
System.out.println("I like Lambda 3...");
}
}
ILike like3 = new Like3();
like3.like();
// 简化 4 ~ 匿名内部类。
ILike like4 = new ILike() {
@Override
public void like() {
System.out.println("I like Lambda 4...");
}
};
like4.like();
// 用 Lambda 简化。
ILike like1 = () -> System.out.println("I like Lambda 5...");
like1.like();
}
// 简化 2 ~ 静态内部类。
static class Like2 implements ILike {
@Override
public void like() {
System.out.println("I like Lambda 2...");
}
}
}
// 实现类。
class Like implements ILike {
@Override
public void like() {
System.out.println("I like Lambda.");
}
}
线程状态。
停止线程。
不推荐使用 jdk 提供的 stop();、destroy(); 方法。(已废弃)。
推荐线程自己停止下来。
建议使用一个标志位进行终止变量,当 flag = false,则终止线程运行。
package com.geek.state;
/**
* 测试 stop。
*/
public class TestStop implements Runnable {
// 设置一个标志位。
private boolean flag = true;
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("线程该停止了。");
}
}
}
public void stop() {
this.flag = false;
}
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("Thread " + i + " running..");
}
}
}
线程休眠。
sleep(ms) 指定当前线程阻塞的毫秒数。
sleep(); 存在异常 InterryptedException。
sleep(); 时间达到后线程进入就绪状态。
sleep(); 可以模拟网络延时、倒计时等。
每个对象都有一个锁,sleep(); 不会释放锁。
package com.geek.state;
import com.geek.TestThreadMulti;
/**
* 模拟网络延时。放大问题的发生性。
*/
public class TestSleep implements Runnable {
// 票数。
private int ticketNum = 10;
public static void main(String[] args) {
TestThreadMulti testThreadMulti = new TestThreadMulti();
new Thread(testThreadMulti, "小明").start();
new Thread(testThreadMulti, "老师").start();
new Thread(testThreadMulti, "黄牛").start();
}
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
break;
}
// 模拟延时。
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " 拿到了第 " + ticketNum-- + " 票。");
}
}
}
package com.geek.state;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSleep2 {
public static void main(String[] args) throws InterruptedException {
// tenDown();
currentTime();
}
// 打印当前系统时间。
public static void currentTime() {
Date date = new Date(System.currentTimeMillis());
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
}
}
// 模拟倒计时。
public static void tenDown() throws InterruptedException {
int number = 10;
while (true) {
Thread.sleep(1000);
System.out.println(number--);
if (number < 0) {
break;
}
}
}
}
线程礼让。
yield
v. 出产(作物);产生(收益、效益等);提供;屈服;让步;放弃;缴出
n. 产量;产出;利润
礼让线程,让当前正在执行的线程暂停,但不阻塞。
将线程从运行状态转为就绪状态。
让 CPU 重新调度,礼让不一定成功,看 CPU 心情。
package com.geek.state;
/**
* 测试线程礼让。
* 礼让不一定成功,看 CPU 心情。
*/
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() + " 线程停止执行。");
}
}
join。
-
Join 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
-
可以想象成插队。
package com.geek.state;
/**
* 测试 join。
*/
public class TestJoin implements Runnable {
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
// 主线程。
for (int i = 0; i < 500; i++) {
if (i == 200) {
thread.join();// 插队。
}
System.out.println("main" + i);
}
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip 线程来了 ~ " + i);
}
}
}
线程状态。
/**
* A thread state. A thread can be in one of the following states:
* <ul>
* <li>{@link #NEW}<hr>
<br>
* A thread that has not yet started is in this state.
* </li>
* <li>{@link #RUNNABLE}<hr>
<br>
* A thread executing in the Java virtual machine is in this state.
* </li>
* <li>{@link #BLOCKED}<hr>
<br>
* A thread that is blocked waiting for a monitor lock
* is in this state.
* </li>
* <li>{@link #WAITING}<hr>
<br>
* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
* </li>
* <li>{@link #TIMED_WAITING}<hr>
<br>
* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
* </li>
* <li>{@link #TERMINATED}<hr>
<br>
* A thread that has exited is in this state.
* </li>
* </ul>
*
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
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;
}
-
NEW。
尚未启动的线程。 -
RUNNABLE。
在 Java 虚拟机中执行的线程状态。 -
BLOCKED。
被 阻塞等待监视器锁定的线程状态。 -
WAITING。
正在等待另一个线程执行特定动作的线程状态。 -
TIMED_WAITING。
正在等待另一个线程执行动作打到指定等待时间的线程状态。 -
TERMINATED。
已退出的线程状态。
一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟状态。
package com.geek.state;
/**
* 线程状态。
*/
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("~ ~ ~ ~ ~ ~ ~");
});
// 观察状态。
Thread.State state = thread.getState();
System.out.println("state = " + state);
// 启动后。
thread.start();
state = thread.getState();
System.out.println("state = " + state);
System.out.println("~ ~ ~ ~ ~ ~ ~");
while (state!= Thread.State.TERMINATED) {// 只要线程不终止,就一直输出状态。
Thread.sleep(100);
state = thread.getState();
System.out.println("state = " + state);
}
thread.start();// Exception in thread "main" java.lang.IllegalThreadStateException
}
}
线程优先级。
Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围 1 ~ 10。
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
- 获取 / 改变优先级。
getPriority(); setPriority(int a);
package com.geek.state;
/**
* 测试线程优先级。
*/
public class TestPriority {
public static void main(String[] args) {
// 主线程 ~ 默认优先级。
System.out.println(Thread.currentThread().getName() + " ~ ~ ~ " + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
// 优先级。
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);// 10
t4.start();
// t5.setPriority(-1);
// t5.start();
//
// t6.setPriority(11);
// t6.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " ~ ~ ~ " + Thread.currentThread().getPriority());
}
}
守护进程 ~ daemon。
daemon
程序;守护线程;背景程序;守护进程;守护程序
线程分为用户线程
和守护线程
。
虚拟机必须确保用户线程执行完毕。
虚拟机不用等待守护线程执行完毕。
eg. 后台记录操作日志,监控内存、垃圾回收…
package com.geek.state;
/**
* 测试守护线程。
* 上帝守护你。
*/
public class TestDaemon {
/**
* You 线程结束,程序就结束。不管 God 线程是否结束。
*
* @param args
*/
public static void main(String[] args) {
God god = new God();
Thread thread = new Thread(god);
thread.setDaemon(true);// 默认是 false,表示是用户线程。
thread.start();
new Thread(new 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("你一生都开心的活着。");
}
System.out.println("~ ~ ~ ~ ~ ~ ~ goodbye, world.");
}
}
线程同步。
多个线程操作同一个资源。
并发:同一个对象被多个线程同时操作。
上万人同时抢 100 张票。
两个银行同时取钱。
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,eg. 食堂排队打饭,每个人都想吃饭,最天然的办法就是,排队,一个个来。
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这是我们就需要线程同步。线程同步其实就是一种等待机制,读个需要同时访问此对象的线程进入到这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题。
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题 。
- 如果一个优先级高的线程等待一个优先级低的线程锁释放,会导致优先级倒置,引起性能问题。
synchronize
v. (使)同步,在时间上一致,同速进行
package com.geek.syn;
/**
* 不安全的银行。
*/
public class UnsafeBank {
public static void main(String[] args) {
// 账户。
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
/**
* 账户。
*/
class Account {
int money;// 余额。
String name;// 卡名。
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread {
Account account;// 账户。
int drawingMoney;// 取了多少钱。
int nowMoney;// 手里的钱。
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// 取钱。
@Override
public void run() {
// super.run();
// 判断有没有钱。
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + " 钱不够,取不了。");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额 - 你取的钱。
account.money = account.money - drawingMoney;
// 手里的钱。
nowMoney += drawingMoney;
System.out.println(account.name + " 余额为:" + account.money);
// this.getName() = Thread.currentThread().getName()。
System.out.println(this.getName() + " 手里的钱:" + nowMoney);
}
}
线程不安全的集合。
package com.geek.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 线程不安全的集合。
*/
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());// 9999
}
}
解决 ~ 同步方法。
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是 synchonized 关键字。ta 包括两种用法。
synchonized 方法和 synchonized 块。
public synchonized void method(String[] args) {}
synchonized 方法控制对“对象”的访问,每个对象对应一把锁,每个 synchonized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法声明为 synchonized 将会影响效率。
同步块 ~ synchonized(Object object) {}。
synchonized(Object object) {}
obj 称之为同步监视器
。
obj 可以是任何对象,但是推荐使用共享资源作为同步监视器。
同步方法中无需 指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class(反射中讲解)。
同步监视器的执行过程。
- 第一个线程访问,锁定同步监视器,执行其中的代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问。
- 第一个线程访问完毕,解锁同步监视器。
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
package com.geek.syn;
/**
* 不安全买票。
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "苦逼的我").start();
new Thread(buyTicket, "牛逼的你们").start();
new Thread(buyTicket, "可恶的黄牛").start();
}
}
class BuyTicket implements Runnable {
// 外部停止方式。
boolean flag = true;
// 票。
private int ticketNumber = 10;
@Override
public void run() {
// 买票。
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// synchronized 同步方法
// private void buy() throws InterruptedException {
private synchronized void buy() throws InterruptedException {
// 判断是否有票。
if (ticketNumber <= 0) {
flag = false;
return;
}
// 模拟延时。
Thread.sleep(100);
// 买票。
System.out.println(Thread.currentThread().getName() + " 拿到 " + ticketNumber--);
}
}
package com.geek.syn;
/**
* 不安全的银行。
*/
public class UnsafeBank {
public static void main(String[] args) {
// 账户。
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
/**
* 账户。
*/
class Account {
int money;// 余额。
String name;// 卡名。
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread {
Account account;// 账户。
int drawingMoney;// 取了多少钱。
int nowMoney;// 手里的钱。
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// 这里 用 synchronized 锁 run(); 方法没用。
// synchronized 默认锁 this 对象。
// 我们需要锁的是 Account 对象。
// 取钱。
@Override
// public synchronized void run() {
public void run() {
// super.run();
// 锁的对象是变化的量,公共资源。
synchronized (Account.class) {
// 判断有没有钱。
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + " 钱不够,取不了。");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额 - 你取的钱。
account.money = account.money - drawingMoney;
// 手里的钱。
nowMoney += drawingMoney;
System.out.println(account.name + " 余额为:" + account.money);
// this.getName() = Thread.currentThread().getName()。
System.out.println(this.getName() + " 手里的钱:" + nowMoney);
}
}
}
package com.geek.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 线程不安全的集合。
*/
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (List.class) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());// 9996
}
}
juc。
package com.geek.juc;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());// 10000
}
}
死锁。
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对放释放资源,都停止执行的情形。某一个同步代码块同时拥有两个以上对象的锁
时,就可能发生“死锁”的问题。
package com.geek.deadLock;
/**
* 多个线程互相抱着对放需要的资源,形成僵持。
*/
public class TestDeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
// 口红。
class Lipstick {
}
// 镜子。
class Mirror {
}
class Makeup extends Thread {
// 需要的资源只有一份,用 static 来保证。
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;// 选择。
String girlName;// 使用化妆品的人。
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
// super.run();
// 化妆。
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 化妆。互相持有对方的锁,就是需要拿到对方的资源。
*/
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {// 获得口红的锁。
System.out.println(this.girlName + " 获得口红的锁。");
Thread.sleep(1000);
synchronized (mirror) {// 一秒后想获得镜子。
// (此时 Makeup g2 = new Makeup(1, "白雪公主");)
// mirror 是被锁住的。
System.out.println(this.girlName + " 获得镜子的锁。");
}
}
} else {
synchronized (mirror) {// 获得镜子的锁。
System.out.println(this.girlName + " 获得镜子的锁。");
Thread.sleep(2000);
synchronized (lipstick) {// 两秒后想获得口红。
System.out.println(this.girlName + " 获得口红的锁。");
}
}
}
}
}
结果。卡着不动。
灰姑娘 获得口红的锁。
白雪公主 获得镜子的锁。
↓ ↓ ↓
改进。不抱对方的锁。
package com.geek.deadLock;
/**
* 多个线程互相抱着对放需要的资源,形成僵持。
*/
public class TestDeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
// 口红。
class Lipstick {
}
// 镜子。
class Mirror {
}
class Makeup extends Thread {
// 需要的资源只有一份,用 static 来保证。
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;// 选择。
String girlName;// 使用化妆品的人。
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
// super.run();
// 化妆。
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 化妆。互相持有对方的锁,就是需要拿到对方的资源。
*/
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {// 获得口红的锁。
System.out.println(this.girlName + " 获得口红的锁。");
Thread.sleep(1000);
}
synchronized (mirror) {// 一秒后想获得镜子。
System.out.println(this.girlName + " 获得镜子的锁。");
}
} else {
synchronized (mirror) {// 获得镜子的锁。
System.out.println(this.girlName + " 获得镜子的锁。");
Thread.sleep(2000);
}
synchronized (lipstick) {// 两秒后想获得口红。
System.out.println(this.girlName + " 获得口红的锁。");
}
}
}
}
白雪公主 获得镜子的锁。
灰姑娘 获得口红的锁。
灰姑娘 获得镜子的锁。
白雪公主 获得口红的锁。
Process finished with exit code 0
产生死锁的四个必要条件。
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
我们只要想办法破其中任意一个或多个条件就可以避免死锁。
Lock 锁 ~ 可重入锁。
从 jdk 5.0 开始,Java 提供了更强大的线程同步机制 ~ 通过显式定义同步锁 对象来实现同步。同步锁使用 Lock 对象充当。
java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对关系资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
ReentrantLock 类实现了 Lock,ta 拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁,释放锁。
package com.geek.juc;
import java.util.concurrent.locks.Lock;
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 {
// Lock 锁。
private final Lock lock = new ReentrantLock();
int ticketNumber = 10;
@Override
public void run() {
while (true) {
try {
lock.lock();// 加锁。
if (ticketNumber > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNumber--);
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
synchronized 与 Lock 对比。
-
Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁)。
synchronized 是隐式锁,出了作用域自动释放。 -
Lock 只有代码块锁。
synchronized 有代码块和方法锁。 -
使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且有更好的扩展性(提供更多的子类)。
-
优先使用顺序。
Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)。
线程协作 ~ 生产者消费者问题。
- 应用场景。
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走。
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
Producer(生产者) --> 数据缓存区 --> Consumer(消费者)。
- 分析。
这是一个线程同步问题。生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
- 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
- 在生产者消费者问题中,仅有 synchronized 是不够的。
synchronized 可阻止并发更新同一个共享资源,实现了同步。
synchronized 不能用来实现不同线程之间的消息传递(通信)。
- Java 提供了 方法去解决线程之间的通信问题。
方法名 | 作用 |
---|---|
wait(); | 表示线程一直等待,直到其他线程通知。与 sleep(); 不同,会释放锁。 |
wait(long timeout); | 指定等待的毫秒数。 |
notify(); | 唤醒一个处于等待状态的线程。 |
notifyAll(); | 唤醒同一个对象上所有调用 wait(); 方法的线程,优先级别搞的线程优先调度。 |
均是 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常 IllegalMonitorStateException。
解决方式 1。
并发协作模型。“生产者 / 消费者模式” ~ 管程法。
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)。
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)。
- 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
Producer(生产者) --> 数据缓存区 --> Consumer(消费者)。
解决方式 2。
并发协作模型。“生产者 / 消费者模式” ~ 信号灯法。
package com.geek.juc;
/**
* 生产者消费者模型 ~ 标志位解决。 ~ 信号灯法。
*/
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 {
private TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("快乐大本营。");
} else {
this.tv.play("抖音记录美好生活。");
}
}
}
}
/**
* 消费者 ~ 观众。
*/
class Watcher extends Thread {
private TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
/**
* 产品 ~ 节目。
*/
class TV {
// 演员表演,观众等待。T
// 观众观看,演员等待。F
String voice;// 表演的节目。
boolean flag = true;
/**
* 表演。
*
* @param voice
*/
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;
}
}
线程池。
背景。
经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路。
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处。
- 提高响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)。
- 便于线程管理。
corePoolSize ~ 核心池的大小。
maximunPoolSize ~ 最大线程数。
keepAliveTime ~ 线程没有任务时最多保持多长时间后会终止。
使用。
JDK 5.0 起提供了线程池相关 API:ExecutorService 和 Executors。
ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor。
- void execute(Runnable command) ~ 执行任务 / 命令,没有返回值,一般用来执行 Runnable。
<T> Future<T> submit(Callable<T> task)
~ 执行任务,有返回值,一般用来执行 Callable。- void shutdown() ~ 关闭连接池。
Executors ~ 工具类、线程池的工具类,用于创建并返回不同类型的线程池。
package com.geek;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadNew {
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 = " + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* 继承 Thread 类。
*/
class MyThread1 extends Thread {
@Override
public void run() {
// super.run();
System.out.println("MyThread ~ 1");
}
}
/**
* 实现 Runnable 类。
*/
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread ~ 2");
}
}
/**
* 实现 Callable 接口。
*/
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("MyThread ~ 3");
return 100;
}
}