文章目录
多线程的常用操作方法
1.1 线程命名与取得
多线程的运行状态是不确定的,所以对于多线程操作必须有一个东西明确标识出线程对象的信息,这个信息往往通过名称来描述。在Thread类中提供有如下的线程名称方法:
No | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Thread(String name) | 构造 | 直接继承Thread类时运用 |
1 | public Thread(Runnable target ,String name) | 构造 | 实现Runnable接口时运用 |
2 | public final synchronized void setName(String name) | 普通 | 设置线程名字 |
3 | public final String getName() | 普通 | 取得线程名字 |
1.1.1 取得线程的对象
要想取得线程的对象,在Thread类中提供有一个方法取得当前的线程对象:public static native Thread currentThread()
class MyThread implements Runnable{
public void run()
{
for(int i=0;i<10;i++)
{
//public final String getName()取得线程名字
System.out.println("当前线程:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class Test{
public static void main(String[] args)
{
MyThread thread1 = new MyThread();
new Thread(thread1).start();
new Thread(thread1).start();
}
}
通过上述代码发现,如果没有设置线程名字,则会自动分配一个线程名字,需要注意的是,线程名字如果要设置应避免重复,同时中间不能修改。
1.1.2 设置线程名字
继承Thread类:
class Mythread extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++)
{
//取得线程信息,获得进程名称
System.out.println("当前线程:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class Test {
public static void main(String[] args) {
Mythread thread = new Mythread();
thread.setName("A");
thread.start();
}
}
class Mythread extends Thread{
public Mythread(String name)
{
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++)
{
//取得线程信息,获得进程名称
System.out.println("当前线程:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class Test {
public static void main(String[] args) {
Mythread thread = new Mythread("A");
thread.start();
}
}
class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++)
{
System.out.println("当前线程:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class Test{
public static void main(String [] args){
Thread thread = new Thread(new Mythread());
thread.setName("A");
thread.start();
}
}
class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++)
{
System.out.println("当前线程:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class Test{
public static void main(String [] args){
Thread thread = new Thread(new Mythread(),"A");
thread.start();
}
}
观察主线程的执行结果:
package www.miao.java;
class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++)
{
System.out.println("当前线程对象:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class Test{
public static void main(String [] args){
Mythread thread = new Mythread();
//对象名.run()相当于调用普通方法
thread.run();
new Thread(thread).start();
}
}
通过以上程序我们发现,主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。
实际上每当使用了Java命令去解释程序的时候,都表示启动了一个新的JVM程序。而主方法只是这个进程上的一个线程而已。
1.2 线程休眠(sleep方法)
线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。线程休眠会交出CPU,让CPU去执行其他的任务,但是sleep方法不会释放锁。public static native void sleep(long millis)throws InterruptedException //休眠时间以毫秒为单位
sleep为类方法,可通过方法名直接调用。
处理休眠操作:
package www.miao.java;
class Mythread implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++)
{
try {
//线程休眠0.1s
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:"+Thread.currentThread().getName()+" i = "+i);
}
}
}
public class Test{
public static void main(String [] args){
Mythread mt = new Mythread();
new Thread(mt,"A").start();
new Thread(mt,"B").start();
new Thread(mt,"C").start();
}
}
通过代码运行会错误的认为三个线程是同时休眠的,但是,所有的代码是依次进入到run()方法中的,真正进入到方法的对象可能是多个,也可能是一个,进入代码的顺序可能有差异,但是总体的执行是并发执行。(并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行)
当调用sleep方法,会将线程从运行状态变到阻塞状态(线程暂停不动)并且一定会交出CPU并且不会释放锁,阻塞解除后(自动解除)返回到就绪状态,但是从就绪状态到运行状态需要系统调度并不是立即执行,但是CPU什么时候调度我们并不知道并且是随机的,所以会造成速度上的差异,所以我们可以看见并不是匀速的输出。
1.3 线程让步 (yield方法)
线程让步(yield()方法): public static native void yield();
暂停执行当前正在执行的线程对象,并执行其它线程,意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程,它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点和sleep是不一样的。
package www.miao.java;
class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++)
{
Thread.yield();
System.out.println("当前线程:"+Thread.currentThread().getName()+" i="+i);
}
}
}
public class Test{
public static void main(String [] args){
Mythread mt = new Mythread();
new Thread(mt,"A").start();
new Thread(mt,"B").start();
new Thread(mt,"C").start();
}
}
其实这段代码视觉上我们并没有看出来有什么差别,也不知道进程有没有交出CPU。
yield与sleep的区别?
1. 调用yield方法,进程无法控制交出CPU的时间,由系统调度决定,但是sleep调用后进程会立马交出CPU,只是什么时候从就绪状态变成运行状态有系统调度决定。
2.sleep从运行状态到阻塞状态,yield从运行状态到就绪状态。
如何证明进程立马交出了CPU?
线程进入run方法执行sleep()或者yield(),如果线程不是立马交出CPU,我们肯定可以看见一个线程连续执行的情况,但根据执行结果我们可以发现并没有这种情况出现。
1.4 等待线程终止 join()方法
等待该线程终止,意思就是如果在主线程中调用该方法就会让主线程休眠,让调用该方法的线程执行完毕后在恢复执行主线程。
在主线程中调用此方法,即使子线程在等待sleep(),主线程也不会立即执行,仍会等待子进程运行完。
调用join()方法线程会从运行状态到阻塞状态,并且会释放对象锁,当join方法执行完毕后从阻塞状态回到就绪状态。
package www.miao.java;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
class Mythread implements Runnable{
@Override
public void run() {
System.out.println("主线程睡眠前的时间");
Test.printTime();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println("睡眠结束的时间");
Test.printTime();
}
}
public class Test{
public static void main(String [] args)throws InterruptedException{
Mythread mt = new Mythread();
Thread thread = new Thread(mt,"子线程A");
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName());
System.out.println("代码结束");
}
//打印当前时间
public static void printTime()
{
Date data = new Date();
//规定日期格式
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(data);
System.out.println(time);
}
}
我们说主线程和子线程是同时进行的,如果主线程中没有调用join方法,那么主线程和子线程应该会同时运行也就是说:System.out.println(Thread.currentThread().getName()); System.out.println("代码结束");
这两句代码有可能会在子线程开始运行之前或者子进程运行期间完成,不一定会最后执行。
但是当主线程中调用join方法时,即就是等子线程运行完之后才会运行,即使子进程中有sleep语句,这两句代码也一定是在子进程运行完之后才在打印。
join()方法只是对Object提供的wait()做了一层包装而已。为什么jion方法会释放对象锁原因就是wait方法释放了对象锁。
1.5 线程停止
多线程中有三种方式可以停止线程:
1.5.1 设置标记位
设置标记位,可以使线程正常退出。
package www.miao.java;
class Mythread implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i=1;
//当flag为true时进入
while(flag)
{
try {
//子线程休眠2s
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 Test{
public static void main(String [] args)throws InterruptedException{
Mythread thread = new Mythread();
Thread thread1 = new Thread(thread,"子线程A");
thread1.start();
//主线程休眠2s
Thread.sleep(2000);
//将flag设置为flase循环将退出,线程停止
thread.setFlag(false);
System.out.println("代码结束");
}
}
1.5.2 使用stop方法
调用Thread类的stop方法强制使线程退出,但是该方法不安全,所以已经被Deprecated(废弃)了。
package www.miao.java;
class Mythread implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i=1;
while(flag)
{
try {
Thread.sleep(1000);
System.out.println("第" +i+"次执行,线程名称为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test{
public static void main(String [] args)throws InterruptedException{
Mythread thread = new Mythread();
Thread thread1 = new Thread(thread,"子线程A");
thread1.start();
Thread.sleep(2000);
thread1.stop();
System.out.println("代码结束");
}
}
为什么说stop不安全呢?
假如一个线程正在执行:while(flag){x = 3; y =4;}如果是设置标记位停止线程,即使代码正在执行x=3,那么y=4也会执行,但若是调用stop方法强行停止,即使代码正在执行x=3,循环还没有执行完毕,线程也会立马停止,所以很会容易产生废弃数据。
1.5.3 使用interrupt()方法
interrupt()方法只是将线程状态置为中断状态而已,它不会中断一个运行的线程。此方法只是给线程传递一个中断信号,程序可以根据此信号来判断是否需要终止。
package 多线程;
class Mythread implements Runnable {
@Override
public void run() {
int i = 1;
while(true)
{
//判断当前线程是否被打断
//没被打断之前bool的值为false,被打断之后bool的值改为true
// 但是并不影响后面代码的执行,程序还是会一直循环下去
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+
"次被执行....");
System.out.println(bool);
i++;
}
}
}
public class Test{
public static void main(String [] args)throws InterruptedException{
Mythread thread = new Mythread();
Thread thread1 = new Thread(thread,"子线程A");
thread1.start();
Thread.sleep(3000);
thread1.interrupt();
System.out.println("代码结束");
}
}
一部分执行结果:
package 多线程;
class Mythread implements Runnable {
@Override
public void run() {
int i = 1;
while(true)
{
//判断当前线程是否被打断
//没被打断之前bool的值为false,被打断之后bool的值改为true
// 但是并不影响后面代码的执行,程序还是会一直循环下去
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+
"次被执行....");
System.out.println(bool);
//标志位被改之后退出
if(bool)
{
System.out.println("线程退出");
break;
}
i++;
}
}
}
public class Test{
public static void main(String [] args)throws InterruptedException{
Mythread thread = new Mythread();
Thread thread1 = new Thread(thread,"子线程A");
thread1.start();
Thread.sleep(3000);
thread1.interrupt();
System.out.println("代码结束");
}
}
当线程中使用了wait、sleep、join导致此线程阻塞,则interrupt会在线程中抛出InterruptedException,并且将线程的中断状态由true置为false。
package 多线程;
class Mythread implements Runnable {
@Override
public void run() {
int i = 1;
while(true)
{
try {
//判断当前线程是否被打断
//没被打断之前bool的值为false,被打断之后bool的值改为true
// 但是并不影响后面代码的执行,程序还是会一直循环下去
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+
"次被执行....");
System.out.println(bool);
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程异常抛出,线程停止");
boolean bool = Thread.currentThread().isInterrupted();
System.out.println("catch 块中中断状态为:"+bool);
return;
}
}
}
}
public class Test{
public static void main(String [] args)throws InterruptedException{
Mythread thread = new Mythread();
Thread thread1 = new Thread(thread,"子线程A");
thread1.start();
Thread.sleep(3000);
thread1.interrupt();
System.out.println("代码结束");
}
}
在主线程中调用interrupt,线程的中断信号由false改为true,但是由于线程中有sleep方法导致线程阻塞,则interrupt会在线程中抛出InterruptedException异常,并且将线程的中断状态又由true置为false。抛出异常后进入catch 块线程退出。
通过上面的分析,我们可以总结,调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。
1.6 线程优先级
线程的优先级指的是:线程的优先级越高越有可能先执行,但仅仅是有可能而已,设置优先级的目的只是建议而已,CPU是不是真的采纳由CPU自己决定。
对于优先级设置的内容可以通过Thread类的几个常量来决定:
- 最高优先级:
public final static int MAX_PRIORITY = 10;
- 中等优先级:
public final static int NORM_PRIORITY = 5
- 最低优先级:
public final static int MIN_PRIORITY = 1
在Thread类中提供有如下优先级方法:
1.6.1 设置优先级
public final void setPriority(int newPriority)
package www.miao.java;
class Mythread implements Runnable{
public void run()
{
for(int i=0;i<5;i++)
{
System.out.println("当前线程:"+Thread.currentThread().getName()+" i = "+i);
}
}
}
public class Test{
public static void main(String [] args)throws InterruptedException{
Mythread thread = new Mythread();
Thread thread1 = new Thread(thread,"子线程A");
Thread thread2 = new Thread(thread,"子线程B");
Thread thread3 = new Thread(thread,"子线程C");
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
thread3.setPriority(Thread.NORM_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
1.6.2 取得优先级
public final int getPriority()
观察主方法的优先级:
public class Test{
public static void main(String [] args)throws InterruptedException{
System.out.println(Thread.currentThread().getPriority());
}
}
可见主方法是中等优先级
1.6.3 线程的继承性
线程是具有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的
例:观察线程的继承性
class A implements Runnable{
@Override
public void run() {
System.out.println("A的优先级为:"+Thread.currentThread().getPriority());
Thread thread = new Thread(new B());
thread.start();
}
}
class B implements Runnable{
@Override
public void run() {
System.out.println("B的优先级为:"+Thread.currentThread().getPriority());
}
}
public class Test{
public static void main(String[] args) {
A a = new A();
new Thread(a).start();
}
}
1.7 守护线程
守护线程是一种特殊的线程,它属于一种陪伴线程,简单点说java有两种线程:用户线程和守护线程
。可以通过Thread类提供的isDaemon()
方法来区别它们:如果返回false,则说明线程是“用户线程”;否则就是“守护线程”。
典型的守护线程就是垃圾回收线程,只要当前JVM进程中存在任何一个非守护进程没有结束,守护进程就在工作;只有当最后一个用户线程结束时,守护线程才会随着JVM一同停止工作。
主方法main是用户线程
Thread提供的setDaemon()可以将用户线程设置为守护线程
class A implements Runnable{
private int i;
@Override
public void run() {
try{
while(true)
{
i++;
System.out.println("线程名称:"+Thread.currentThread().getName()+",i="+i+",是否为守护线程:"
+Thread.currentThread().isDaemon());
Thread.sleep(1000);
}
}
catch(InterruptedException e)
{
System.out.println("线程名称:"+Thread.currentThread().getName()+"中断了");
return;
}
}
}
public class Test{
public static void main(String[] args)throws InterruptedException{
Thread thread = new Thread(new A(),"子线程A");
//设置线程A为守护线程,此语句必须在start之前完成
thread.setDaemon(true);
thread.start();
Thread thread1 = new Thread(new A(),"子线程B");
thread1.start();
Thread.sleep(3000);
//中断非守护线程
thread1.interrupt();
Thread.sleep(4000);
System.out.println("代码结束");
}
}