多线程
多线程类体系结构和声明周期
实现多线程的方法
启动线程必须通过Thread类的start()
方法,因为线程的运行需要计算机操作系统的支持,查看该方法的源码发现,若重复启动线程则会抛出IllegalThreadStateException
异常,并进行了系统调用native void start0()
方法,带有native修饰的方法表示操作系统的系统函数
继承Thread类
构造方法
方法名 | 描述 |
---|---|
Thread() |
创建默认命名线程对象 |
Thread(String name) |
创建指定名的线程对象 |
Thread(Runnable target) |
创建基于Runnable接口实现类的线程对象,使用默认命名 |
Thread(Runnable target, String name) |
创建基于Runnable接口实现类的线程对象,使用指定命名 |
自己启动线程只能启动一次(若启动多次会抛出异常),利用另一个线程对象启动可以对同一个对象启动多次(没必要,浪费资源)
实现Runnable接口
重写run()
方法,使用Thread来启动线程
实现Callable接口
重写call()
方法,此方法有返回值,使用FutureTask类对Callable实现类进行包装,再使用Thread来启动线程,使用Futuretask的get()
方法来获取返回值,获取返回值时调用线程会处于阻塞状态
要注意的是,使用FutureTask类进行包装后就无法对同一给FutureTask对象启动多次(若启动多次只有随机的一个线程执行),而未使用FutureTask类进行包装的(Runnable实现类和Thread子类)可以用另一个线程对象启动多次
线程控制
线程控制由Thread类的方法来实现
线程获取与命名
方法名 | 描述 |
---|---|
String getName() |
获取线程名称 |
void setName(String name) |
设置或修改线程名称 |
static native Thread currentThread() |
获取当前线程对象 |
线程休眠
方法名 | 描述 |
---|---|
static native void sleep(long millis) |
使线程阻塞,单位毫秒 |
static void sleep(long millis, int nanos) |
使线程阻塞,单位毫秒.纳秒 |
线程中断
方法名 | 描述 |
---|---|
boolean isInterrupted() |
判断线程中断终态 |
void interrupt() |
中断线程 |
Thread thread = new Thread(()->{
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println("线程中断");
// e.printStackTrace();
}
});
thread.start();
System.out.println(thread.getName()+":"+thread.isInterrupted());
Thread.sleep(500);
thread.interrupt();
System.out.println(thread.getName()+":"+thread.isInterrupted());
//有时候是false有时候是true,不知道为啥
System.out.println(Thread.currentThread().getName()+"执行完毕");
线程强制执行
方法名 | 描述 |
---|---|
void join() |
让出当前线程资源,交由该线程执行到结束后,当前线程再执行 |
void join(final long millis) |
强制执行若干毫秒 |
void join(long millis, int nanos) |
强制执行若干毫秒.纳秒 |
Thread thread0 = new Thread(()->{
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
});
Thread thread1 = new Thread(()->{
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
});
thread0.start();
thread1.start();
for (int i = 0; i < 50; i++) {
if (i == 10){
thread0.join();
//此时主线程让出资源,不在执行,thread0和thread1交替执行
//而并不是只有thread0执行
}
Thread.sleep(50);
System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
线程礼让
方法名 | 描述 |
---|---|
static native void yield() |
当前线程礼让一次,随后继续按照抢占式继续执行 |
线程优先级
线程优先级必须在MAX_PRIORITY(10)和MIN_PRIORITY(1)之间(若不再则会抛出IllegalArgumentException
非法参数异常),默认优先级是NORM_PRIORITY(5)
优先级高只表示抢到cup的可能性高,并不表示一定先执行
方法名 | 描述 |
---|---|
void setPriority(int newPriority) |
设置优先级 |
int getPriority() |
获取优先级 |
守护线程
所有非守护线程结束,守护线程自动结束
方法名 | 描述 |
---|---|
void setDaemon(boolean on) |
设置守护线程 |
线程同步
synchronized关键字
同步代码块
synchronized(锁对象){
多条控制语句
}
//多条线程只有看到是同一把锁才能使得代码块中的代码同步执行
//同一把锁指的是同一个对象,即地址相同
同步方法
- 使用synchronized修饰方法即可
- 普通同步方法的锁对象是当全对象
this
- 静态同步方法的锁对象是该类的反射对象
类名.class
Lock锁
Lock是个接口,实现类是不同形式的锁,与synchronized相同的机制的锁就是其子类ReentrantLock(可重入锁),意思就是当一个同步方法调用另一个同步方法时不用再去申请加锁
此方法加锁一般会用try...finally块来包裹,以防止出错而无法解锁
方法名 | 描述 |
---|---|
void lock() |
加锁 |
void unlock() |
解锁 |
volatile修饰符
- 只能用于成员变量上
- 传统线程操作数据是将主内存的数据copy到此线程工作内存中成为副本,对副本进行一系列操作后写回主内存。而volatile修饰的变量则是此线程直接操作主内存的数据,所以内存变量访问更加快
线程等待与唤醒机制
此些方法都在Object类中定义
方法名 | 描述 |
---|---|
void wait() |
线程等待 |
native void wait(long timeoutMillis) |
线程等待,且有超时时间 |
native void notify() |
随机唤醒一个等待线程 |
native void notifyAll() |
唤醒全部等待线程,那个优先级高那个先执行 |
public class Main{
public static void main(String[] args) {
Good good = new Good(); //相当于一个仓库,生产者和消费者共用统一个仓库
Producer producer = new Producer(good);
Consumer consumer = new Consumer(good);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Good {
private String title;
private String info;
private boolean flag = true;//true生产,false消费
public synchronized void set(String title, String info) {
//问题:数据错位,就是没有生产完呢就消费了,消费的是上次留下的
//加上同步可解决数据错位
if (this.flag == false){
//问题:生产好几个才消费一个
//加上标志位和等待唤醒机制可解决生产一个消费一个
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.title = title;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.info = info;
this.flag = false;
notify();
}
public synchronized void get() {
if (this.flag == true){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.title +":"+this.info);
this.flag = true;
notify();
}
}
class Producer implements Runnable{
private Good good;
public Producer(Good good) {
this.good = good;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 == 0){
this.good.set("A","A");
}else{
this.good.set("B","B");
}
}
}
}
class Consumer implements Runnable{
private Good good;
public Consumer(Good good) {
this.good = good;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
this.good.get();
}
}
}
ThreadLocal
解决多个线程内部使用同一个线程外部对象,修改值覆盖问题,ThreadLocal存储的数据结构存储当前线程标记和要存储的内容,所以对不同的线程都有与之对应的一个内容,不会被覆盖掉(效果相当于在线程内部new了一个公共用的那个对象,但是实际上不是,这样更加节省堆空间内存)
方法名 | 描述 |
---|---|
void set(T value) |
设置或修改内容 |
T get() |
获取内容 |
void remove() |
删除内容 |
T initialValue() |
设置初始值,也就是当前值为null的默认值,用于方法重写 |
Comments NOTHING