最近在学习Java多线程,算是入了一个门,在这里记录一下如何使用Java实现多线程,其背后的深层原理日后再分析。本文主要介绍以下内容:
1. 使用Thread类和Runnable接口实现Java多线程
2. Java多线程关键字和关键方法
2. Java多线程如何正确停止
3. Java多线程实现加锁
一、Java多线程的创建:
我们使用以下两种方法创建
1. 创建Thread类子类,覆盖run()方法,然后创建Thread实例
public class Stage extends Thread {
@Override
public void run() {
// code here
}
public static void main(String args []){
Thread sThread = new Stage();
sThread.start();
}
}
2. 创建类实现Runnable接口,创建该类的对象,将对象作为target,创建Thread实例对象。该Thread的实例对象才是真正的线程对象
public class ArmyRunnable implements Runnable {
@Override
public void run() {
// code here
}
}
ArmyRunnable a= new ArmyRunnable();
Thread t = new Thread(a, "线程名称");
t.start();
二、关键字和关键方法:
关键字:
volatile:修饰变量,使得变量具有可见性。即其他的线程也可以使用并修改该变量。
synchronized:它用来修饰一个方法或者代码块的时候,意味着当前只能有一个线程执行该方法或者代码块。需要配合锁变量来使用
private final Object lockObj = new Object();
synchronized(lockObj){
// code here
}
关键方法:
sleep(): Thread.sleep(1000),Thread类的静态函数,让当前线程休眠1000ms,从而让开CPU,但是并不会释放锁
yield(): 通俗的解释,就是当前的线程让出CPU,变成就绪态,然后CPU从所有就绪态的线程中重新选择一个线程运行。该线程仍然可能被选中运行。
run()/start():线程运行具体的业务代码要放到run()函数中,但是我们运行线程,要使用start()函数。
join():A.join()会阻塞A所在的线程,使得A线程完成以后,其所在的线程才会继续运行
wait()/notify()/notifyAll():用于加锁,释放锁,如下所示:
synchronized (lockObj) {
// 此处添加if-else判断语句,如果条件不满足那么进入锁标志等待序列
try {
lockObj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//此处添加if-else判断语句,如果条件满足,那么进行相关操作
// code here
// 线程释放锁,同时唤醒锁标志等待池中的所有对象
lockObj.notifyAll();
}
wait(): 释放锁,并且进入对象等待池
notify():唤醒对象等待池中的一个线程,放入锁标志等待池中
notifyAll():唤醒对象等待池中所有等待某个锁标志的线程,放入对应的锁标志等待池中
三、正确停止Java线程:
错误方法1:使用stop(),使得线程突然停止,甚至破坏线程的原子性。已经被废弃了
错误方法2:使用interrupt ()
public void run(){
while(!this.isInterrupted()){
System.out.println("Thread is running..");
long time = System.currentTimeMillis();
Thread.sleep(1000);
}
}
使用interrupt停止线程
thread.interrupt();
发现,上述的线程并不能被中断,会继续运行,同时抛出异常。
我们查看Java API可以发现:
当我们使用了正常使用interrupt的时候,会设置中断位;但是如果该线程被阻塞了,例如使用了wait(),sleep()的时候,这时,线程的中断位就会被清除,同时会收到InterruptedException 异常。这就可以解释上边的线程为什么不能很好地退出。
正确方法:设置线程标志位
public class ArmyRunnable implements Runnable {
volatile boolean keepRunning = true;
@Override
public void run() {
while(keepRunning){
// ...
}
}
}
当我们在外部改变了keepRunning的值的时候,该线程就不会在运行。这样就可以保证我们想要的线程原子性,而不会突然中断
四、线程加锁
在第二部分已经讲的差不多。主要就是定义一个 Object类型的锁变量,使用synchronized关键字标记临界区,然后使用wait和notify函数来标志阻塞或者唤醒线程
private final Object lockObj = new Object();
public void func(int from, int to, double amount) {
synchronized (lockObj) {
// 此处添加if-else判断语句,如果条件不满足那么进入锁标志等待序列
try {
lockObj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//此处添加if-else判断语句,如果条件满足,那么进行相关操作
// code here
// 线程释放锁,同时唤醒锁标志等待池中的所有对象
lockObj.notifyAll();
}
}
P.S.文章不妥之处还望指正