赞
踩
程序(program):为完成特定任务、用某种语言编写的一-组指令的集合。即指一段静态的代码,静态对象。在计算机操作系统中,程序就表现为一个可执行文件。
进程(process):程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它自身的产生、存在和消亡的过程,即进程的生命周期。
➢ 程序是静态的,进程是动态的;
➢ 每一个进程,系统是分配一个唯一id标识(pid);
➢ 进程作为系统资源分配的最小单位,系统在运行时会为每个进程分配不同的内存区域。
线程(thread): 进程可进一 步细化为线程,是-一个程序内部的一 条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。
➢ 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
➢ 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会帶来安全的隐患。
进程与线程关系图:
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。
例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员,如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他, 等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核CPU的话, 才能更好的发挥多线程的效率。(现在的服务器都是多核的)。
例如:一个Java应用程序java.exe, 其实至少有三个线程: main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
现代操作系统比如Mac OS X, UNIX, Linux, Windows等,都是支持多任务的操作系统。所谓的多任务,就是操作系统可以同时运行多个任务。
操作系统(如Windows、Linux) 的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。
这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在"同时进行”,这也就是我们所说的并发。
一般的操作系统(如Windows、Linux)对执行权限进行分级:用户态和内核态。
需要注意的是:当用户态转变为内核态时,是比较消耗资源的!!!
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片轮转调度的方式)同时执行多个任务。比如:秒杀、多个人做同一件事。
思考:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,那么为何仍需多线程呢?
多线程程序的优点:
参考代码:
package test_MultiThreading; public class ThreadTest_3 { public static void main(String[] args) { //为该分线程起个名字~~ //方式一:在下面构造一个带参数的构造函数,初始化的时候直接命名~~ MyThread_3 myThread_3 = new MyThread_3("Thread-1"); //方式二:调用setName()函数 //myThread_3.setName("分线程1"); myThread_3.start(); //给主线程起个名字~~ Thread.currentThread().setName("主线程"); for (int i=1;i<100;i++){ if (i % 2 != 0){ //Thread.currentThread().getName()--->获取当前进程名字 System.out.println(Thread.currentThread().getName()+":"+ i); } //在主线程中,当运行到i=21的时候,主线程断开,进入Thread-1线程中~~ if (i == 21){ try { myThread_3.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class MyThread_3 extends Thread{ @Override public void run(){ for (int i=1;i<100;i++){ if (i % 2 == 0){ //让进程睡眠一段时间,单位:ms try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //Thread.currentThread().getName()--->获取当前进程名字 System.out.println(Thread.currentThread().getName()+":"+ i); } //当i能够整除20的时候,释放当前线程的使用权~~ if (i%20 == 0){ Thread.currentThread().yield(); } } } //创建一个带参数的构造函数 public MyThread_3(String name){ super(name); } }
1.创建一个继承于Thread类的子类;
2.重写Thread类的run()—>将此线程需要执行的操作声明在run()函数中;
3.创建Thread类的子类的对象;
4. 通过此对象调用start()—>启动当前线程,调用当前线程的run()函数。
参考代码:
//1.创建一个继承于Thread类的子类 class MyThread extends Thread{ //2.重写Thread类的run() @Override public void run(){ for (int i=1;i<100;i++){ if (i % 2 == 0){ //Thread.currentThread().getName()--->获取当前进程名字 System.out.println(Thread.currentThread().getName()+":"+ i); } } } } public class ThreadTest { public static void main(String[] args) { //3.创建Thread类的子类的对象 MyThread myThread_1 = new MyThread(); //4. 通过此对象调用start() myThread_1.start(); //注意事项: //myThread.run(); //通过这种方式启动的线程不是多线程~~ //如果此时,我们需要再次调用myThread线程,不可以直接再次调用start(), //而是需要重新创建对象后再调用~~ MyThread myThread_2 = new MyThread(); myThread_2.start(); //测试一下多线程 for (int i=0;i<50;i++){ System.out.println(Thread.currentThread().getName()+":"+i*i); } } }
运行结果:
这个输出结果是可以看出,该程序运行是开辟了3个线程进行运行的!
注意:
参考代码:
public class ThreadTest_2 { public static void main(String[] args) { MyThread myThread_1 = new MyThread(); MyThread myThread_2 = new MyThread(); myThread_1.start(); myThread_2.start(); } } class MyThread_1 extends Thread{ @Override public void run(){ for (int i=1;i<100;i++){ if (i % 2 == 0){ //Thread.currentThread().getName()--->获取当前进程名字 System.out.println(Thread.currentThread().getName()+":"+ i); } } } } class MyThread_2 extends Thread{ @Override public void run(){ for (int i=1;i<100;i++){ if (i % 2 != 0){ //Thread.currentThread().getName()--->获取当前进程名字 System.out.println(Thread.currentThread().getName()+":"+ i); } } } }
运行结果:
在上面两个例子中,我们发现这样一个问题:我们所创建的分线程,只是使用了一次就不用了,每次使用需要创建新的对象,其实还是挺麻烦的~~那么我们就可以使用Thread的匿名子类。
//创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run(){
//具体重写内容
}
}.start();
1.创建一个实现了Runnable接口的实现类;
2.实现类去实现Runnable 中的抽象方法run();
3.创建实现类的对象;
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象;
5.通过Thread类的对象 调用start()。
参考代码:
public class ThreadTest_5 { public static void main(String[] args) { //3.创建实现类的对象 MyThread_5 myThread_5 = new MyThread_5(); //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象; Thread thread1 = new Thread(myThread_5); thread1.setName("线程1"); //5.通过Thread类的对象 调用start() thread1.start(); //再启动一个线程 Thread thread2 = new Thread(myThread_5); thread2.setName("线程2"); thread2.start(); } } //1.创建一个实现Runnable接口的实现类 class MyThread_5 implements Runnable{ //2.实现类去实现Runnable中的run()抽象方法 @Override public void run() { for (int i=0;i<100;i++){ if (i%2 == 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } }
我们会有这样一个疑问:因为调用start()方法后,线程启动,随后会执行当前线程内的run()函数,但是我们当前线程是Thread,为什么执行下来是调用的下面Runnable中的抽象run()方法呢?
通过阅读源码可以发现,Thread类中run()函数中的target是一个Runnable类型的值~~故而可以执行到Runnable节课欧中的抽像方法!
参考代码:
public class ThreadTest_4 { public static void main(String[] args) { MyThread_4 window1 = new MyThread_4(); MyThread_4 window2 = new MyThread_4(); MyThread_4 window3 = new MyThread_4(); window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); window1.start(); window2.start(); window3.start(); } } class MyThread_4 extends Thread{ private static int ticket = 100; //注意这里必须要用static修饰 @Override public void run(){ while(true){ if (ticket > 0){ System.out.println(getName() + ":卖票,票号为:"+ ticket); ticket--; }else{ break; } } } }
运行结果:
我们会发现,运行结果中有3个票号为100的票,这是因为涉及到线程安全问题~ ~后面会讲如何解决!
参考代码:
public class ThreadTest_6 { public static void main(String[] args) { MyThread_6 window = new MyThread_6(); Thread window1 = new Thread(window); Thread window2 = new Thread(window); Thread window3 = new Thread(window); window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); window1.start(); window2.start(); window3.start(); } } class MyThread_6 implements Runnable{ private int ticket = 100; //注意这里没有用static修饰 @Override public void run() { while(true){ if (ticket > 0){ System.out.println(Thread.currentThread().getName() + ":卖票,票号为:"+ ticket); ticket--; }else{ break; } } } }
运行结果:
思考一下,这道例题用方式一和方式二实现的最大区别在于定义stick总数的时候有没有用static修饰?
方式一必须要用是因为方式一种创造出了三个对象,那么如果不用static修饰,每个创造对象,stick就会+100,最后就有300张stick。
方式二不需要用static修饰stick,是因为方式二中只创造了一个对象,这一个对象放到了三个构造器中,这三个线程只使用了一个对象。
// 使用匿名类创建 Runnable 子类对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//具体重写内容
}
});
综合上面两个方式及其例题,我们发现,线程的创建和使用中,方式二更好一下~~
方式一的局限性在于java的单继承特性,拿购票窗口举例,如果使用方法一的方式实现多线程,现实中的购票窗口还有其他特性,也许还需要继承其他父类,那么方法一下面它就无法继承其他类了,所以还是使用方式二实现Runnable接口比较方便。
方式二能够更好的处理多个线程有共享数据的情况,例如购票窗口中的总票数和窗口之间的关系,方式二在创建中就不需要按每个窗口来创建对象,只需要创建一个对象,然后调用好对象的构造器就可实现多个对象的创建。
方式一和方式二之间的联系就在于两种方法都需要重写run()函数,将线程要执行的逻辑声明在run()函数中。
public class FlagStopThread { //使用标志位isQuit标识是否继续执行线程中的任务,true中断,false继续执行 private static class MyRunnable implements Runnable { public volatile boolean isQuit = false; @Override public void run() { while (!isQuit) { System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+ ":好!停止转账!"); } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。"); thread.start(); Thread.sleep( 1000); System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!"); target.isQuit = true; } }
public class InterruptThread { private static class MyRunnable implements Runnable { @Override public void run() { // 两种方法均可以 while (!Thread.interrupted()) { //while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(Thread.currentThread().getName()+ ": 有内鬼,终止交易!"); break; } } System.out.println(Thread.currentThread().getName()+ ": 好!停止转账!"); } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。"); thread.start(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!"); thread.interrupt(); } }
重点说明下第二种方法:
通过 thread 对象调用 interrupt() 方法通知该线程停止运行。thread 收到通知的方式有两种:
第二种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
1.时间片
2.抢占式,高优先级的线程抢占CPU
~~未完待续
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。