理解多线程
当我们打开了一个程序,就意味着在系统内部新建了一个进程,进程并不是程序执行的最小单位,每一个进程下面可能有很多线程,线程是实际CPU能够执行的最小单位
主线程
在Java程序启动时,一个线程立刻运行,该线程通常称为程序的主线程。
- 主线程的重要性体现在两个方面:
- 它是产生其他子线程的线程。
- 通常它必须(除了守护线程之外)最后完成执行,因为它执行各种关闭动作。
注意
- 主线程并不一定负责去关闭其他的子线程
- 主线程并不一定是最后结束的(守护线程)
如何创建线程
方法1:创建类,使其继承Thread,并重写其中的run()方法
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.currentThread().getName()+":这是一条使用方法1创建的线程");
System.out.println("此方法新建一个类,使其继承自Thread,并重写run()");
}
}
//使用
MyThread myThread = new MyThread(“测试线程”); //新建自定义线程类的实例对象
myThread.start(); //开始执行线程
方法2:定义一个实现Runnable接口的实现类,实现run()方法
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这是一条使用方法2创建的线程");
System.out.println("此方法新建一个类,使其实现Runnable接口,并重写run()");
}
}
//使用
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable, "方法2");
thread.start();
方式3:定义一个实现Callable接口的实现类
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + ":这是一条使用方法3创建的线程");
System.out.println(Thread.currentThread().getName() + ":此方法新建一个类,使其实现Callable接口,并重写call(),且有返回值");
System.out.println(Thread.currentThread().getName() + ":此线程正在计算数据:1+2+3+……+100");
int x = 0;
for (int i = 0; i < 100; i++) {
x += i;
}
return x;
}
}
//使用:
MyCallable myCallble = new MyCallable();
FutureTask<Integer> futrueTask = new FutureTask<Integer>(myCallble);
System.out.println(Thread.currentThread().getName() + ":主线程正在运行");
Thread callble = new Thread(futrueTask, "MyCallble");
callble.start();
Integer integer = futrueTask.get();
System.out.println(Thread.currentThread().getName() + ":获取到子线程的运行结果:" + integer);
三种创建线程的区别:
- 实现Runnale接口可以避免你Java单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的,适合多个相同程序代码的线程去处理同一资源的情况,
- 继承Thread类和实现Runnable方法启动线程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理机可用,则执行Run方法;
- 实现Callable接口要实现call方法,并且线程执行完毕后会有返回值,其他的两种都是重写run方法,没有返回值
- 使用Thread创建线程,不能做到资源共享
//创建Thread继承
class MyThread1 extends Thread {
int price = 10;
@Override
public void run() {
while (true) {
if (price > 0) {
price--;
System.out.printf(Thread.currentThread().getName() + ":正在售票,目前剩余%d\r", price);
}
}
}
}
//测试使用
MyThread1 thread = new MyThread1();
thread.start();
MyThread1 thread2 = new MyThread1();
thread2.start();
//结果显示各卖各的票
//创建Runnable接口的实现类
class MyRunnable2 implements Runnable {
int price = 10;
@Override
public void run() {
while (true) {
if (price > 0) {
price--;
System.out.printf(Thread.currentThread().getName() + ":正在售票,目前剩余%d\r", price);
}
}
}
}
//测试使用
MyRunnable2 runnable = new MyRunnable2();
Thread thread1 = new Thread(runnable) ;
Thread thread2 = new Thread(runnable) ;
thread1.start();
thread2.start();
//结果显示售卖同一份
Thread的重要方法
start() :启动线程
setPriority(int p): 设置线程的优先级
interrupt():有条件中断线程;
sleep(long s):(static) 使线程睡眠让出CPU资源
current Thread():(static)获取当前正在执行的线程
isAliva():判断线程是否在活动状态
yield():让出CPU资源
setDaemon():是否是守护线程;
join():等待线程消亡
线程的状态:
- 新建状态:线程对象被创建后,就进入了新建状态,例如:Thread thread = new Thread();
- 就绪状态:也被称为可执行状态,线程对象被创建后,其他线程调用了该对象的Start()方法,从而来启动该线程,处于就绪状态的线程,随时可能被CPU调度执行
- 运行状态:线程获取CPU权限进行执行,需要注意的是,线程只能从就绪状态进入到运行状态
阻塞状态:阻塞状态是现成因为某种原因放弃CPU的使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态,阻塞的情况分三种:- 等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程所占用)塔会进入同步阻塞状态
- 其他阻塞:通过调用线程的sleep()或发出了IO请求时,线程会进入到阻塞状态,当sleep()状态超时,join()等待线程终止或超市,或者IO处理完成时, 线程重新进入就绪状态
死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期
线程的优先级
通过setPriority(),来设置线程的优先级,默认为5,最高为10,最低为1,优先级越高的线程更有机会获得CPU使用权
Mthread m1 = new Mthread("线程1");
Mthread m2 = new Mthread("线程2");
m1.setPriority(10);
m2.setPriority(1);
线程让步
yield():目前运行的线程让出CPU资源,然后重新竞争(效果不明显)
线程插队
线程使用join()方法来进行线程插队
Thread mt = new Thread(){
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在计数:"+i);
}
};
};
mt.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在计数:"+i);
if(i==3) {
mt.join();
}
}
//main正在计数:0
//main正在计数:1
//main正在计数:2
//main正在计数:3
//Thread-0正在计数:0
//Thread-0正在计数:1
//Thread-0正在计数:2
//Thread-0正在计数:3
//Thread-0正在计数:4
//Thread-0正在计数:5
//Thread-0正在计数:6
//Thread-0正在计数:7
//Thread-0正在计数:8
//Thread-0正在计数:9
//main正在计数:4
//main正在计数:5
//main正在计数:6
//main正在计数:7
//main正在计数:8
//main正在计数:9
线程等待
- wait():当前线程调用wait()释放对象锁,进入等待状态
- wait(long timeout):时间到了自动唤醒
- notify()/notifyAll()唤醒等待的线程
生产者和消费者来理解wait()和notify()
public class Test {
public static void main(String[] args) {
Factory factory = new Factory();
ProThread pro = new ProThread(factory);
ConThread con = new ConThread(factory);
pro.start();
con.start();
}
}
//创建工厂
class Factory{
int num = 0;
//制造方法
synchronized void set() {
if (num == 10) {
notify();
try {
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
num++;
System.out.println("生产" + num);
}
//消费方法
synchronized void get() {
if (num == 0) {
notify();
try {
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
num--;
System.out.println("消费" + num);
}
}
//制造者线程类
class ProThread extends Thread {
Factory factory;
public ProThread(Factory factory) {
this.factory = factory;
}
@Override
public void run() {
while (true) {
factory.set();
}
}
}
//消费者线程类
class ConThread extends Thread {
Factory factory;
public ConThread(Factory factory) {
this.factory = factory;
}
@Override
public void run() {
while (true) {
factory.get();
}
}
}
守护线程
不是应用程序的核心部分:当后台只有守护线程在运行的时候,程序终止
设置守护线程
Thread thread = new Thread();
thread.setDaemon(true);
结束线程:
结束线程就是让线程的run方法执行完毕
方法1:修改标记
- 使用于停止不阻塞的线程
- 定义flag
在run方法中使用while(flag),当修改flag的值时,停止线程
- 定义flag
class ThreadTest extends Thread {
boolean flag = true;
void setFlag(boolean flag) {
this.flag = flag;
}
public void run() {
while (flag) {
System.out.println("程序正在运行");
}
}
}
//测试
ThreadTest thread = new ThreadTest();
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
thread.setFlag(false);
}
方法2:使用isInterruoted()//返回线程是否中断
- 适用于睡眠或等待中的线程
- 同上,在while(this.isInterruoted())
然后使用 interrupt()方法就可以终止线程//中断该线程
- 同上,在while(this.isInterruoted())
Thread thread = new Thread() {
public void run() {
while (!this.isInterrupted()) {
System.out.println("此线程正在执行!");
}
};
};
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
thread.interrupt();
线程同步
当两个或者多个线程需要访问统一资源的时候,他们需要以某种顺序来确保该资源某一时刻只能被一个线程使用的方式成为同步
实现同步
同步方法
synchronized关键字修饰的方法,称为同步方法
- 当一个线程调用synchronized修饰的方法后,其他的线程必须等待此线程的结束才可以继续调用此方法
-
测试:10万个人,每人给你1元钱,测试setMoney()方法,加synchronized关键字和不加的区别
class Bank {
int money;
public void setMoney(int money) {
this.money += money;
}
public int getMoney() {
return money;
}
}
class BankThread extends Thread {
Bank bank;
public BankThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.setMoney(1);
System.out.println(Thread.currentThread().getName() + "存入了1元,当前余额:" + bank.getMoney());
}
}
测试:
Bank bank = new Bank();
for (int i = 0; i < 100000; i++) {
BankThread test = new BankThread(bank);
test.start();
}
同步代码块
- 锁住对象,用法:
synchronized(this){
//代码
}
- 也可以锁静态的方法,当一个线程访问synchronized修饰的static方法,其他线程就不能访问本类所有的synchronized static
static synchronized void add(){
}
锁的理解
可以同时访问的情况:
- 情况1:一条线程调用有锁的静态方法,一条线程调用有锁的实例方法
- 情况2:一条线程用实例a调用有锁的实例方法A,一条线程用实例b调用有锁的实例方法B
- 锁实例方法:
当锁实例方法时,锁住的时对象,所以如果有一条线程调用了该对象加锁的实例方法,其他线程就不能再用该对象调用此类下的所有加锁的实例方法,但是可以进入未加锁的实例方法 - 锁静态方法:
锁住的是类,当一条线程调用被锁住的静态方法时,其他线程将不能调用本类中其他加锁的静态方法,但是可以调用未加锁的静态方法
- 锁实例方法:
死锁
当两个线程同时执行,线程Xa进入了有锁的Fa,线程Xb进入了 有锁的Fb,当这个时候,Xa需要执行Fb,Xb需要执行Fa的时候,就发生了死锁
可重入锁
当一个线程调用加锁的Fa()的时候,如果Fa()内部或者接下来需要调用同一类的加锁的Fb(),不需要继续获取锁
lock锁(lock是一个类,可以实现线程同步的效果)
lock锁可以判断线程有没有获得锁,必须手动释放
- 常用方法:
新建方法:Lock lock =new ReentrantLock();
void lock()//获取锁
void unlock() //释放锁
class A{
Lock lock = new ReentrantLock();
public void Fa() {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取了锁");
System.out.println(Thread.currentThread().getName()+"正在执行Fa");
System.out.println(Thread.currentThread().getName()+"释放锁");
lock.unlock();}
}
boolean trylock() //判断是否获取了锁
boolean trylock(int ,time timeUnit) //判断在某时间加单位内是否获取到了锁
void lockInterruotibly() 如果当前线程未中断,则获取锁,可以响应中断
Condition newCondition() 返回绑定到此lock实例的新condition实例