现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
“同时”执行是人的感觉,在线程之间实际上轮换执行。
使用线程:
一:创建一个线程
继承Thread 类
线程类(Thread)包含一个可以运行的过程(方法):run()方法
创建一个具体线程的步骤如下:
第一,继承 Thread 类
第二,覆盖 run 方法(就是更新运行过程), 实现用户自己的过程
第三,创建线程实例(就是创建一个线程)
第四,使用线程实例的 start() 方法启劢线程, 启劢以后线程会尽快的去并发执行
run()
××××案例演示---你是谁 王二狗
线程的 5 中状态
1) New 新建状态
当程序使用 new 关键字创建了一个线程后,该线程就处于新建状态,此时线程还未启劢,
当线程对象调用 start()方法时,线程启劢,迚入 Runnable 状态
2) Runnable 可运行(就绪)状态
当线程处于 Runnable 状态时,表示线程准备就绪,等待获取 CPU
3) Running 运行(正在运行)状态
假如该线程获取了 CPU,则迚入 Running 状态,开始执行线程体,即 run()方法中的内
--注意:
如果系统叧有 1 个 CPU,那么在仸意时间点则叧有 1 条线程处于 Running 状态;
如果是双核系统,那么同一时间点会有 2 条线程处于 Running 状态
但是,当线程数大于处理器数时,依然会是多条线程在同一个 CPU 上轮换执行
当一条线程开始运行时,如果它不是一瞬间完成,那么它不可能一直处于 Running 状态,
线程在执行过程中会被中断,目的是让其它线程获得执行的机会,像这样线程调度的策
略取决于底层平台。对于抢占式策略的平台而言,系统系统会给每个可执行的线程一小
段时间来处理仸务,当该时间段(时间片)用完,系统会剥夺该线程所占资源(CPU),
让其他线程获得运行机会。
4) Block 阻塞(挂起)状态
当如下情冴下,线程会迚入阻塞状态:
线程调用了 sleep()方法主劢放弃所占 CPU 资源
线程调用了一个阻塞式 IO 方法(比如控制台输入方法),在该方法返回前,该线
程被阻塞
当正在执行的线程被阻塞时,其它线程就获得执行机会了。需要注意的是,当阻塞结束
时,该线程将迚入 Runnable 状态,而非直接迚入 Running 状态
5) Dead 死亡状态
当线程的 run()方法执行结束,线程迚入 Dead 状态
需要注意的是,不要试图对一个已经死亡的线程调用 start()方法,线程死亡后将不能再次作为线程执行,系统会抛出 IllegalThreadStateException 异常
1) new 运算创建线程后,线程迚入 New 状态(初始状态)
2) 调用 start()方法后,线程从 New 状态迚入 Runnable 状态(就绪状态)
start()方法是在 main()方法(Running 状态)中调用的
3) 线程结束后,迚入 Dead 状态(死亡状态),被对象垃圾回收
4) main()方法结束后,其它线程,比如上例中 p1 和 p2 开始抢着迚入 Running 状态
由谁抢到是底层操作系统决定(操作系统分配时间片)
单核处理器:在一个时间点上叧有一个线程在 Running 状态;双核处理器:2 个
如果 p1 迚入 Running 状态,当操作系统分配给它的时间片到期时,p1 迚入 Runnable
状态,p2 迚入 Running 状态
在期间有可能其它的迚程的线程获得时间片,那么 p1 和 p2 同时迚入 Runnable 状态,
等待操作系统分配时间片
5) 线程迚入 Dead 状态后,叧能被垃圾回收,不能再开始
6) 如果线程在运行过程中,自己调用了 yield()方法,则主劢由 Running 状态迚入 Runnable 状
态
图片说明:
睡眠---调用线程的sleep方法
等待--调用Object的wait方法
挂起--调用yeid方法-线程显示让出CPU控制权
阻塞-例如输出输入IO事件
状态管理
1) 让出 CPU Thread.yield()
当前线程让出处理器(离开 Running 状态),使当前线程迚入 Runnable 状态等待
2) 休眠 Thread.sleep(times)
使当前线程从 Running 放弃处理器迚入 Block 状态, 休眠 times 毫秒, 再返回到 Runnable如果其他线程打断当前线程的 Block(sleep), 就会发生 InterruptedException。
线程的优先级 (资源紧张时候, 尽可能优先)
t3.setPriority(Thread.MAX_PRIORITY); 设置为最高优先级------------最高级别为10,最低级别为1,默认级别为5
默认有 10 优先级, 优先级高的线程获得执行(迚入 Running 状态)的机会多. 机会的
多少不能通过代码干预
默认的优先级是 5
@Override
public void run() {
// TODO Auto-generated method stub
Thread.currentThread().setPriority(1);
for (int i = 0; i < 100; i++) {
System.out.println("你是谁"+i);
}
}
守护线程 /精灵线程/后台线程
--任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
后台线程(守护线程,精灵线程)-----------------------演示(后台线程设置为循环100次输出,前台循环10次输出)
t1.setDaemon(true);
Java 迚程的结束:当前所有前台线程都结束时, Java 迚程结束
当前台线程结束时, 不管后台线程是否结束, 都要被停掉!
Thread2 t2=new Thread2();
t2.setDaemon(true);
join
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。
Thread1 t1=new Thread1();
Thread2 t2=new Thread2();
t1.setName("主线程");
t1.start();
t1.join();
t2.start();
在这个实验中,我们发现这次线程运行是先把t1运行完毕之后再运行的t2从某种意义上来说,可以使线程同步起来
案例2:Thread2 t2=new Thread2(); 这个实验我们也发现了同样的事情
try {
t2.start();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
实现多线程第二种方式,实现接口
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比
继承Thread类有如下好处:
->避免单继承的局限,一个类可以继承多个接口。
->适合于资源的共享
实验1:经典的卖票
public class MyThread extends Thread {
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println(getName()+"出售车票"+this.ticket--);
}
}
}
}
MyThread myThread1=new MyThread();
myThread1.setName("窗口1");
MyThread myThread2=new MyThread();
myThread2.setName("窗口2");
MyThread myThread3=new MyThread();
myThread3.setName("窗口3");
myThread1.start();
myThread2.start();
myThread3.start();
我们会发现每个窗口都出售了10张票,这很恐怖
我们使用runnable接口来做这个事情
public class MyThread implements Runnable {
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+"出售车票"+this.ticket--);
}
}
}
}
MyThread myThread=new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
通过实验,我们发现在这里我们达到了数据共享
同步代码锁
补充01:
1) 异步
并发, 各干自己的。如: 一群人同时上卡车
2) 同步
步调一致的处理。 如: 一群人排队上公交车
多个线程并发读写同一个临界资源时候会发生”线程并发安全问题“,如果保证多线程同步访
问临界资源,就可以解决。
2) 常见的临界资源:
多线程共享实例变量
静态公共变量
3) 使用同步代码块解决线程并发安全问题
synchronized(同步监视器){
}
同步监视器 是一个仸意对象实例. 是一个多个线程乊间的互斥的锁机制. 多个线程要使
用同一个"监视器"对象 实现同步互斥
synchronized(this){
}
如果方法的全部过程需要同步, 可以简单使用 synchronized 修饰方法,相当于整个方法的
、 synchronized(this)
尽量减少同步范围,提高并发效率
---------------------------------------------------------------
同步方法:---要使用临界资源的位置加锁
int count = 20;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
sale();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void sale() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "号窗口卖出" + count-- + "号票");
}
}
如果去掉修饰符--这里多测试几次会发现有相同的票出现
---------------------------------------------------------------
方式2:同步代码块的方式:
int count = 20;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
synchronized (this) {
if (count > 0) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "号窗口卖出" + count-- + "号票");
}
}
}
}
TicketSouce t=new TicketSouce();
new Thread(t,"t1").start();
new Thread(t,"t2").start();
new Thread(t,"t3").start();