1 线程的概述
进程:正在运行的程序,负责了这个程序的内存空间分配,代表了内存中的执行区域。
线程:就是在一个进程中负责一个执行路径。
多线程:就是在一个进程中多个执行路径同时执行。
图上的一键优化与垃圾清除同时在运行,在一个进程中同时在执行了多个任务。
假象:
电脑上的程序同时在运行。“多任务”操作系统能同时运行多个进程(程序)——但实际是由于CPU分时机制的作用,使每个进程都能循环获得自己的CPU时间片。但由于轮换速度非常快,使得所有程序好象是在“同时”运行一样。
多线程的好处:
1. 解决了一个进程里面可以同时运行多个任务(执行路径)。
2. 提供资源的利用率,而不是提供效率。
多线程的弊端:
1. 降低了一个进程里面的线程的执行频率。
2. 对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。
3. 公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,发生线程安全问题。
4. 线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
2 创建线程的方式
2.1 创建线程的方式一
1. 继承Thread类
getName()是获取线程的名字。
执行后的效果:
问题: 先按照顺序运行完了张三,然后接着再按照顺序运行完李四,我们想要的效果是张三和李四做资源的争夺战,也就是先是张三然后李四,没有顺序的执行。这就证明多线程没有起到效果。
2. 需要复写run方法,把要执行的任务放在run方法中。
运行效果:
问题: 先按照顺序运行完了张三,然后接着再按照顺序运行完李四,我们想要的效果是张三和李四做资源的争夺战,也就是先是张三然后李四,没有顺序的执行。这就证明多线程没有起到效果。
3. 调用start()方法启动线程
效果:
达到了我们预期的效果。
线程的使用细节:
1. 线程的启动使用父类的start()方法
2. 如果线程对象直接调用run(),那么JVN不会当作线程来运行,会认为是普通的方法调用。
3. 线程的启动只能由一次,否则抛出异常
4. 可以直接创建Thread类的对象并启动该线程,但是如果没有重写run(),什么也不执行。
5. 匿名内部类的线程实现方式
2.2 线程的状态
创建:新创建了一个线程对象。
可运行:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。
运行:就绪状态的线程获取了CPU执行权,执行程序代码。
阻塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡:线程执行完它的任务时。
2.3 常见线程的方法
Thread(String name) 初始化线程的名字
getName() 返回线程的名字
setName(String name) 设置线程对象名
getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
setPriority(int newPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
currentThread() 返回CPU正在执行的线程的对象
class ThreadDemo1 extends Thread { public ThreadDemo1(){
} public ThreadDemo1( String name ){ super( name ); }
public void run(){ int i = 0; while(i < 30){ i++; System.out.println( this.getName() + " "+ " : i = " + i); System.out.println( Thread.currentThread().getName() + " "+ " : i = " + i); System.out.println( Thread.currentThread() == this ); System.out.println( "getId()" + " "+ " : id = " + super.getId() ); System.out.println( "getPriority()" + " "+ " : Priority = " + super.getPriority() ); } } } class Demo3 { public static void main(String[] args) { ThreadDemo1 th1 = new ThreadDemo1("线程1"); ThreadDemo1 th2 = new ThreadDemo1("线程2"); // 设置线程名 th1.setName( "th1" ); th2.setName( "th2" ); // 设置线程优先级 1 ~ 10 th1.setPriority( 10 ); th2.setPriority( 7 ); // 查看SUN定义的线程优先级范围 System.out.println("max : " + Thread.MAX_PRIORITY ); System.out.println("min : " + Thread.MIN_PRIORITY ); System.out.println("nor : " + Thread.NORM_PRIORITY ); th1.start(); th2.start(); System.out.println("Hello World!"); } } |
练习:模拟卖票
存在问题:这时候启动了四个线程,那么tickets是一个成员变量,也就是在一个线程对象中都维护了属于自己的tickets属性,那么就总共存在了四份。
解决方案一:tickets使用staitc修饰,使每个线程对象都是共享一份属性。
解决方案2:编写一个类实现Runnable接口。
2.4 创建线程的方式二
创建线程的第二种方式.使用Runnable接口.
该类中的代码就是对线程要执行的任务的定义.
1:定义了实现Runnable接口
2:重写Runnable接口中的run方法,就是将线程运行的代码放入在run方法中
3:通过Thread类建立线程对象
4:将Runnable接口的子类对象作为实际参数,传递给Thread类构造方法
5:调用Thread类的start方法开启线程,并调用Runable接口子类run方法
为什么要将Runnable接口的子类对象传递给Thread的构造函数,因为自定义的run方法所属对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法
package cn.itcast.gz.runnable; public class Demo1 { public static void main(String[] args) { MyRun my = new MyRun(); Thread t1 = new Thread(my); t1.start(); for (int i = 0; i < 200; i++) { System.out.println("main:" + i); } } } class MyRun implements Runnable { public void run() { for (int i = 0; i < 200; i++) { System.err.println("MyRun:" + i); } } } |
理解Runnable:
Thread类可以理解为一个工人,而Runnable的实现类的对象就是这个工人的工作(通过构造方法传递).Runnable接口中只有一个方法run方法,该方法中定义的事会被新线程执行的代码.当我们把Runnable的子类对象传递给Thread的构造时,实际上就是让给Thread取得run方法,就是给了Thread一项任务.
买票例子使用Runnable接口实现
在上面的代码中故意照成线程执行完后,执行Thread.sleep(100),以让cpu让给别的线程,该方法会出现非运行时异常需要处理,这里必须进行try{}catch(){},因为子类不能比父类抛出更多的异常,接口定义中没有异常,实现类也不能抛出异常。
运行发现票号出现了负数,显示了同一张票被卖了4次的情况。
出现了同样的问题。如何解决?
class MyTicket implements Runnable { int tickets = 100; public void run() { while (true) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口@销售:" + tickets + "号票"); tickets--;
} else { System.out.println("票已卖完。。。"); break; } } } } public class Demo6 { public static void main(String[] args) { MyTicket mt = new MyTicket(); Thread t1 = new Thread(mt); Thread t2 = new Thread(mt); Thread t3 = new Thread(mt); Thread t4 = new Thread(mt); t1.start(); t2.start(); t3.start(); t4.start(); } } |