多线程有很多的方法定义,大部分都在Thread类里面,我们这里强调几个与我们日常开发有关的方法。
复制代码
线程的命名与取得
先来看下Thread类的构造函数
这里由两个可以在创建线程时指定名字的构造函数。
然后在Thread类中也有设置和取得名字的函数。如下:
因为这些方法是在Thread类里面的,如果换回Runnable接口实现的线程,它的定义里面并没有这些方法。
所以想取得线程的名字,也只是能取得当前执行run()方法的线程名字。(在一个时间点上,只能有一个线程操作)
在Thread类中就有这么一个方法:取得当前线程的名字
常用操作就为: Thread.currentThread().getName() ----> 取得当前线程名字
线程的休眠
所谓线程的休眠就是指让当前执行的线程进入到一个阻塞状态。
这个方法很见名知意呀,让当前线程睡一会。
但是这个方法会抛出一个异常。
这是一个中断异常。就相当于你在睡觉,突然有人把你吵醒一样,那你起床气不来了么。哈哈。
注意点:如果在多线程操作时,所有线程都会休眠。
线程优先级
我们知道线程的执行顺序是由CPU进行调度的,分配了时间片就可以执行,但是如果我们想让线程有先后顺序呢?
- 设置优先级: public final void setPriority(int newPriority)
- 取得优先级: public final int getPriority()
我们看到设置和取得都是整型变量,这是个什么意思呢?
我们可以在Thread类中看到有这么3个常量,通过注释,我们知道可以用它们来设置线程的优先级。
注意:设置线程的优先级是处于理论上的,并不能完全保证线程的优先级越高就一定会先执行这个线程
线程的同步与死锁
线程的同步就是多个线程访问同一资源时需要考虑的问题。
比如:
我卖20张票,应该是票卖完了就不能卖了。所以我会判断当票大于等于1时,我才能卖票,然后对票数递减。
但是在最后一张票时,一个线程(人买票)进来,发现有一张票,好,可以过了判断。但是刚好过了判断,没有对票数递减时,这个线程的执行时间结束了!
然后另外一个线程获取到时间片进来了,它也进行判断,发现 诶,还有一张票,过了判断然后对票数递减,这时已经没有票了。
那么上一个过了判断的线程再次获取到时间片,但是它是已经过了判断的,所以它也对票数递减。这样票数就成负数了。这是错误的。所以我们在处理多线程时,一定要着重注意此类问题。
复制代码
同步处理
我们观察上面举的案例,发现判断和递减是分步操作所以才会有别的线程钻了空子,从而引发安全问题。
所以我们应该把判断和递减弄成一步操作(可以说是事务),那么问题不就解决了么。
Java中有一个关键字:synchronized 这算是Java中单词最长的关键字了,哈哈。
第一种:public synchronized void test(){}
第二种: synchronized(锁对象){ 代码块 }
它可以在方法上使用,表明是个同步方法。也可以只针对某一代码块使用。
多线程运行时,有且只有一个线程能对这个方法进行操作(进入时会把这个方法给锁上)。别的线程要想进入必须等上一个线程把这个方法执行完(执行完时会把锁给释放)。
这个关键字就可以帮我们解决以上的问题啦。
注意点:这个锁对象一定要是多线程共同对应的对象,要不然一个线程拿着A锁,一个线程拿着B锁,毛用没有呀。一般情况下我们都是使用当前类对象:this
死锁
通过以上解释可以分析出,所谓的同步就是一个线程等待另一个线程执行完才可以执行,但是同步过多就有可能导致一个很严重的问题:死锁
死锁通俗点解释就是:
线程A进入同步方法A,拿到A锁,线程B进入同步方法B,拿到B锁。但是方法A中需要调用方法B,方法B也需要调用方法A。
我们知道加了同步后,线程必须得一个一个执行。
上面的情况就是:A没执行完(拿着A锁,没有释放),需要调用B。B没执行完(拿着B锁,没有释放),需要调用A。
但是这样能调用吗?是不是A和B就在互相较劲了。你不能调用我,我也不能调用你,就卡死在这了。这就是我们说的死锁。
复制代码
通常情况死锁并不会频繁产生,只需要开发人员在编码时注意点就行。
最后来个多线程中最具有代表性的生产者和消费者模型。
package com.wuzzzh.thread;
class Info {
private String name;
private String job;
// private boolean flag = true;
// 第一版去除 synchronized
public synchronized void set(String name, String job) {
// if(flag == false){
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
this.name = name;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.job = job;
// flag = false;
// this.notify();
}
// 第一版去除 synchronized
public synchronized void get() {
// if(flag == true){
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name= " + name +
", job= " + job);
// flag = true;
// this.notify();
}
}
class Productor implements Runnable {
private Info info;
public Productor(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
info.set("cuz", "小奶狗");
} else {
info.set("wes", "大狼狗");
}
}
}
}
class Custormer implements Runnable {
private Info info;
public Custormer(Info info) {
this.info = info;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
info.get();
}
}
}
/**
* @Description: // TODO 多线程生产者与消费者模型
* @Author: Wuzzzh
* @Create: 2019/5/4
*/
public class ThreadDemo {
public static void main(String[] args) {
Info info = new Info();
new Thread(new Productor(info)).start();
new Thread(new Custormer(info)).start();
}
}
复制代码
这其中分为三个版本:
第一个版本:注释中的什么都不放开,并把方法上的synchronized关键字去除。
第二个版本:给方法上加上synchronized关键字。
第三个版本:方法上加上synchronized关键字并把注释全部解开。
第一个版本运行后有以下问题:
- 输出的文字有很多重复
- 输出的文字错乱
第二个版本运行后:
- 解决了文字错乱问题
- 但是文字重复问题更加严重
第三个版本运行后:
- 这才是我们想要的结果嘛。(生产一个消费一个)