多线程是异步的,代码顺序并不能当成是线程的执行顺序,线程被调用的时机是随机的
一个进程正在运行时至少会有一个线程正在运行。
public static void mian() #main方法的线程由JVM创建的。
从源码上不难发现,Thread实现了Runnable接口,它们之间具有多态关系。
继承Thread类的最大局限就是不支持多继承。需要多继承则可以实现Runnable接口。二者工作性质一样,没有本质上的区别。
run方法里的内容为子线程执行内容,线程通过start方法告知CPU等待调度,调度时机是随机的,由CPU分配时间调度。
注意:子线程是start方法启动,而不是调用run方法。直接调用run方法,那不还是顺序执行,而不是多线程了。
看一下原书对这里的解释:
Thread.java类中的start方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run方法。这个过程其实就是让系统安排一个时间来调用Thread中的run方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码thread的run方法就不啥异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run方法,也就是必须run方法中的代码执行完成后才可以执行后面的代码。
注意:执行start方法的顺序不代表线程启动的顺序。只是告诉系统等待启动,启动是随机的。
关于使用Runnable接口,本质上还是创建Thread类:
public class MyRunnable implements Runnable{
@Override
publlic void run(){
System.out.println("运行中!")
}
}
public class Run{
public static void main(String[] args){
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!")
}
}
既然创建线程都是创建Thread类,那么创建线程的方法都在Thread的构造方法中了。
Thread类的构造方法中可以接收Runnable对象,而Thread又实现了Runnable对象,也就是说Thread类可以接收Thread对象。将一个Thread对象中的run方法交由其他的线程进行调用。
多个线程可以调用同一个Thread的run方法?那么线程安全问题就来了。
两种情况,不共享数据和共享数据。
不共享数据
public class MyThread extends Thread{
private int count = 5;
....
}
public class Run{
public static void main(String[] args){
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread b = new MyThread("C");
a.start();
b.start();
c.start();
}
}
这种情况下,每个Thread的count是不共享的,各种拥有各种的属性。
共享变量
public class Run{
public static void main(String[] args){
MyThread mythread = new MyThread();
Thread a = new Thread(mythread,"A");
Thread b = new Thread(mythread,"B");
Thread c = new Thread(mythread,"C");
a.start();
b.start();
c.start();
}
}
还记得上一节引出的安全问题吗?来了,将一个Thread的run方法交给多个线程执行时,多个线程对该Thread的属性进行操作,数据共享,便有了安全问题。
synchronized
通过synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。
当一个线程调用run方法前,先判断run方法有没有被上锁,如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。
这样就实现了排队调用run方法的目的,实现了线程安全。
synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。
当一个线程想要去执行同步方法里面的代码时,线程首先尝试去拿这把锁,如果能拿到这把锁,就可以执行synchronized里面的代码。如果拿不到,那么这个线程就会不断的尝试拿这把锁,直到能够拿到为止,而且是多个线程同时去争取这把锁。
通过源码可以发现,print方法是同步方法,有个细节问题。
System.out.println("i=" + (i--));
上面这个代码在多线程访问时还是出现了线程安全的问题。
print不是同步方法吗?
是的,虽然print方法在内部是同步的,但是i–的操作却是在进入print之前发生的,所以有发生非线程安全问题的概率。
所以为了防止发生非线程安全问题,还是应该继续使用同步方法。
currentThread()方法是Thread的静态方法,可以返回代码段正在被哪个线程调用的信息。
System.out.println(Thread.currentThread().getName());
// 返回当前执行线程的线程名称
this.getName();
//当前run方法所在Thread类的name属性
//二者不一定相同,比如
Mythread mythread = new Mythread();
Thread thread = new Thread(mythread);
thread.setName("A");
thread.start();
class Mythread extends Thread{
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName() + this.getName());
}
}
//先输出了当前线程thread的名称,A
//然后再输出Mythread的Name。
方法isAlive()的功能是判断当前的线程是否处于活动状态。
什么是活动状态?
活动状态就是线程已经启动且尚未终止。线程正处于正在运行或者准备开始运行的状态,就认为线程是“存活”的。
注意:如果将线程对象以构造参数的方式传递给Thread对象进行start时,运行结果跟上一小结一样,会有源自Thread.currentThread()和this的差异。
方法sleep()的作用是在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行)。
这个“正在执行的线程”是指this.currentThread()返回的线程。
getId()方法的作用是取得线程的唯一标识。
停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前操作。
看起来好像很简单?但是必须做好防范措施,以便达到预期的效果。
在Java中有以下3种方法可以终止正在运行的线程:
interrupt方法停止线程,不像for+break语句那样,马上停止。
调用interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。
在停止线程钱,先要知道线程的状态是不是停止的。
二者区别就是前一个是静态方法,只能判断当前正在执行该语句的线程是否中断,而第二个则可以判断子线程。
而且interrupted会清楚中断状态,而isInterrupted则不会。
原理就是检测线程状态,中断抛出异常结束。
class Mythread extends Thread{
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if(this.isInterrupted()){
System.out.println("线程中断~~~~~");
throw new InterruptedException();
}
System.out.println("i=" + (i+1));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如果线程在sleep状态下,改变状态停止。会被catcha捕获。
public class Test {
public static void main(String[] args) throws InterruptedException {
Mythread mythread = new Mythread();
mythread.start();
Thread.sleep(2000);
mythread.interrupt();
}
}
class Mythread extends Thread{
@Override
public void run() {
super.run();
try {
Thread.sleep(200000);
} catch (InterruptedException e) {
System.out.println("线程中断"+this.isInterrupted());
e.printStackTrace();
}
}
}
结论就是,在sleep状态下停止某一线程,会进入catch语句,并且清楚停止状态,使之变成false。
使用stop方法停止线程则是非常暴力的。
不推荐使用!
stop方法会抛出ThreadDeath异常,此异常不需要显示的捕捉。
stop方法停止线程可能会导致一些清理性的工作得不到完成。
另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现了了数据不一致的问题。
将方法interrupt与return结合使用也能实现停止线程的效果。
判断isInterrupted然后进行return。
不过还是推荐使用“抛异常”的方法来实现线程的停止。
因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播。
暂停线程意味着此线程还可以恢复运行。
可以使用suspend方法暂停线程,
使用resume方法恢复线程执行。
暂停线程有一定的安全问题
在同步代码块中暂停线程会导致对同步对象的独占。因为暂停线程并不会释放锁。
还有一个特别细的问题
System.out.ptintln()
方法内部是线程安全的,如果线程在该方法内被暂停,导致同步锁不能释放
会导致其他线程无法进入该方法,无法打印信息。
suspend方法是过期作废的方法,但还是有必要研究它过期作废的原因。
除了上诉问题,也会出现多线程最普遍的问题——数据不同步
比如修改数据一部分后线程暂停,前后数据就会不同。
yield方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。
但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮"线程规划器"确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()
方法
线程优先级分为1~10这十个等级,小于1或大于10,则抛出异常。
线程的优先级具有继承性:比如A线程启动B线程,则B线程的优先级与A是一样的。
线程的优先级具体体现在什么地方那?
高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部先执行。
高优先级的线程可能会优先得到执行,但不绝对,只是可能。低优先级也可能先执行。
线程的优先级与代码的执行顺序无关,线程的优 先级具有一定的规则性,也就是CPU尽量将执行资源让给优先级比较高的线程。让他得到更多的机会运行,运行的更快。
在Java中有两种线程,一种用户线程,另一种是守护线程。
守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非先守护线程了,则守护线程自动销毁。
典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了
则垃圾回收线程也就没有存在必要了,自动销毁。
本文地址:https://blog.csdn.net/lihangfu/article/details/107387320
如对本文有疑问, 点击进行留言回复!!
HashMap、Hashtable、ConcurrentHashMap三者间的异同
解决RecycleView 中Item包含Edittext时,滑动view复用导致数据错乱的问题
多线程、同步工作原理、死锁案例、Lock接口、线程的生命周期的讲解及实现
网友评论