多线程(multithreading):指从软件或者硬件上实现多个线程并发执行的技术。
在 Java 中,共有三种方式可以使用多线程:
示例:
package com.wmwx.thread;
//方式一:继承 Thread 类,重写 run() 方法,调用 start() 方法开启线程
//总结:线程不一定立即执行,由 CPU 调度执行
public class TestThread1 extends Thread{
@Override
public void run(){
//run 方法线程体
for (int i=0;i<20;i++){
System.out.println("我在阅读源码第"+(i+1)+"段");
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用线程
testThread1.start();
//main 线程,即主线程
for (int i=0;i<20;i++){
System.out.println("我在看视频学习第"+(i+1)+"集");
}
}
}
输出结果:
我在看视频学习第1集
我在阅读源码第1段
我在看视频学习第2集
我在阅读源码第2段
我在看视频学习第3集
我在阅读源码第3段
我在看视频学习第4集
我在阅读源码第4段
我在看视频学习第5集
我在阅读源码第5段
我在看视频学习第6集
我在阅读源码第6段
我在看视频学习第7集
我在阅读源码第7段
我在看视频学习第8集
我在阅读源码第8段
我在看视频学习第9集
我在阅读源码第9段
我在看视频学习第10集
我在阅读源码第10段
我在看视频学习第11集
我在阅读源码第11段
我在看视频学习第12集
我在阅读源码第12段
我在看视频学习第13集
我在阅读源码第13段
我在看视频学习第14集
我在阅读源码第14段
我在看视频学习第15集
我在阅读源码第15段
我在看视频学习第16集
我在阅读源码第16段
我在看视频学习第17集
我在阅读源码第17段
我在看视频学习第18集
我在阅读源码第18段
我在看视频学习第19集
我在阅读源码第19段
我在看视频学习第20集
我在阅读源码第20段
package com.wmwx.thread;
//方式二:实现 Runnable 接口,重写 run() 方法,执行线程需丢入该实现类,调用 start() 方法
public class TestThread2 implements Runnable{
@Override
public void run(){
//run 方法线程体
for (int i=0;i<20;i++){
System.out.println("我在阅读源码第"+(i+1)+"段");
}
}
public static void main(String[] args) {
//创建一个接口实现类
TestThread2 testThread2 = new TestThread2();
//创建线程对象,通过线程对象来开启线程
Thread thread = new Thread(testThread2);
thread.start();
//main 线程,即主线程
for (int i=0;i<20;i++){
System.out.println("我在看视频学习第"+(i+1)+"集");
}
}
}
输出结果:
我在看视频学习第1集
我在阅读源码第1段
我在看视频学习第2集
我在阅读源码第2段
我在看视频学习第3集
我在阅读源码第3段
我在看视频学习第4集
我在阅读源码第4段
我在看视频学习第5集
我在阅读源码第5段
我在看视频学习第6集
我在阅读源码第6段
我在看视频学习第7集
我在阅读源码第7段
我在看视频学习第8集
我在阅读源码第8段
我在看视频学习第9集
我在阅读源码第9段
我在看视频学习第10集
我在阅读源码第10段
我在看视频学习第11集
我在阅读源码第11段
我在看视频学习第12集
我在阅读源码第12段
我在看视频学习第13集
我在阅读源码第13段
我在看视频学习第14集
我在阅读源码第14段
我在看视频学习第15集
我在阅读源码第15段
我在看视频学习第16集
我在阅读源码第16段
我在看视频学习第17集
我在阅读源码第17段
我在看视频学习第18集
我在阅读源码第18段
我在看视频学习第19集
我在阅读源码第19段
我在看视频学习第20集
我在阅读源码第20段
问题:多个线程操作同一个资源时,可能会导致线程不安全,数据发生紊乱。
示例:
package com.wmwx.thread;
public class Demo01 {
public static void main(String[] args) {
TestRunnable runnable = new TestRunnable();
new Thread(runnable, "天圣").start();
new Thread(runnable, "花泪").start();
new Thread(runnable, "雪仙").start();
}
}
class TestRunnable implements Runnable {
//票数
private int tickets = 10;
@Override
public void run() {
while (true){
if (tickets<=0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿了第"+tickets--+"张票");
}
}
}
输出结果:
花泪拿了第10张票
天圣拿了第9张票
雪仙拿了第8张票
天圣拿了第7张票
花泪拿了第7张票
雪仙拿了第6张票
花泪拿了第5张票
天圣拿了第4张票
雪仙拿了第3张票
花泪拿了第2张票
天圣拿了第2张票
雪仙拿了第1张票
天圣拿了第0张票
花泪拿了第-1张票
可以看到,输出中不仅出现了 -1 张票的情况,还出现了多个人拿走同一张票的情况,这就是多线程操作同一个对象时带来的并发问题。
使用步骤:
示例:
package com.wmwx.thread;
import java.util.concurrent.*;
public class TestCallable implements Callable<Integer> {
private Integer num = 0;
@Override
public Integer call() throws Exception {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"==>"+(i+1));
}
num++;
return num;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable();
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行
Future<Integer> r1 = service.submit(t1);
Future<Integer> r2 = service.submit(t1);
Future<Integer> r3 = service.submit(t1);
//获取结果
Integer b1 = r1.get();
Integer b2 = r2.get();
Integer b3 = r3.get();
System.out.println("Thread1 第"+b1+"个执行结束");
System.out.println("Thread2 第"+b2+"个执行结束");
System.out.println("Thread3 第"+b3+"个执行结束");
//关闭服务
service.shutdown();
}
}
输出结果:
pool-1-thread-1==>1
pool-1-thread-1==>2
pool-1-thread-1==>3
pool-1-thread-1==>4
pool-1-thread-1==>5
pool-1-thread-1==>6
pool-1-thread-1==>7
pool-1-thread-1==>8
pool-1-thread-1==>9
pool-1-thread-1==>10
pool-1-thread-1==>11
pool-1-thread-1==>12
pool-1-thread-1==>13
pool-1-thread-1==>14
pool-1-thread-1==>15
pool-1-thread-1==>16
pool-1-thread-1==>17
pool-1-thread-1==>18
pool-1-thread-1==>19
pool-1-thread-1==>20
pool-1-thread-3==>1
pool-1-thread-3==>2
pool-1-thread-3==>3
pool-1-thread-3==>4
pool-1-thread-3==>5
pool-1-thread-3==>6
pool-1-thread-3==>7
pool-1-thread-3==>8
pool-1-thread-3==>9
pool-1-thread-3==>10
pool-1-thread-3==>11
pool-1-thread-3==>12
pool-1-thread-3==>13
pool-1-thread-3==>14
pool-1-thread-3==>15
pool-1-thread-3==>16
pool-1-thread-3==>17
pool-1-thread-3==>18
pool-1-thread-3==>19
pool-1-thread-3==>20
pool-1-thread-2==>1
pool-1-thread-2==>2
pool-1-thread-2==>3
pool-1-thread-2==>4
pool-1-thread-2==>5
pool-1-thread-2==>6
pool-1-thread-2==>7
pool-1-thread-2==>8
pool-1-thread-2==>9
pool-1-thread-2==>10
pool-1-thread-2==>11
pool-1-thread-2==>12
pool-1-thread-2==>13
pool-1-thread-2==>14
pool-1-thread-2==>15
pool-1-thread-2==>16
pool-1-thread-2==>17
pool-1-thread-2==>18
pool-1-thread-2==>19
pool-1-thread-2==>20
Thread1 第1个执行结束
Thread2 第3个执行结束
Thread3 第2个执行结束
Callable 的优点:
lambda 表达式是 Java 1.8 中的新特性。使用 lambda 表达式有以下好处:
定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
示例:
public interface Runnable { public abstract void run();}
对于函数式接口,我们可以通过 lambda 表达式来创建该接口的对象。
示例:
new Thread(()->System.out.println("这里是 lambda 表达式"));
定义一个函数式接口 示例:
package com.wmwx.thread; //1.定义一个函数式接口 interface ILike { void lambda(); } /* 推导 lambda 表达式 */ public class TestLambda1 { public static void main(String[] args) { } }
编写实现类 示例:
package com.wmwx.thread; //1.定义一个函数式接口 interface ILike { void lambda(); } //2.编写实现类 class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda!"); } } /* 推导 lambda 表达式 */ public class TestLambda1 { public static void main(String[] args) { //接口类型 new 实现类 ILike like = new Like(); like.lambda(); } }
输出结果:
I like lambda!
将实现类改为静态内部类
package com.wmwx.thread; //1.定义一个函数式接口 interface ILike { void lambda(); } //2.编写实现类 class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda!"); } } /* 推导 lambda 表达式 */ public class TestLambda1 { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2!"); } } public static void main(String[] args) { //接口类型 new 实现类 ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); } }
输出结果:
I like lambda! I like lambda2!
将静态内部类改为局部内部类 示例:
package com.wmwx.thread; //1.定义一个函数式接口 interface ILike { void lambda(); } //2.编写实现类 class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda!"); } } /* 推导 lambda 表达式 */ public class TestLambda1 { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2!"); } } public static void main(String[] args) { //接口类型 new 实现类 ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); //4.局部内部类 class Like3 implements ILike { @Override public void lambda() { System.out.println("I like lambda3!"); } } like = new Like3(); like.lambda(); } }
输出结果:
I like lambda! I like lambda2! I like lambda3!
将局部内部类改为匿名内部类 示例:
package com.wmwx.thread; //1.定义一个函数式接口 interface ILike { void lambda(); } //2.编写实现类 class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda!"); } } /* 推导 lambda 表达式 */ public class TestLambda1 { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2!"); } } public static void main(String[] args) { //接口类型 new 实现类 ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); //4.局部内部类 class Like3 implements ILike { @Override public void lambda() { System.out.println("I like lambda3!"); } } like = new Like3(); like.lambda(); //5.匿名内部类(没有类的名称, 必须借助接口或父类) like = new ILike() { @Override public void lambda() { System.out.println("I like lambda4!"); } }; like.lambda(); } }
输出结果:
I like lambda! I like lambda2! I like lambda3! I like lambda4!
用 Lambda 简化 示例:
package com.wmwx.thread; //1.定义一个函数式接口 interface ILike { void lambda(); } //2.编写实现类 class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda!"); } } /* 推导 lambda 表达式 */ public class TestLambda1 { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2!"); } } public static void main(String[] args) { //接口类型 new 实现类 ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); //4.局部内部类 class Like3 implements ILike { @Override public void lambda() { System.out.println("I like lambda3!"); } } like = new Like3(); like.lambda(); //5.匿名内部类(没有类的名称, 必须借助接口或父类) like = new ILike() { @Override public void lambda() { System.out.println("I like lambda4!"); } }; like.lambda(); //6.使用 lambda 简化 like = () -> { System.out.println("I like lambda5!"); }; like.lambda(); } }
输出结果:
I like lambda! I like lambda2! I like lambda3! I like lambda4! I like lambda5!
需要传参数时,也可以用同样的方式: 示例:
package com.wmwx.thread; //1.定义函数式接口 interface ILove { void love(String A, String B); } //2.编写实现类 class Love1 implements ILove { @Override public void love(String A, String B) { System.out.println(A + "与" + B + "相爱已经一年了!"); } } /* 带参数的lambda表达式 */ public class TestLambda2 { //3.编写静态内部类 static class Love2 implements ILove { @Override public void love(String A, String B) { System.out.println(A + "与" + B + "相爱已经两年了!"); } } public static void main(String[] args) { ILove love = new Love1(); love.love("花泪", "天圣"); love = new Love2(); love.love("花泪", "天圣"); //4.局部内部类 class Love3 implements ILove { @Override public void love(String A, String B) { System.out.println(A + "与" + B + "相爱已经三年了!"); } } love = new Love3(); love.love("花泪", "天圣"); //5.匿名内部类 love = new ILove() { @Override public void love(String A, String B) { System.out.println(A + "与" + B + "相爱已经四年了!"); } }; love.love("花泪", "天圣"); //6.lambda 表达式 love = (String A, String B) -> { System.out.println(A + "与" + B + "相爱已经五年了!"); }; love.love("花泪", "天圣"); } }
输出结果:
花泪与天圣相爱已经一年了! 花泪与天圣相爱已经两年了! 花泪与天圣相爱已经三年了! 花泪与天圣相爱已经四年了! 花泪与天圣相爱已经五年了!
在 2.2 中分别演示了 lambda 表达式无参和有参的推导过程。事实上,这样的表达式依旧可以继续简化下去。以有参表达式为例:
//lambda 表达式
love = (String A, String B) -> {
System.out.println(A + "与" + B + "相爱已经五年了!");
};
love.love("花泪", "天圣");
可以去除所有的参数类型。 示例:
//lambda 表达式 love = (String A, String B) -> { System.out.println(A + "与" + B + "相爱已经五年了!"); }; love.love("花泪", "天圣"); //lambda 表达式简化1:去除所有的参数类型 love = (A, B) -> { System.out.println(A + "与" + B + "相爱已经五年了!"); }; love.love("花泪", "天圣");
输出结果:
花泪与天圣相爱已经五年了! 花泪与天圣相爱已经五年了!
当接口中抽象方法的方法体内只有一条语句时,可以去除大括号。 示例:
//lambda 表达式 love = (String A, String B) -> { System.out.println(A + "与" + B + "相爱已经五年了!"); }; love.love("花泪", "天圣"); //lambda 表达式简化1:去除参数类型 love = (A, B) -> { System.out.println(A + "与" + B + "相爱已经五年了!"); }; love.love("花泪", "天圣"); //lambda 表达式简化2:去除大括号 love = (A, B) -> System.out.println(A + "与" + B + "相爱已经五年了!"); love.love("花泪", "天圣");
输出结果:
花泪与天圣相爱已经五年了! 花泪与天圣相爱已经五年了! 花泪与天圣相爱已经五年了!
当接口中的抽象方法有且只有一个参数时,可以去除小括号。 示例:
//定义函数式接口 interface ILove { void love(String name); } //...中间代码... //lambda 表达式简化3:去除小括号 love = name -> System.out.println(name + "发觉自己爱上了那个人。"); love.love("诗人");
输出结果:
诗人发觉自己爱上了那个人。
方法 | 说明 |
---|---|
setPriority (int newPriority) | 更改线程的优先级 |
static void sleep (long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join () | 等待该线程中止 |
static void yield () | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt () | 中断线程(不推荐使用) |
boolean isAlive() | 测试线程是否处于活动状态 |
示例:
实现 Runnable 接口
package com.wmwx.thread; //测试线程终止 public class TestStop implements Runnable{ @Override public void run() { } public static void main(String[] args) { } }
设置标志位,并重写 run() 方法
package com.wmwx.thread; //测试线程终止 public class TestStop implements Runnable{ //设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while (flag) { System.out.println("runnable:"+i++); } } public static void main(String[] args) { } }
编写转换标志位的方法
package com.wmwx.thread; //测试线程终止 public class TestStop implements Runnable{ //1.设置一个标志位 private boolean flag = true; //2.设置一个方法转换标志位 public void stopRunnable(){ this.flag = false; System.out.println("线程停止了。"); } @Override public void run() { int i = 0; while (flag) { System.out.println("runnable:"+i++); } } public static void main(String[] args) { } }
编写 main() 方法,并在其中创建新线程
package com.wmwx.thread; //测试线程终止 public class TestStop implements Runnable{ //1.设置一个标志位 private boolean flag = true; //2.设置一个方法转换标志位 public void stopRunnable(){ this.flag = false; System.out.println("线程停止了。"); } @Override public void run() { int i = 0; while (flag) { System.out.println("runnable:"+i++); } } 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){ //3.调用stopRunnable()切换标志位,停止线程 testStop.stopRunnable(); } } } }
输出结果:
//...省略上文... main线程:899 main线程:900 runnable:1844 线程停止了。 main线程:901 main线程:902 //...省略下文...
以 1.4 中的代码为例,便是模拟网络延时的用法。
示例:
package com.wmwx.thread;
//模拟网络延时
public class TestSleep implements Runnable{
//票数
private int tickets = 10;
@Override
public void run() {
while (true){
if (tickets<=0) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿了第"+tickets--+"张票");
}
}
public static void main(String[] args) {
TestSleep testSleep = new TestSleep();
new Thread(testSleep, "天圣").start();
new Thread(testSleep, "花泪").start();
new Thread(testSleep, "雪仙").start();
}
}
并且,sleep() 还可以用来模拟倒计时。
示例:
package com.wmwx.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟倒计时
public class TestSleep2{
public static void countDown(int second) throws InterruptedException {
int num = second;
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();
}
}
}
}
输出结果:
15:32:05
15:32:06
15:32:07
15:32:08
15:32:09
礼让:令当前正在执行的线程暂停,但不进入阻塞态
示例:
package com.wmwx.thread;
//测试礼让线程
//礼让不一定成功
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()+"==>线程结束执行。");
}
}
输出结果:
A==>线程开始执行。
A==>线程结束执行。
B==>线程开始执行。
B==>线程结束执行。
此时,线程 A 执行结束后线程 B 才开始执行,这意味着线程 A 礼让失败。
不改变任何代码,重新启动测试,可以看到输出结果:
A==>线程开始执行。
B==>线程开始执行。
A==>线程结束执行。
B==>线程结束执行。
这次线程 A 开始执行后,线程 B 便开始执行,由此可见礼让成功。
通过 join() 方法可以强制令其他线程先阻塞,等到该线程执行完毕后,再去执行其他线程。
示例:
package com.wmwx.thread;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("我是VIP"+i+"号,都给我让道!");
}
}
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);
}
}
}
输出信息:
main:0
我是VIP0号,都给我让道!
main:1
我是VIP1号,都给我让道!
main:2
我是VIP2号,都给我让道!
main:3
我是VIP3号,都给我让道!
... ...
main:199
我是VIP173号,都给我让道!
我是VIP174号,都给我让道!
我是VIP175号,都给我让道!
我是VIP176号,都给我让道!
... ...
我是VIP999号,都给我让道!
main:200
main:201
main:202
... ...
main:497
main:498
main:499
通过 Thread.State 可以获取 Java 中线程对应的六种状态常量,分别为:
一个线程可以在给定时间点处于一个状态。这些状态是不反应任何操作系统线程状态的虚拟机状态。
示例:
package com.wmwx.thread;
//观测线程的状态
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);
//观测线程启动后的状态
thread.start();
state = thread.getState();
System.out.println(state);
//只要线程不终止,就一直输出状态
while (state != Thread.State.TERMINATED) {
Thread.sleep(500);
state = thread.getState(); //切记:更新线程状态!
System.out.println(state);
}
//报错:java.lang.IllegalThreadStateException 原因:死亡之后的线程不能再次启动!
thread.start();
}
}
输出结果:
NEW
RUNNABLE
TIMED_WAITING
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
==============
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at com.wmwx.thread.TestState.main(TestState.java:31)
示例:
package com.wmwx.thread;
//测试线程优先级
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, "t1");
Thread t2 = new Thread(myPriority, "t2");
Thread t3 = new Thread(myPriority, "t3");
Thread t4 = new Thread(myPriority, "t4");
Thread t5 = new Thread(myPriority, "t5");
t1.start();
t2.setPriority(2);
t2.start();
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
t4.setPriority(Thread.MIN_PRIORITY);
t4.start();
t5.setPriority(Thread.NORM_PRIORITY);
t5.start();
}
}
class MyPriority implements Runnable {
//测试优先级
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==>"+Thread.currentThread().getPriority());
}
}
输出结果:
main==>5
t2==>2
t3==>10
t1==>5
t5==>5
t4==>1
可见,设置优先级不能直接决定线程的执行顺序,只能提高权重。
使用 setDaemon(true) 方法可以设置线程为守护线程。守护线程如:后台记录操作日志、监控内存、垃圾回收等等。
示例:
package com.wmwx.thread;
//测试守护线程
public class TestDaemon {
public static void main(String[] args) {
Tao tao = new Tao();
Thread t1 = new Thread(tao);
t1.setDaemon(true); //默认是 false
t1.start();
new Thread(new Human()).start();
}
}
//道
class Tao implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("道生一,一生二,二生三,三生万物。");
}
}
}
//人类
class Human implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这个人活了"+(i+1)+"岁。");
}
}
}
输出结果:
这个人活了1岁。
道生一,一生二,二生三,三生万物。
这个人活了2岁。
... ...
这个人活了99岁。
道生一,一生二,二生三,三生万物。
这个人活了100岁。
可见,当 Human 的线程结束时,原本死循环的 Tao 线程也结束了。
在处理多线程问题时,时常会遇到多个线程想要访问同一个对象,并且其中某些线程还想要修改这个对象的情况,此时我们就需要线程同步。
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,然后为正在使用的线程加锁。当一个线程获得对象的排它锁,独占资源时,其他线程必须等待。资源使用完毕后,再将缩释放掉即可。如此,等到前一个线程使用完毕后,下一个线程才会去使用,就可以保证线程的安全性。
但是,使用锁机制可能会导致以下问题:
还是以 1.4 中的代码为例:
package com.wmwx.thread;
//不安全的买票
public class UnsafeBuyTickets {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
Thread t1 = new Thread(buyTickets, "天圣");
Thread t2 = new Thread(buyTickets, "花泪");
Thread t3 = new Thread(buyTickets, "雪仙");
t1.start();
t2.start();
t3.start();
}
}
class BuyTickets implements Runnable {
//票的数量
private int ticketNum = 10;
//外部停止方式的标志位
private boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
private void buy() throws InterruptedException {
if (ticketNum>0) {
Thread.sleep(200); //模拟延时
System.out.println(Thread.currentThread().getName()+"拿走了第"+ticketNum--+"张票。");
} else {
flag = false;
}
}
}
输出结果:
花泪拿走了第10张票。
天圣拿走了第9张票。
雪仙拿走了第10张票。
天圣拿走了第8张票。
雪仙拿走了第7张票。
花泪拿走了第8张票。
花泪拿走了第6张票。
天圣拿走了第6张票。
雪仙拿走了第6张票。
天圣拿走了第5张票。
花泪拿走了第4张票。
雪仙拿走了第3张票。
天圣拿走了第2张票。
花泪拿走了第1张票。
雪仙拿走了第0张票。
同一张票被多个人取走,这显然不是我们想看到的。
下面再举一个例子:
package com.wmwx.thread;
//不安全的取钱
public class UnsafeAccount {
public static void main(String[] args) {
//账户
Account account = new Account(100, "银行卡");
new Drawing(account, 50, "天圣").start();
new Drawing(account, 100, "舞泪").start();
}
}
//账户
class Account {
private int money;
private String name;
public Account() {
}
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread {
private Account account; //账户
private int drawingMoney; //取钱的数额
private int nowMoney; //现在手里钱的数额
public Drawing(){
}
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run(){
//判断有没有钱
if (account.getMoney()<drawingMoney) {
System.out.println(Thread.currentThread().getName()+"账户余额不足。");
return;
}
account.setMoney(account.getMoney() - drawingMoney);
nowMoney = nowMoney + drawingMoney;
System.out.println(account.getName()+"账户的余额为:"+account.getMoney());
System.out.println(this.getName()+"手里还剩下"+nowMoney);
}
}
输出结果:
银行卡账户的余额为:50
天圣手里还剩下50
花泪账户余额不足。
可以看到,一个人取出了 50 元后,账户里只剩 50 元,另一个人就没有办法再取出 100 元了,这是正常的。
但是,如果在取钱的代码中加入 sleep() 方法模拟网络延迟,输出结果又会变成怎样呢?
//取钱
@Override
public void run(){
//判断有没有钱
if (account.getMoney()<drawingMoney) {
System.out.println(Thread.currentThread().getName()+"账户余额不足。");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.getMoney() - drawingMoney);
nowMoney = nowMoney + drawingMoney;
System.out.println(account.getName()+"账户的余额为:"+account.getMoney());
System.out.println(this.getName()+"手里还剩下"+nowMoney);
}
输出结果:
银行卡账户的余额为:50
银行卡账户的余额为:50
天圣手里还剩下50
花泪手里还剩下100
可以看到,两个人都取出了钱,加起来足有 150 元,超出了原本 100 元的储备。而在实际业务中,网络延迟是必须要考虑的问题。因此,通过使用 sleep() 方法来模拟网络延迟,可以放大问题发生的可能性。
接下来,再来看一看集合线程不安全的例子:
package com.wmwx.thread;
import java.util.ArrayList;
//线程不安全的集合
public class UnsafeList {
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(3000);
System.out.println(list.size());
}
}
输出结果:
9998
按照逻辑,此处本应输出 10000,最后输出的却只有 9998,意味着有两项覆盖了数组前面的内容。
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制。这套机制就是 synchronized 关键字,它包括两种用法︰synchronized 方法和 synchronized 块。
**synchronized 方法控制对对象的访问。**每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。而方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,并继续执行。
以 4.1 中的买票为例,为 buy() 方法添加 synchronized 关键字:
package com.wmwx.thread;
//安全的买票
public class UnsafeBuyTickets {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
Thread t1 = new Thread(buyTickets, "天圣");
Thread t2 = new Thread(buyTickets, "花泪");
Thread t3 = new Thread(buyTickets, "雪仙");
t1.start();
t2.start();
t3.start();
}
}
class BuyTickets implements Runnable {
//票的数量
private int ticketNum = 10;
//外部停止方式的标志位
private boolean flag = true;
@Override
public void run() {
while (flag) {
try {
Thread.sleep(200); //模拟延时
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//安全的买票方法
private synchronized void buy() throws InterruptedException {
if (ticketNum>0) {
System.out.println(Thread.currentThread().getName()+"拿走了第"+ticketNum--+"张票。");
} else {
flag = false;
}
}
}
输出结果:
花泪拿走了第10张票。
天圣拿走了第9张票。
雪仙拿走了第8张票。
天圣拿走了第7张票。
花泪拿走了第6张票。
雪仙拿走了第5张票。
天圣拿走了第4张票。
花泪拿走了第3张票。
雪仙拿走了第2张票。
天圣拿走了第1张票。
可以看到,每一张票都只被一个人取走,并且没有人取到第负数张票。
//同步块
synchronized(Obj) {
//想要同步执行的代码
}
同步监视器的执行过程:
以 4.1 中的银行卡为例,将 run() 方法中的代码放入同步块中,并将 account 属性作为同步监视器:
package com.wmwx.thread;
//安全的取钱
public class UnsafeAccount {
public static void main(String[] args) {
//账户
Account account = new Account(100, "银行卡");
new Drawing(account, 50, "天圣").start();
new Drawing(account, 100, "花泪").start();
}
}
//账户
class Account {
private int money;
private String name;
public Account() {
}
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread {
private Account account; //账户
private int drawingMoney; //取钱的数额
private int nowMoney; //现在手里钱的数额
public Drawing(){
}
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//线程安全的取钱方法
@Override
public void run(){
synchronized (account){
//判断有没有钱
if (account.getMoney()<drawingMoney) {
System.out.println(Thread.currentThread().getName()+"账户余额不足。");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.getMoney() - drawingMoney);
nowMoney = nowMoney + drawingMoney;
System.out.println(account.getName()+"账户的余额为:"+account.getMoney());
System.out.println(this.getName()+"手里还剩下"+nowMoney);
}
}
}
输出结果:
银行卡账户的余额为:0
花泪手里还剩下100
天圣账户余额不足。
可见,当一个人取走了钱导致余额不足之后,另一个人就不能再取钱了。
同理可以修改 4.1 中集合的例子:
package com.wmwx.thread;
import java.util.ArrayList;
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
//通过 synchronized 实现线程安全
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
可以看到正确的输出结果:
10000
事实上,Java 本身提供了一种线程安全的集合 CopyOnWriteArrayList,它位于 JUC(java.util.concurrent)包下。
示例:
package com.wmwx.thread;
import java.util.concurrent.CopyOnWriteArrayList;
//线程安全的集合
public class SafeList {
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());
}
}
注意代码中不再使用 synchronized 方法或 synchronized 代码块,但输出结果依然正常:
10000
多个线程各自占有一些共享资源,并互相等待其他线程占有的资源,导致两个或者多个线程都在等待对方释放资源,从而全都停止执行的情形,就叫做死锁。
某一个同步块同时拥有两个以上对象的锁时,就可能会发生死锁的问题。
产生死锁的四个必要条件:
示例:
package com.wmwx.thread;
//死锁:多个线程互相占有者对方需要的资源,就会形成死锁
public class DeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup(0, "花泪");
Makeup m2 = new Makeup(1, "雪仙");
m1.start();
m2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
//化妆
class Makeup extends Thread {
//用static保证资源只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String name; //人名
public Makeup(){
}
public Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}
//化妆
private void makeUp() throws InterruptedException {
if (choice==0) {
synchronized (lipstick) {
System.out.println(name+"获得了口红的锁。");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(name+"获得了镜子的锁。");
}
}
} else {
synchronized (mirror) {
System.out.println(name+"获得了镜子的锁。");
Thread.sleep(1000);
synchronized (lipstick) {
System.out.println(name+"获得了口红的锁。");
}
}
}
}
@Override
public void run(){
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
花泪获得了口红的锁。雪仙获得了镜子的锁。
注意,此时程序仍在运行,并没有停止。两个人一个占有了口红资源,一个占有了镜子资源,并且都想要对方的资源,僵持不下,因而陷入了死锁。
从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显式定义 Lock 对象充当同步锁来实现同步。
java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁。线程开始访问共享资源之前应先获得 Lock 对象。
ReentrantLock(可重入锁) 类实现了 Lock 接口,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中比较常用。它可以显式地加锁、显式地释放锁。
根据官方的建议,最好将 lock() 方法放在 try 代码块前,而将 unlock() 方法放在 finally 代码块中。
示例:
package com.wmwx.thread;
import java.util.concurrent.locks.ReentrantLock;
//测试Lock锁
public class TestLock implements Runnable{
int ticketNum = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
TestLock testLock = new TestLock();
new Thread(testLock, "天圣").start();
new Thread(testLock, "花泪").start();
new Thread(testLock, "雪仙").start();
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
if (ticketNum>0){
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"张票。");
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
输出结果:
天圣拿到了第10张票。
花泪拿到了第9张票。
雪仙拿到了第8张票。
花泪拿到了第7张票。
天圣拿到了第6张票。
雪仙拿到了第5张票。
天圣拿到了第4张票。
花泪拿到了第3张票。
雪仙拿到了第2张票。
天圣拿到了第1张票。
分析可知,这是一个线程同步问题。生产省和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
Java 提供了以下几个方法来解决线程间的通信问题:
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知;与 sleep() 不同,wait() 会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用 wait() 方法的线程,优先级别高的线程优先调度 |
示例:
产品类 Product:
//产品
class Product {
//编号
int num;
public Product(int num) {
this.num = num;
}
}
缓冲区 SynContainer:
//缓冲区
class SynContainer {
//容器
Product[] products = new Product[3];
//容器计数器
int productNum = 0;
//生产者丢入产品
public synchronized void push(Product product) {
//如果容器满了,就要等待消费者消费
while (productNum == products.length){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果还没满,就要丢入产品
products[productNum++] = product;
//通知消费者消费
this.notifyAll();
}
//消费者取出产品
public synchronized Product pop() {
//如果容器空了,就要等待生产者生产
while (productNum==0) {
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果还没空,就要取出产品
Product product = products[--productNum];
//通知生产者生产
this.notifyAll();
//返回取出的产品
return product;
}
}
生产者 Producer:
//生产者
class Producer extends Thread {
//缓冲区
SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
//生产
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("生产第"+(i+1)+"只鸡。");
container.push(new Product(i+1));
}
}
}
消费者 Consumer:
//消费者
class Consumer extends Thread {
//缓冲区
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run(){
for (int i = 0; i < 10; i++) {
Product product = container.pop();
System.out.println("消费第"+product.num+"只鸡。");
}
}
}
main 方法:
//测试生产者消费者问题(利用缓冲区解决:管程法)
//需要:生产者、消费者、产品、缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Thread(new Producer(container)).start();
new Thread(new Consumer(container)).start();
}
}
输出结果:
生产第1只鸡。
生产第2只鸡。
生产第3只鸡。
生产第4只鸡。
生产第5只鸡。
消费第2只鸡。
消费第3只鸡。
生产第6只鸡。
消费第4只鸡。
生产第7只鸡。
消费第5只鸡。
生产第8只鸡。
消费第6只鸡。
生产第9只鸡。
消费第7只鸡。
生产第10只鸡。
消费第8只鸡。
消费第9只鸡。
消费第1只鸡。
消费第0只鸡。
以演员录制节目和观众观看节目为例:
节目类 TV:
//节目
class TV {
String program; //节目名
boolean flag = true; //标志位
//录制
public synchronized void record(String program) {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员录制了:"+program);
this.program = program;
//通知观众观看
this.notifyAll();
this.flag = !flag;
}
//播放
public synchronized void play() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了:"+program);
//通知演员表演
this.notifyAll();
this.flag = !flag;
}
}
演员类 Player:
//演员
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
if ((i+1)%5==0){
tv.record("广告");
} else {
tv.record("《降温》");
}
}
}
}
观众类 Watcher:
//观众
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
tv.play();
}
}
}
main 方法:
//测试生产者消费者问题(利用标志位解决:信号灯法)
//需要:生产者(演员)、消费者(观众)、产品(录制的节目)
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Thread(new Player(tv)).start();
new Thread(new Watcher(tv)).start();
}
}
输出结果:
演员录制了:《降温》
观众观看了:《降温》
演员录制了:《降温》
观众观看了:《降温》
演员录制了:《降温》
观众观看了:《降温》
演员录制了:《降温》
观众观看了:《降温》
演员录制了:广告
观众观看了:广告
演员录制了:《降温》
观众观看了:《降温》
演员录制了:《降温》
观众观看了:《降温》
演员录制了:《降温》
观众观看了:《降温》
演员录制了:《降温》
观众观看了:《降温》
演员录制了:广告
观众观看了:广告
void execute(Runnable command)
∶执行任务/命令,没有返回值,一般用来执行 Runnable。<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般用来执行 Callable。void shutdown()
:关闭连接池。示例:
package com.wmwx.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池,参数为线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
//2.执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//3.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
输出结果:
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-3:0
pool-1-thread-4:0
pool-1-thread-2:0
pool-1-thread-4:1
pool-1-thread-3:1
pool-1-thread-1:2
pool-1-thread-3:2
pool-1-thread-4:2
pool-1-thread-2:1
pool-1-thread-2:2
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。