线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能
。
什么是多线程呢?即就是一个程序中有多个线程在同时执行。
通过下图来区别单线程程序与多线程程序的不同:
? 单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如,去网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。
? 多线程程序:即,若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网。
? 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
? 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
原因是:jvm启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程。当程序的主线程执行时,如果遇到了循环而导致程序在指定位置停留时间过长,则无法马上执行下面的程序,需要等待循环结束后能够执行。
那么,能否实现一个主线程负责执行其中一个循环,再由另一个线程负责其他代码的执行,最终实现多部分代码同时执行的效果?
能够实现同时执行,通过Java中的多线程技术来解决该问题。
? 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法。
? 另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。
SubThread类
package cn.itcast.thread;
public class SubThread extends Thread {
//继承Thread,重写方法run()
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
System.out.println("run" + i);
}
}
}
demo测试类
package cn.itcast.thread;
//如何创建和启动线程
public class Demo {
public static void main(String[] args) {
SubThread st = new SubThread();
st.start();//start()方法是父类的,必须调start(),jvm开启新线程才调用了run,直接手动手动调用run()是不对的,这样的话还是个单线程程序
for (int i = 0; i < 50; i++) {
System.out.println("main" + i);
}
}
}
结果:(每次运行都会变化)
main0
main1
main2
main3
run0
main4
main5
main6
main7
main8
main9
main10
main11
main12
main13
main14
run1
run2
run3
run4
run5
run6
run7
run8
run9
run10
run11
run12
run13
run14
run15
run16
run17
run18
run19
run20
run21
run22
run23
run24
run25
run26
run27
run28
run29
run30
run31
run32
run33
run34
run35
run36
run37
run38
run39
run40
run41
run42
run43
run44
run45
run46
run47
run48
run49
main15
main16
main17
main18
main19
main20
main21
main22
main23
main24
main25
main26
main27
main28
main29
main30
main31
main32
main33
main34
main35
main36
main37
main38
main39
main40
main41
main42
main43
main44
main45
main46
main47
main48
main49
思考:线程对象调用 run方法和调用start方法区别?
线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。
我们为什么要继承Thread类,并调用其的start方法才能开启线程呢?
继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?如下代码:
Thread t1 = new Thread();
t1.start();//这样做没有错,但是该start调用的是Thread类中的run方法,而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。
创建线程的目的是什么?
是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。
对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。
Thread类run方法中的任务并不是我们所需要的,只有重写这个run方法。既然Thread类已经定义了线程任务的编写位置(run方法),那么只要在编写位置(run方法)中定义任务代码即可。所以进行了重写run方法动作。
? Thread.currentThread()获取当前线程对象
? Thread.currentThread().getName();获取当前线程对象的名称
通过结果观察,原来主线程的名称:main;自定义的线程:Thread-0,线程多个时,数字顺延。如Thread-1......
异常看名字:
NameThread类
package cn.itcast.threadname;
public class NameThread extends Thread {
public void run() {
System.out.println(0/0);
}
}
ThreadDemo类
package cn.itcast.threadname;
public class ThreadDemo {
public static void main(String[] args) {
NameThread nt = new NameThread();
//nt.run();
/*
*
* Exception in thread "main" java.lang.ArithmeticException: / by zero
at cn.itcast.threadname.NameThread.run(NameThread.java:5)
at cn.itcast.threadname.ThreadDemo.main(ThreadDemo.java:7)
*/
nt.start();
/*
*
* Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at cn.itcast.threadname.NameThread.run(NameThread.java:5)
*/
}
}
看线程名字:getName();
package cn.itcast.threadname;
public class NameThread extends Thread {
public void run() {
//System.out.println(0/0);
System.out.println(getName());
}
}
通用得到线程名字
System.out.println(Thread.currentThread().getName());
改线程名,先改名,后开启线程
nt.setName("hah");
子类构造器改名
public NameThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
NameThread nt = new NameThread("hah");
sleep是静态方法
package cn.itcast.sleep;
public class Demo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
Thread.sleep(50);//sleep是静态方法
System.out.println(i);
}
}
}
多线程实现接口方式
RunnableDemo类
package cn.itcast.runnable;
public class RunnableDemo {
public static void main(String[] args) {
SubRunnable sr = new SubRunnable();
Thread t = new Thread(sr);
t.start();
for (int i = 0; i < 50; i++) {
System.out.println("main--" + i);
}
}
}
SubRunnable类
package cn.itcast.runnable;
public class SubRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
System.out.println("run--" + i);
}
}
}
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。
实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法,将线程任务代码定义到run方法中。
创建Thread类的对象,只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
1.6.2 实现Runnable的好处
第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
匿名内部类实现多线程
package cn.itcast.anonymous;
public class Demo {
public static void main(String[] args) {
new Thread(){
public void run() {
System.out.println("---");
}
}.start();
new Thread(new Runnable(){
public void run(){
System.out.println("0000000");
}
}).start();
}
}
实现线程池
package cn.itcast.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//使用jdk1.5新特性,实现线程池,使用工厂类Executor中的静态方法创建线程对象,指定线程的个数
public class ThreadPoolDemo {
public static void main(String[] args) {
//调用工厂类创建线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(2);
threadPool.submit(new ThreadPoolRunnable());
threadPool.submit(new ThreadPoolRunnable());
threadPool.submit(new ThreadPoolRunnable());
threadPool.shutdown();
}
}
package cn.itcast.threadpool;
public class ThreadPoolRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("线程提交的任务" + Thread.currentThread().getName());
}
}
实现Callable接口的多线程,可以有返回值,可以抛异常
package cn.itcast.callable;
import java.util.concurrent.Callable;
public class CallableDemo implements Callable<String>{
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "abc";
}
}
package cn.itcast.callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<String> submit = es.submit(new CallableDemo());
System.out.println(submit.get());
}
}
案例:用多线程计算求和
package cn.itcast.casedemo;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CaseDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(new SumCallable(100));
Future<Integer> f2 = pool.submit(new SumCallable(200));
System.out.println(f1.get() + "--------" + f2.get());
pool.shutdown();
}
}
package cn.itcast.casedemo;
import java.util.concurrent.Callable;
public class SumCallable implements Callable<Integer>{
private int a;
public SumCallable(int a) {
this.a = a;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= a; i++) {
sum += i;
}
return sum;
}
}
注意call是没有参数的,传参只能通过构造函数.