Java多线程编程详解——快速上手附代码
Java多线程编程方面涉及到的知识,结合B站狂神说视频总结笔记,附代码。
文章目录
一、创建多线程的方法
三种:
- 继承Thread类
- 实现Runnable接口,将重写run方法,将Runnable实现类作为Thread类的Target创建线程对象,再调用该Thread类的start()方法启动该线程。(这里是用到了静态代理模式,两个类都实现同一个接口,将实际类当作代理类的Target传入构造函数参数中,接着由代理类创建对象代理实际类完成操作)
- 实现Callable接口。可以定义返回类型,获取线程执行结果。
多线程创建方法代码示例:
import java.util.concurrent.*;
// 1. 继承Thread类
public class T1 extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "这是继承Thread类方法");
}
public static void main(String[] args) {
T1 thread1 = new T1();
// 继承Thread类启动线程
thread1.run();
TestRunnable tr = new TestRunnable();
TestCallable tc = new TestCallable();
// 启动Runnable线程
new Thread(tr,"Runnable实现线程").start();
// // Callable启动线程方式一
// ExecutorService service = Executors.newFixedThreadPool(3);
// Future<String> result = service.submit(tc);
//
// try {
// String out = result.get();
// System.out.println(out);
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
//
// service.shutdown();
// Callable启动线程方式二
FutureTask<String> futureTask = new FutureTask<String>(tc);
new Thread(futureTask).start();
try {
String out = futureTask.get();
System.out.println(out);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// 2. 实现Runnable接口方式
class TestRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
// 3. 实现Callable接口方式
class TestCallable implements Callable{
@Override
public String call() {
System.out.println("输出一个人");
return "小纶纶";
}
}
二、静态代理
即如上面Runnable实现创建线程即是使用了静态代理模式。
三、Lambda表达式
作用:简化代码(jdk8新特性)
注意:函数式接口是Lambda表达式的关键所在。
public class LamdaThread {
//内部静态类实现方式
static class Like implements ILike{
@Override
public void like() {
System.out.println("I Like You!");
}
}
public static void main(String[] args) {
Like like1 = new Like();
like1.like();
//函数式接口lambda实现方式
ILike like = () -> {
System.out.println("This is a lambda!");
};
like.like();
}
}
interface ILike{
void like();
}
总结:
- lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就该用代码块包裹;
- 前提是接口为函数式接口;
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。
四、线程状态
五、 常用线程方法
1. 线程停止
不推荐使用自带的线程内部的停止方法。如:
在Thread类destroy()方法上面加有@Deprecated注解,左边方法也被划了横线,表示这个方法已经被废弃,使用可能会有bug或弊端。
优质解决方法:可以用一个标志位,在线程体内部加入标志位,更改标志位已终止线程执行。
2. 线程休眠——sleep()
// 模拟演示
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
3. 线程礼让——yield()
方法:Thread.yield()
4. 线程强制执行——join()
5. 线程状态观测——Thread.State
有了线程的状态,我们就能在线程执行的过程中根据线程的状态进行标志,当线程处于某种状态时我们就执行某种操作。比如我们在监测线程状态时发现一个线程在很长一段时间内都是TIME_WAITING状态的话就执行break。线程只能启动一次,一旦进入死亡模式的话就不能再次启动了。
六、线程优先级
线程优先级默认是5,最高为10,最低为1。使用Thread.getPriority()获取线程的优先级信息,使用Thread.setPriority()设置线程优先级。
注:优先级高并不代表一定是其先执行,只是说在权重分配上其权重更高,实际运行情况还是看CPU执行结果。一般来说权重高的线程优先执行的概率高。
思考:性能倒置问题(指优先级低的线程被执行了,优先级高的反而在等待)
Why?正常来说,无论怎么样,高优先级的任务一来,低优先级任务就会让位,所以似乎必定高优先级任务能拿到CPU,那为啥还会有优先级倒置?
让我们考虑这么个例子,优先级 T1>T2>T3
T3先执行,他执行的时候正好拿到了资源A,意味着别的任务不能访问资源A,而会被阻塞;
这时 T2执行,由于优先级更高,他很轻松的抢占了CPU 然后他成功的访问到了资源B,成为唯一能够访问资源B,拿到锁的幸运儿。有趣的是,资源B也是T3所需要的,但是这不影响,因为按理来说,T2会执行,直到释放B的锁,以及让出CPU,这样T3可以继续执行;
这时出现了T1,他毫不意外的从T2抢占了CPU,但是却没办法执行,因为缺乏资源A!所以理论上他需要T3执行完才能继续执行。。。
你说接下来发生什么?自然是T1被阻塞,然后让出CPU,实际上最先执行的是T2,等T2执行完,T3才能获得CPU(此时因为T1被阻塞了,没办法抢占CPU),继续执行,等到T3执行完,释放T1所需的资源A,最后才轮到T1.
解决方案:
目前据我所知有两种解决方案
1. 优先级继承
我们发现问题出在,一个矛盾,高优先级的任务因为低优先级任务持有的资源(当然不是CPU资源),而阻塞,既然我们没法轻易释放锁,解锁,因为锁需要CPU运行才能释放,那我们干脆直接让,那个获得资源的任务,得到相同的优先级,或者说继承那个高优先级,这样原先的低优先级任务,可以先运行,赶紧用完赶紧走,别打扰我T1干事。
换个角度来看,这就是临时提升CPU的抢占优先级,临时“升官”,为的就是赶紧执行让出资源。
资源让出来以后,再恢复优先级,该干啥干啥:)
这样 一开始你会觉得 这不还是让T3先于T1了嘛?但是你别忘了,我们这层优化可以使得T1不用在T2之后执行
So?不就节省了一个任务嘛?那如果我改变一下条件,假设有100个任务 T1 ~ T100,T100最低,持有T1的资源,你觉得会发生什么事?
很明显T1会被迫等待T2~T99,所有的任务都执行完才能执行!!!!
这就非常显著的性能差异了
2. 优先级天花板
除了优先级继承模式,还有个模式被称为优先级天花板,
这个模式很好玩的在于,他直接避免了阻塞的发生:)优先级继承的机制触发条件是,高优先级任务被手握资源的低优先级任务阻塞,而天花板则是,只要他手握资源,那么在所有可能需要此资源的线程中,他拥有最高优先级,
这意味着,其他资源的占有比CPU的占有更重要,占有其他临界资源,我可以给你配好CPU的使用优先级,但你如果只是占有CPU 实际上你还被阻塞 等同于没有优先。
————————————————
版权声明:本文为CSDN博主「阮菜鸡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43178828/article/details/113914627
七、守护线程
Thread.setDaemon(true); 表示将该线程设置为守护线程,默认是false表示是用户线程。
八、线程同步
1. synchronized关键字
线程不安全示范:
代码如下:
// 线程不安全示例代码
public class ThreadNotSafe {
public static void main(String[] args) {
Ticket t = new Ticket(10);
Thread thread1 = new Thread(t,"小明");
Thread thread2 = new Thread(t,"小红");
Thread thread3 = new Thread(t,"小强");
thread1.start();
thread2.start();
thread3.start();
}
}
class Ticket implements Runnable{
private int ticket;
Boolean flag = true;
public Ticket(int ticket){
this.ticket = ticket;
}
@Override
public void run() {
while (flag){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
public void buy(){
if(ticket <= 0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() +"买到了第" + ticket-- + "张票");
}
}
上示例模拟火车票购票,三个线程同时执行买票操作,总票数设置为10张,未保证线程安全,运行结果如下:
可以看到这里有多人买到了同样的票,在这个例子中这样线程是不安全的。
解决方法:
// buy方法中加入synchronized关键字进行线程同步控制
public synchronized void buy(){
if(ticket <= 0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() +"买到了第" + ticket-- + "张票");
}
Synchronized锁对象内方法时,默认锁的是对象本身 this, 因此对于我们不需要锁本身而是实际操作对象时,可以使用同步块策略。
2. CopyOnWriteArrayList
测试JUC安全类型的集合,它是线程安全的。java.util.concurrent.CopyOnWriteArrayList
3. 死锁
以下为一段死锁代码示例:
public class DeadLock {
public static void main(String[] args) {
MakeUp makeUp1 = new MakeUp(0,"灰姑凉");
MakeUp makeUp2 = new MakeUp(1,"白雪公主");
new Thread(makeUp1).start();
new Thread(makeUp2).start();
}
}
// 口号
class Lipstick{
}
// 镜子
class Mirror{
}
class MakeUp implements Runnable{
// 需要的资源只有一份,用static来保证
static Mirror mirror = new Mirror();
static Lipstick lipstick = new Lipstick();
int choose; // 选择
String girlName; // 使用化妆品的人
public MakeUp(int choose, String girlName){
this.choose = choose;
this.girlName = girlName;
}
@Override
public void run() {
// 进行化妆
if(choose == 0){
synchronized (mirror){
System.out.println(girlName + "拿到了镜子");
synchronized (lipstick){
System.out.println(girlName + "拿到了口红");
}
}
}else {
synchronized (lipstick){
System.out.println(girlName + "拿到了口红");
synchronized (mirror){
System.out.println(girlName + "拿到了镜子");
}
}
}
}
}
灰姑凉和白雪公主这两个女孩要化妆,灰姑凉先选择拿口号给自己上个口红先,白雪公主先拿到镜子先照照,但是当前镜子和口红都只有一个,这时候灰姑凉要拿镜子了,但是还拿着口红不放,白雪公主想要画口红了但是同样不放开已经拿到手的镜子,结果两个人进入了僵持,谁也不先让步,结果谁也画不成妆了。
程序死锁,进程永远无法继续运行
同一资源被两个进程使用,互相持有又不放开,谁都请求不到造成死锁,这里解决的方法就是不要让synchronized代码块持有两个或以上的对象锁,在写代码的时候就要采用合理的策略,避免这种情况的发生,如上例中对一个对象操作完释放掉对象锁。
4. Lock(锁)
Lock测试代码:
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Station s = new Station();
new Thread(s,"黄牛").start();
new Thread(s,"小红").start();
new Thread(s,"小明").start();
}
}
class Station implements Runnable{
private int ticket = 1000;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
buy();
// 模拟延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void buy(){
lock.lock();
try {
while (true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +"买到了第" + ticket-- + "张票");
}else {
break;
}
}
}finally {
lock.unlock();
}
}
}
九、线程协作
生产者消费者模式
1. 线程通信
2. 管程法
生产者消费者模型 利用缓冲区解决:管程法。
生产者、消费者、产品、缓冲区
代码示例:
public class TestGuanCheng {
public static void main(String[] args) {
Container maidaolao = new Container();
Productor productor = new Productor(maidaolao);
Consumer consumer = new Consumer(maidaolao);
new Thread(productor).start();
new Thread(consumer).start();
}
}
class Chicken{
int id;
public Chicken(int id){
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Consumer implements Runnable{
Container store;
public Consumer(Container store){
this.store = store;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第" + store.pop().getId() + "只鸡");
}
}
}
class Productor implements Runnable{
Container store;
public Productor(Container store){
this.store = store;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken c = new Chicken(i);
store.push(c);
System.out.println("已经生产了第" + i + "只鸡了");
}
}
}
class Container{
Chicken[] chickens = new Chicken[10];
int count = 0;
// 生产
public synchronized void push(Chicken chicken){
// 如果容器满了,就需要等待消费者来消费
if(count == chickens.length){
// 生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没满就丢入产品
chickens[count] = chicken;
count++;
// 通知等待中的线程来进行消费
this.notifyAll();
}
// 消费
public synchronized Chicken pop() {
// 判断能否消费
if(count == 0){
// 消费等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果可以消费
count--;
Chicken chicken = chickens[count];
// 通知等待中的线程来进行生产
this.notifyAll();
return chicken;
}
}
3. 信号灯法
测试生产者消费者问题2 信号灯法:标志位解决
简单来讲就是利用一个或多个标志位来控制各个线程之间的执行
代码示例:
public class TestXinHaoDeng {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
class Player extends Thread{
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{
TV tv;
public Watcher(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
class TV{
String show;
// 正在表演,观众等待 T
// 正在观看,演员等待 F
Boolean flag = true;
// 表演
public synchronized void play(String show){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了" + show);
// 通知观众观看
this.notifyAll();
this.show = show;
this.flag = !this.flag;
}
// 观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了" + show);
// 提醒演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
十、线程池
创建一个线程池的方法:
创建线程池代码示例(newFixedThreadPool(int nThreads)方法):
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
// 1. 创建服务,创建线程池
// newFixedThreadPool 参数为线程池大小,即最大线程数
ExecutorService se = Executors.newFixedThreadPool(10);
// 2. 执行
se.execute(new MyThread());
se.execute(new MyThread());
se.execute(new MyThread());
se.execute(new MyThread());
// 3. 关闭线程池
se.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
总结
以上能在实际运用多线程有个初步的了解,了解其使用原理的话理解起来也更加轻松了,文章内截图来自B站狂神说多线程详解视频。