一、概述
进程和线程:
线程可以看作是对进程的再划分,比如eclipse是一个进程,而eclipse提供的各种功能就是一个个小小的线程
进程应当是独立的应用程序,而线程,同类的进程能够共享同一块内存空间,这样在各个线程之间切换时,负担比进程之间的切换小
进程为独立运行,而线程之间可能会相互影响,考虑到死锁和线程之间的通信,多线程就是多个线程同时运行
线程和CPU:
CPU分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
CPU抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度非常快,看上去就是在同一时刻运行。
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
线程的基本状态:
创建,创建一个Thread对象,Thread thread = new Thread();
就绪,执行start()方法,进入就绪状态,等待cpu资源
执行,执行run()方法的内容,线程只能从就绪状态进入执行
阻塞,
1.获取到锁(synchronized,lock()等),未释放
2.wait()线程等待,notify()唤醒后进入就绪状态,准备运行
3.执行sleep()、join()等方法
死亡,线程执行完毕,或异常导致退出
线程安全:在并发的情况下,该代码经过多线程使用,线程的调度顺序不影响任何结果,线程不安全指代码可能会因调度顺序出现问题。
实现多线程的两种方法:
继承Thread类
继承Runnable接口
二、多线程操作
case:卖票案例
实现一个多线程卖票的案例,关键的地方在于怎么让多线程共享一份数据(票数)。
看一下Thread类构造方法Thread(Runnable target)的源码:
看关于target参数的说明:target必须实现Runnable接口,具体是指当此线程启动时,run()方法被调用的对象。
所以要实现多个线程共享数据,在new Thread()的时候,必须传入同一个对象。代码如下。
//5个线程卖票
public class TicketDemo2 extends Thread {
private int ticket = 5;
// 可以在run()方法上加synchronized关键字
@Override
public void run() {
// 加锁
synchronized (this) {
for (int i = 0; i < 10; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "--" + ticket--);
}
}
}
}
// 多个线程共享一份数据
public static void main(String[] args) {
TicketDemo2 td = new TicketDemo2();
Thread t1 = new Thread(td, "t1");
Thread t2 = new Thread(td, "t2");
Thread t3 = new Thread(td, "t3");
Thread t4 = new Thread(td, "t4");
Thread t5 = new Thread(td, "t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
synchronized关键字:
在上面的卖票案例中,ticket--,的过程如下:
取得原有ticket值
计算i -1
对i进行赋值
这样多个线程执行run()中的操作时,一个线程执行ticket--时,可能还没有执行完,另外一个线程就过来再执行ticket--,这样就导致ticket数量不会递减,因为那个线程执行到ticket--那个步骤并不确定。
加上synchronized关键字之后,当前线程会拿到锁,只有此线程执行完之后释放锁,其他线程才可以继续执行。
可以使用synchronized代码块,为关键步骤加锁,也可以在run()方法上加锁,只是这样会为整个run()方法中的内容都加锁,不如synchronized代码块灵活。
que:start()和run()的区别?
start()在Thread类中,作用是启动一个线程,线程进入就绪状态,此时线程未必会立即执行,要等待获取到CPU资源之后才会执行,run()是Runnable()接口中的唯一方法,覆写后的run()方法中是线程要执行的内容。
线程的终止
Java提供了强制中止线程的方法stop(),不过是已弃用Deprecated,很显然强制终止线程会造成数据安全问题。
终止线程需要开发者手动进行终止,利用interuppt()和isInterrupted()方法。
interuppt()方法不会真正的打断线程,只会为线程加上一个中断的标记,发者利用终止标记来用break或return等终止线程。
isInterrupted()判断线程是否被终止,此方法不会清除InterruptStatus,isInterrupted(boolean)如果传入参数为ture的话,会清除InterruptStatus。isInterrupted()其实就是isInterrupted(false)。
中断线程的例子:
public class InterruptDemo extends Thread {
@Override
public void run() {
while (true) {
//判断终止标志
if (this.isInterrupted()) {
System.out.println("ֹͣ停止了!");
//停止run()方法,线程终止
return;
}
System.out.println("timer=" + System.currentTimeMillis());
}
}
public static void main(String[] args) {
InterruptDemo t=new InterruptDemo();
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//设置终止标志
t.interrupt();
}
}
接下来进一步讨论下Interrupt()方法的细节:
如果线程由于wait(),join(),sleep()这些方法进入阻塞状态,调用Interrupt()方法会报错InterruptedException,InterruptStatus被清空。
上面大意是说在NIO中线程阻塞时Interrupt()的结果。
刚开始看到是IO,但是又不认识上面那两个类,仔细看才知道是NIO中的类,然后百度发现,原来传统的IO是不支持中断的,如果在IO过程中被阻塞,Interrupt()无效,这样将无法按照上述的方法终止一个线程。
只凭这一点,传统IO中的PipedIO,已经是鸡肋,看来有时间还要去看看NIO。我去,我太难了!
最后,再mark一个setPriority()方法,可设置的三种优先级:
MAX_PRIORITY 10
NORM_PRIORITY 5
MIN_PRIORITY 1
优先级就好像轮询中的权重一样,不是优先级高就一定会先获取到CPU资源,概率大而已。