控制线程
join线程
等那个线程做完后,当前线程再做!
import java.lang.Thread;
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run(){
for(int i = 0; i < 4; i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws Exception{
new MyThread("新线程").start();
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i == 4){
MyThread t = new MyThread("被join的线程");
t.start();
t.join();
}
}
}
}
上面的程序中,i<4的时候,主线程main和“新线程”并发执行,i=4之后,主线程等待“被join的线程”执行完后,再执行。
一共有三个join方法如下:
后台线程
有一种线程,在后台运行,为其他线程提供服务,这种线程被称为“后台线程”,或“守护线程”,或“精灵线程”。JVM的垃圾回收线程就是后台线程。
- 如果所有的前台线程都死亡,JVM会通知后台线程死亡。
- 调用Thread的setDaemon(true)方法可以把线程设置为后台线程,注意必须是调用start方法之前设定。
- 主线程默认是前台线程,默认是前台线程的创建的子线程也默认是前台线程,默认是后台线程的创建的子线程默认也是后台线程
import java.lang.Thread;
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run(){
//这里的i的范围搞大点,为了让前台main线程执行完后,后台线程就自动死亡
for(int i = 0; i < 1000; i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws Exception{
MyThread t = new MyThread("新线程");
t.setDaemon(true);//设置为后台线程
t.start();
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
线程睡眠:sleep
让线程进入阻塞状态,即使系统中没有其他可执行的线程,处于sleep状态的线程也不会执行。
下面程序运行,可以观察到,每打印一次就暂停一秒!
改变线程优先级
基于优先级抢占式调度策略情况下,自然优先级高被调度到的概率大了!!!展示的程序就是设置优先级有悬殊,可以看到优先级高的线程被调度的次数比较多了。。。展示程序参看《疯狂Java讲义(第4版)》742~743页。
每个线程默认的优先级和创建他的父线程的优先级相同。
改变优先级和获得优先级的方法:
优先级的值可以手动设置为1~10,怎么知道这个范围的呢?可以查看这几个值啊,如下下图。但是要注意的是,最好使用这几个静态常量设置优先级,因为这些优先级依赖于操作系统的支持,手动写上优先级,可移植性弱。。
线程同步
银行取钱例子说明多线程(线程并发)具有安全问题,见《疯狂Java讲义(第4版)》743~745页
同步代码块、方法
用synchronized修饰代码块、方法(不可修饰构造器、成员变量)。
obj是同步监视器,Java允许把任何对象(注意是对象)作为同步监视器,但根据同步监视器的目的—阻止两个线程对同一个共享资源进行并发访问,通常把有可能被并发访问的共享资源(临界区)作为同步监视器。任何时刻只能由一个线程可以获得对同步监视器的锁定,当同步代码执行完后,该线程就会释放对该同步监视器的锁定。
synchronized(obj){
}
同步锁
常用的锁是ReentrantLock(可重入锁)。注意加锁的是对象(注意是对象)。具体实例参看《疯狂Java讲义(第4版)》750~751页
class X{
private final ReentrantLock lock = new ReentrantLock();
//...
public void m(){
//加锁
lock.lock();
try{
//需要保证线程安全的代码
}
//用finally保证释放锁
finally{
lock.unlock();
}
}
}
使用Lock与使用同步方法是类似的,只是使用Lock时显式使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器,,都是“加锁–修改—释放锁”模式,同一时刻只有一个线程进入临界区。
ReentrantLock锁有可重入性,也就是说,一个线程可以对已加锁的ReentrantLock锁再次加锁。
死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。具体参看《疯狂Java讲义(第4版)》751~752页示例代码。