由于在程序中使用多线程,如何合理安排线程的执行顺序? 即哪些线程应该先执行,哪些应该后执行?
参考文章:https://juejin.im/post/5d897125e51d4557dc774f6f
线程类的方法
(1) 线程的构造方法:
- public Thread()
- public Thread(String name)
- public Thread(Runnable target)
- public Thread(Runnable target, String name)
(2)线程的类方法:
以下是 Thread类的主要静态方法, 即可以直接从 Thread类调用。
- CurrentThread() 返回正在运行的 Thread对象名称。
- sleep(long n) 让当前线程休眠 n 毫秒。
- sleep(long , int ) 使一个线程休眠 long 毫秒 int 纳秒
- yield() 挂起当前线程, 使其他处于等待状态的线程运行
(3)实例方法
- start() 启动一个线程, 这个线程将会自动调用 run 方法, 同时,在新的线程开始执行时,调用 start 的那个线程将立即返回执行主程序。
控制线程的优先级
尽管从概念上来说线程能够同步运行,但事实上存在着差别,如果计算机由多个 CPU则没有问题,但大部分计算机都是一个 CPU, 一个时刻只能运行一个线程, 如果有多个线程处于可运行状态想,需要排队等待CPU 资源, 此时线程将进队等候, CPU资源分配是根据“先到先服务” 的原则确定线程排队顺序的。
Java为了使有些线程可以提前得到服务, 给线程设置了优先级,在单个 CPU上运行多线程时采用线程队列技术 ,Java虚拟机支持固定优先级队列,一个线程的执行顺序取决于线程的优先级。 高优先的线程大概率比低优先的线程优先获得CPU资源,但不代表一定以优先级低的线程优先执行
线程在创建时,继承了父类的优先级, 线程创建后,还可以在任何时刻调用 setPriority 方法改变线程的优先级。 优先级为 1 ~ 10. Thread 定义了其中 3 个常数:
(1) MAX_PRIORITY, 最大优先级(值为10)。
(2)MIN_PRIORITY,最小优先级(值为1)。
(3)NORM_PRIORITY,默认优先级,(值为5),注意是Java主线程默认的优先级是 5
package Xg27;
class Thread2 extends Thread{
public void run() {
for (int i = 0; i< 1000000; i ++ );
System.out.println(getName() + "线程的优先级是" + getPriority() + "已计算完毕!");
}
}
public class exp9_2{
public static void main(String args[]) {
Thread2[] t = new Thread2[4];
for (int i = 0; i<4; i++) {
t[i] = new Thread2();
}
t[3].setPriority(10);
t[1].setPriority(1);
for(int i = 0 ;i < 4;i++) {
t[i].start();
}
}
}
运行结果:
Thread-2线程的优先级是5已计算完毕!
Thread-1线程的优先级是1已计算完毕!
Thread-3线程的优先级是10已计算完毕!
Thread-0线程的优先级是5已计算完毕!
可见并不是由高优先级就会最先执行。
控制线程的状态
(1)线程的启动——“可执行”状态
一个新的线程被创建后处于初始状态,实际上并没有立刻进入运行状态,而是处理就绪状态,当轮到这个线程执行的时候,即进入“可执行”状态,开始执行线程 run() 方法中的代码。
执行 run() 方法是通过调用 Thread类中的 start () 方法来实现的。
调用 start() 方法启动线程的 run() 方法不同于一般的调用方法,一般方法必须等到方法执行完毕才能够返回, 调用 start() 方法告诉系统该线程准备就绪可以启动 run() 方法后, 就返回, 并继续执行调用 start( )方法下面的语句, 这时 run()方法可能还在运行,这样就实现了多任务操作。
(2)线程的挂起——“非可执行”状态
线程挂起就是使线程进入非可执行状态,线程挂起后 CPU 不会分给线程时间段, 线程暂停运行, 通过重新唤醒线程可以使之恢复运行。 这个过程在外表看来什么也没有发生过, 只是线程很慢的执行一条指令。
线程进入“非可执行” 状态即挂起的原因如下:
(1) 线程休眠。 通过调用 sleep() 方法是线程进入休眠状态, 线程在指定时间内暂停运行,暂停的时间有 sleep() 给定的毫秒数决定。执行休眠方法后,如果任何一个线程中断了当前的休眠, sleep() 将抛出 InterruptedException 异常对象, 所以在使用sleep() 方法时,必须捕获该异常。
(2)线程插入。 通过调用 join() 方法使当前线程挂起,如果线程A调用 线程 B的 join () 方法,那么线程A 将被挂起,直到线程 B 执行完毕。
(3)线程等待。 通过调用 wait() 方法使线程挂起, 直到线程得到了 notify() 和 notifyAll() 消息,线程才会进入“可执行状态”。
- 方法1:通过 thread.wait(1000); 给定线程挂起等待的时间。
- 方法2: 通过thread.wait() 实现挂起, 通过 thread.notify() 或 notifyAll() 唤醒执行。
wait() ,notify() , notifyAll() 不同与其他线程方法,它们是 java.lang.Object 类的一部分,而 Object 类是所有类的父类, 所以这 3 个方法会自动被所有类继承, 它们是 final 类,所以无法重新定义。
(3)确定线程的状态
一般情况下无法确定要给线程的运行状态,对于这些处于未知状态的线程,可以通过isAlive() 方法来确定一个线程是否仍处在活动状态, 对于一个已开始运行但是还没有完成任务的线程, 这个方法返回值是 true 。但处于活动状态的线程不一定正在运行。
(4)结束线程
(1)自然消亡,一个线程从 run( ) 方法的结尾处返回, 自然消亡且不能再被运行。
(2)强制死亡,调用 Thread类中的 stop() 方法强制停止, 不过该方法已经被废弃。最好提供一个方式让线程可以完成 run() 的流程,即让它自然死亡。
(4)后台线程
即 Daemon 线程,它是一个再后台执行服务的线程。
例如,操作系统中的隐藏线程和Java语言中的垃圾自动回收线程等,如果所有的非后台线程都结束了,则后台线程也会自动终止。
可以使用 thread.setDaemon(boolean on) 方法来设置一个后台线程,参数 on 为 true ,则将该线程标记为后台线程。
但是有一点值得注意:必须在线程启动之前调用 setDaemon () 方法, 这样才能将这个线程设置为后台线程, 可以使用 thread.isDaemon() 方法来判断线程是否是后台线程。
Java的线程同步机制与应用模型
之前所讲的线程它们都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需的数据和方法,不要外部资源,也不用关心其他线程的状态和行为。
但有时一些同时运行的线程需要 共享数据 , 例如两个线程同时存取一个数据,其中一个对数据进行了修改,另外一个线程使用的是原来的数据,这就带来了数据不一致问题,因此,编程时必须考虑其他线程的状态和行为,以解决资源共享问题:
—— 同步机制
共享资源一般是文件、输入/输出端口或打印机。
为了避免多线程共享资源发生冲突的情况,只要在线程使用资源时给该资源上一把锁就可以了,
当有线程使用资源时, 此时资源的状态: 正在使用中。 就像银行的排号系统,保证了每个窗口仅有一个客户在办理业务。
访问资源的第一个线程为资源上锁, 其他线程若想使用这个资源必须等到锁解除为止,锁揭开的同时另一个线程使用该资源并为这个资源上锁。
java的线程同步机制:所谓同步机制指的是两个线程同时操作一个对象时为保持 对象数据的统一性和整体性,通过添加 synchronized(同步化)关键字来锁定对象,执行单一线程,使得其他线程不能同时调用同一个对象。
同步方式有两种形式,即 同步方法 和 同步代码块 。
1、 同步方法
同步方法将访问这个资源的方法都标记为 synchronized, 这样在需要调用这个方法的线程执行完之前, 其他调用该方法的线程都会被阻塞。 可以使用如下代码声明一个同步方法。
synchronized void sum(){……}; 定义取和的同步方法
synchronized void max()(……); 定义取最大值的同步方法
2、 同步代码块
Java语言中同步的设定不只应用于同步方法, 也可以设置程序的某个代码段块为同步区域, 可以使用如下代码声明一个代码块。
synchronized(someobject){ //someobject代表当前对象,同步的作用区域是 synchronized 关键字后大括号以内的部分,在程序执行到 synchronized 设定的同步化区块时锁定当前对象, 这样就没有其他线程可以执行这个被同步化的区块了。
……}
使用多线程应注意的问题
1 、 防止线程死锁
因为线程可以阻塞,并且具有同步控制机制可以防止其他线程在还锁还没有释放的情况下访问这个对象,这时就产生了矛盾,比如: 线程 A 在等待线程 B ,而线程 B 又在等待线程 A , 这样就造成了死锁。
一般造成死锁必须同时满足一下四个条件。
(1)互斥条件:线程使用的资源必须至少有一个是不能共享的。
(2)请求与保持条件: 至少有一个线程必须持有一个资源并且正在等待获取一个当前被其他线程持有的资源。
(3)非剥夺条件: 分配的资源不能从相应的线程中被强制剥夺。
(4)循环等待条件:第一个线程等待其他线程,后者又在等待一个线程。
如果要防止死锁,只需破坏其中一个条件即可。
2 、 使用多线程的代价
不是所有的任务都需要多线程,某些任务可以使用多线程,例如数据计算,数据查询以及输入的获得。因为这些任务通常都被认为是后台任务,不直接与用户打交道,在Java语言程序设计中,动态效果的程序会使用多线程, 例如动画的播放、动态的字幕等。
任何事情都不是完美的,多线程也不例外,在程序中使用多线程是有代价的,它会对系统产生如下影响:
(1)线程需要占用内存、
(2)线程过多,会消耗大量CPU时间来跟踪线程。
(3)必须考虑多线程同时访问共享资源的问题,如果没有协调好,就会产生令人意想不到的问题,例如死锁和资源竞争。
(4)因为同一个任务的所有线程都共享相同的地址空间,并共享任务的全局变量,所以程序也必须考虑多线程同时访问全局变量的问题。