Java多线程基本技能
参考:《Java多线程编程核心技术》
一、程序、线程与进程
- 程序:程序是存储在磁盘上, 包含可执行机器指令和数据的静态实体
- 进程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J0Txw49h-1604215640129)(C:\Users\VSUS\Desktop\笔记\多线程\img\1.png)]
上面是百度百科对进程这个词条的解释,程序是静态的,存储在磁盘上,进程是动态的,当程序运行起来就成了进程。
- 线程:在百度百科对进程的解释中,已经说到了进程是线程的容器。线程是在进程中独立运行的子任务,这也说明了进程与线程是包含的关系
二、创建线程的两种方法
在Java的jdk中已经自带了对多线程技术的支持,实现多线程的方式主要有两种:
- 继承Thread类
- 实现Runnable接口
1. 继承Thread类
public class MyThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
//thread.start();
System.out.println("执行结束");
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("MyThread!!");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vd7NwY5k-1604215640133)(C:\Users\VSUS\Desktop\笔记\多线程\img\2.png)]
创建一个类继承Thread,然后重写里面的run方法,在main方法中,通过Thread对象的start()方法去启动一个线程,通过执行结果,我们已经可以看到CPU是先执行MyThread类总的run方法的,可见多线程的执行顺序与代码的顺序没有关系!
2. 实现Runnable接口
java的单继承机制就决定了如果只有继承Thread的方法是具有局限性的,所以出现了实现Runnable接口的方法,同样是上面的代码思路
public class MyThreadTest {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
t1.start();
System.out.println("执行结束");
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("MyThread!!");
}
}
需要启动用实现Runnable方法的线程,可以将其作为参数传入Thread类中,下面是Thread类的构造方法
可见,Thread类的构造方法可以传入一个Runnable接口的实例,多线程的启动永远都是Thread类的start()方法
也可以使用匿名内部类、lamdba的方式去简洁的创建线程
public class MyThreadTest {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("MyThread!!");
}
});
t1.start();
System.out.println("执行结束");
}
}
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("MyThread!!");
Thread t1 = new Thread(runnable);
t1.start();
System.out.println("执行结束");
}
3.一些注意
- 要启动多线程,必须调用start方法,而不是run方法,调用run方法只是在主线程里顺序的执行
- 执行start方法的顺序,并不代表线程的先后执行顺序
public class MyThread extends Thread {
private int i;
public MyThread(int i){
this.i = i;
}
@Override
public void run() {
System.out.println(i);
}
}
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread(1);
MyThread t2 = new MyThread(2);
MyThread t3 = new MyThread(3);
MyThread t4 = new MyThread(4);
MyThread t5 = new MyThread(5);
MyThread t6 = new MyThread(6);
MyThread t7 = new MyThread(7);
MyThread t8 = new MyThread(8);
MyThread t9 = new MyThread(9);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
t9.start();
}
}
执行后的顺序为
可见,多线程的执行顺序根调用顺序并无联系!
- 不能多次调用start方法
public class MyThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
thread.start();
System.out.println("执行结束");
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("MyThread!!");
}
}
多次调用start()方法,会抛出IllegalThreadStateException异常,
- Thread类也实现了Runnable接口
这就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread的对象,这样可以将Thread对象中的run()方法交给其他线程调用,由 其他线程启动线程
三、多线程的常用方法
1.线程的命名与取得
- currentThread()
-
setName()
给线程设置名字
-
getName()
获取线程的名字
public class CountOperate extends Thread {
public CountOperate() {
System.out.println("CountOperate---begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("CountOperate---end");
}
@Override
public void run() {
System.out.println("run---begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("run---end");
}
}
public class Run {
public static void main(String[] args) {
CountOperate countOperate = new CountOperate();
Thread t1 = new Thread(countOperate);
t1.setName("A"); //设置线程名字
t1.start(); //启动线程
}
}
运行结果:
上面的代码还可以证明一个结论:CountOperate的构造方法只在main线程里执行的!
2. 线程休眠(sleep方法)
sleep方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠一段时间,注意:如果调用sleep的线程上拥有该对象的锁,那么调用sleep不会释放锁
public class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("run threadName =" + this.currentThread().getName() + " begin =" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("run threadName =" + this.currentThread().getName() + " end =" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
MyThread thread = new MyThread();
System.out.println("run threadName =" + Thread.currentThread().getName() + " begin =" + System.currentTimeMillis());
thread.start();
System.out.println("run threadName =" + Thread.currentThread().getName() + " end =" + System.currentTimeMillis());
}
}
3.线程让步yield()方法
调用yiled方法,会让线程交出CPU方法,让CPU去执行其他的线程,更sleep方法类似,也不会释放锁,但是yield不能控制具体的交出CPU的时间,注意:调用yield方法并不会让线程进入堵塞状态,而是让线程重回就绪状态,也许当前线程刚调用yiled方法,交出了CPU的控制权,而下次CPU又选择了该线程执行
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
Thread.yield();
System.out.println("run threadName =" + Thread.currentThread().getName() + " i=" + i);
}
}
}
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();
}
}
执行结果:
4.线程停止方法
在Java中,有3种方法可以终止正在运行的线程
- 使用退出标志,是线程正常退出,也就是当run方法完成后线程终止
- 使用stop方法强行停止线程,但是这个方法以及被抛弃,该方法不安全
- 使用interrupt方法
- 使用退出标志
public class MyThread implements Runnable{
private boolean flag;
@Override
public void run() {
try {
flag = true;
int i = 1;
while(flag) {
Thread.sleep(1000);
System.out.println("这是第" + i + "运行线程"+ "线程名称为" + Thread.currentThread().getName());
i++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread t1 = new MyThread();
new Thread(t1).start();
Thread.sleep(2000);
t1.setFlag(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 使用interrupt
使用interrupt并不会马上就停止操作,调用interrupt()方法只是在当前线程上中打了一个停止的标记,和上面例子差不多,只不过上面的例子是手动打上这个标记,而调用interrupt()后JVM帮我们把这个标记打上。那么就需要有一个可以判断当前线程标记是否停止的方法,有两种方法可以判断当前线程是否停止
- this.isinterrupt:测试当前线程是否已经中断,执行后具有将状态标志置清除为false
- this.interrupted:测试线程是否已经中断,不清除状态标志
仅仅使用interrupt()方法只是在当前线程打了一个停止的标记,并不是真的停止线程:
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
System.out.println("i= " + (i + 1));
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
- 使用异常或者return来停止线程
public class MyThread implements Runnable{
@Override
public void run() {
try {
for (int i = 0; i < 500000; i++) {
if(Thread.interrupted()){
System.out.println("停止状态!!");
throw new InterruptedException();
}
System.out.println("i= " + (i + 1));
}
System.out.println("我在for循环下面");
}catch (InterruptedException e){
System.out.println("进入catch!");
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
5. 线程的优先级
- setPriority
设置线程的优先级
- getPriority
取得线程的优先级
线程的优先级用整形数字表示,一共有1~10这10个等级,jdk中有3个常量来预置定义的优先级,默认优先级是5
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
- 优先级具有继承性
例如A线程启动B线程,则B线程的优先级和A一样
//线程1
public class MyThread1 implements Runnable {
@Override
public void run() {
System.out.println("MyThread1 run priority = " + Thread.currentThread().getPriority());
new Thread(new MyThread2()).start();
}
}
//线程2
public class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread2 run priority = " + Thread.currentThread().getPriority());
}
}
//主线程
public class Run {
public static void main(String[] args) {
System.out.println("main thread begin priority= " + Thread.currentThread().getPriority());
Thread.currentThread().setPriority(6);
System.out.println("main thread end priority= " + Thread.currentThread().getPriority());
new Thread(new MyThread1()).start();
}
}
这里说的继承并不是说继承的那种关系,而是指如果A线程启动B线程,那么A,B线程的优先级,默认就一样,上面代码中,main线程启动MyThread1线程,MyThread1启动MyThread2线程,如果改变了main线程的优先级那么MyThread1,MyThread2的优先级都会改变
下一篇: 事件的循环绑定与委托