1 多线程原理
多个线程”同时”运行只是我们感官上的一种表现。事实上线程是并发运行的,OS的线程调度机制将时间划分为很多时间片段(时间 片),尽可能均匀分配给正在运行的程序,获取CPU时间片的线程或进程得以被执行,其他则等待。而CPU则在这些进程或线程上来回切换运行。微观上所有进 程和线程是走走停停的,宏观上都在运行,这种都运行的现象叫并发,但是不是绝对意义上的“同时发生。
2 线程优先级
线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率。
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:
Thread.MIN_PRIORITY, 最低
Thread.MAX_PRIORITY, 最高
Thread.NORM_PRIORITY 中等
设置优先级的方法为: void setPriority(int priority)
3 守护线程
守护线程守护线程与普通线程在表现上没有什么区别,
我们只需要通过Thread提供的方法来设定即可:
void setDaemon(boolean )当参数为true时该线程为守护线程。
守护线程的特点是,当进程中只剩下守护线程时,所有守护线程强制终止。
GC就是运行在一个守护线程上的。
需要注意的是,设置线程为后台线程要在该线程启动前设置。
Thread daemonThread = new Thread();
daemonThread.setDaemon(true);
daemonThread.start();
4 sleep方法、yield方法 和 join方法
sleep方法Thread的静态方法sleep用于使当前线程进入阻塞状态:
static void sleep(long ms)该方法会使当前线程进入阻塞状态指定毫秒,当指定毫秒阻塞后,当前线程会重新进入Runnable状态,等待分配时间片。该方法声明抛出一个InterruptException。所以在使用该方法时需要捕获这个异常。
例如:电子钟程序
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
while(true){
System.out.println(sdf.format(new Date()));
try {
Thread.sleep(1000);//每输出一次时间后阻塞1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
*注:改程序可能会出现"跳秒"现象,因为阻塞一秒后线程并非是立刻回到running状态,
*而是出于runnable状态,等待获取时间片。那么这段等待时间就是"误差"。
*所以以上程序并非严格意义上的每隔一秒钟执行一次输出。
*/
yield方法Thread的静态方法yield:
static void yield()该方法用于使当前线程主动让出当次CPU时间片回到Runnable状态,等待分配时间片。
join方法Thread的方法join:
void join()该方法用于等待当前线程结束。
此方法是一个阻塞方法。该方法声明抛出InterruptException。
例如:
public static void main(String[] args) {
final Thread t1 = new Thread(){
public void run(){
//一些耗时的操作
}
};
Thread t2 = new Thread(){
public void run(){
try {
t1.join();
//这里t2线程会开始阻塞,直到t1线程的run方法执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
//以下是当前线程的任务代码,只有t1线程运行完毕才会运行。
}
};
}
5 线程同步
1 synchronized关键字(具体在锁中看)
多个线程并发读写同一个临界资源时候会发生"线程并发安全问题“常见的临界资源:
多线程共享实例变量 多线程共享静态公共变量 若想解决线程安全问题,需要将异步的操作变为同步操作。
何为同步?那么我们来对比看一下什么是同步什么异步。
所谓异步操作是指多线程并发的操作,相当于各干各的。所谓同步操作是指有先后顺序的操作,相当于你干完我再干。而java中有一个关键字名为:synchronized,该关键字是同步锁,用于将某段代码变为同步操作,从而解决线程并发安全问题。
2 锁机制
Java提供了一种内置的锁机制来支持原子性:
同步代码块(synchronized 关键字 ),同步代码块包含两部分:一个作为锁的对象的引用,一个作为由这个锁保护的代码块。
synchronized (同步监视器—锁对象引用){
//代码块
}
若方法所有代码都需要同步也可以给方法直接加锁。
每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时释放锁,而且无论是通过正常路径退出锁还是通过抛异常退出都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
3 选择合适的锁对象&范围
使用synchroinzed需要对一个锁对象上锁以保证线程同步。
那么这个锁对象应当注意:多个需要同步的线程在访问该同步块时,看到的应该是同一个锁对象引用。否则达不到同步效果。 通常我们会使用this来作为锁对象。
选择合适的锁范围在使用同步块时,应当尽量在允许的情况下减少同步范围,以提高并发的执行效率。
4 静态方法锁
public synchronized static void xxx(){
….
}
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
静态方法与非静态方法同时声明了synchronized,他们之间是非互斥关系的。原因在于,静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
5 wait和notify
多线程之间需要协调工作。
例如,浏览器的一个显示图片的 displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还没有下载 完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示 了”,这时,displayThread继续执行。
以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。