一些概念
1 进程 正在运行的软件
2 单线程 一个进程只有一条执行路径
3 多线程 一个进程有多个执行路径
4 并发 同一时刻,多个指令在单个cpu上交替执行
5 并行 同一时刻 多个指令在多个cpu上同时执行
多线程的实现方式
三种实现方案:
>继承Thread类的方法实现多线程
>实现Runnable接口的方式实现多线程
>利用Callable和Funture接口的方式实现多线程
(1) 继承Thread
1 创建一个类继承Thread
2 重写Thread类的run()方法 (要执行的操作卸载run方法中)
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
3 创建子类对象
4 通过此对象调用start()方法
public class Demo1 {
public static void main(String[] args) {
/* 现在只有1个线程,任何java程序起码有个主线程 main
MyThread t1=new MyThread();并不是一个线程
只是创建了一个线程对象(线程对象和线程还是有很大区别的
对象是创建在虚拟机堆空间中的,
而线程是操作系统维护的一种资源 为什么说Thread对象代表一个线程?
因为把线程对象创建出来以后,以他为代表才能创建线程,
我们程序员不能绕开虚拟机工作,不能直接去访问操作系统),
*/
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//通过此对象调用start方法 : ①启动当前线程,② 调用当前线程的run()方法
//start() 线程对象会帮你到操作系统中发出申请能不能为我创建一个线程
//如果得到批准操作系统就创建一个新线程
//这才是创建线程 或者是启动线程
t1.start();
//这句话是在主线程中执行的
System.out.println("你好世界");
t2.start();
System.out.println("hello world");
}
}
//cup的切换是随机的 所以看到的结果每次是不一样的
run()和start()方法的区别?
如果直接调用run()方法,表示的仅仅是创建对象,用对象去调用方法,并没有开启线程
相当于是普通方法的调用
start():表示启动线程,然后由jvm调用此线程的run()方法
注意点:
(1)不能直接调用run方法的方式启动线程
(2)不能同一个线程启动两次 如果想启动两个线程就只能创建一个新的线程对象
(2)实现Runnable接口
1 创建一个类实现Runnable接口
2 重写run()方法
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
3 创建此类的对象
4 创建Thread类的对象并以此类的对象作为构造参数
5 通过Thread类的对象调用start()方法
public class Demo1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"线程一");
thread1.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
//如何再启动一个线程
Thread thread2 = new Thread(myThread);
thread2.setName("线程二");
thread2.start();
}
}
原码分析
1 使用的是 Thread中的start()方法 该方法会调用Thread类中的run()方法
Thread类中的run()原码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
如果target 不等于空就是调用 target中的run()方法
private Runnable target;//发现target是Runnable类型的
再看Thread thread1 = new Thread(myThread,"线程一"); 构造方法
//把咱自己写的MyThread对象也就是Runnable类的实现类赋值给了target
public Thread(Runnable target, String name) {
this(null, target, name, 0);
}
现在target不为null 调用target.run(); 实际会找咱重写的run方法
(3)实现callable接口
1 创建一个实现了callable接口的实现类
2 重写call() 方法 执行的错做写在其中
class MyThread implements Callable{
@Override
public Object call() throws Exception {
int num=0;
for (int i = 0; i < 100; i++) {
System.out.println(i);
num+=i;
}
return num;
}
}
3 创建实现callable 接口的实现类对象
4 将实现的类对象作为参数传递到FutureTask构造器中
5 将FutureTask对象作为参数传递到Thread类的对象的构造器中,启动线程
6 可以获取call方法的返回值
public class Demo2 {
public static void main(String[] args) {
//需要执行call方法
MyThread my =new MyThread();
//可以获取线程执行完毕的结果,也可以作为参数传递给Thread
FutureTask futureTask=new FutureTask(my);
//启动线程
new Thread(futureTask).start();
try {
//get();
//返回值即为FutureTask构造方法参callable实现类重写的call()方法的返回值
Object num = futureTask.get();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
开发中优先选择实现Runnable接口的方式
1 实现的方式没有类的单继承的限制
2 实现的方式更适合处理多个线程有共享数据的情况
联系:
Thread 实现了Runnable接口 重写了run()方法
在继承Thread类的时候 我们重写的实际上是Thread类重写的Runnable的run()方法
//我重写了你的重写
相同:都需要重写Run()方法
Thread的一些方法
(1)获取线程名字
//返回此线程的名称。 线程有默认的名字 格式 Thread-编号
public final String getName();
(2)设置线程名字
//将此线程的名称更改为等于参数name 。
public final void setName(String name)
//通过构造设置名字
public Thread(String name)
代码实现
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, ExecutionException, InterruptedException {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread("线程2");
m1.setName("线程1");
//m2.setName("线程2");
m1.start();
m2.start();
System.out.println(m1.getName());
System.out.println(m2.getName());
}
}
class MyThread extends Thread {
public MyThread() {
}
//子类不能继承父类的有参构造 需要通过super(有参)调用父类的构造方法
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+" "+i);
}
}
}
(3)获取多线程对象
Thread 中静态方法
//返回对当前正在执行的线程对象的引用。 结果就是当前正在执行的线程。
public static Thread currentThread()
Thread.currentThread().getName() 获取当前线程的名字 默认名(Thread-0/1/2...)
Thread.currentThread().setName() 设置当前线程的名字
public static void main(String[] args) {
//获取主线程名字
System.out.println(Thread.currentThread().getName());
//给主线程是指名字
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName());
}
(4)sleep
// 1秒=1000毫秒
//使当前正在执行的线程停留(暂停执行)指定的毫秒数,这取决于系统定时器和调度程序的精度和准确性。
//线程不会丢失任何显示器的所有权。
//millis - 以毫秒为单位的睡眠时间长度 不能是负数 否则报错
//通俗说法
:让当前线程睡眠指定的m毫秒数,在指定的m毫秒时间内当前线程是阻塞状态
public static void sleep(long millis)
//导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),
//这取决于系统定时器和调度器的精度和准确性。 线程不会丢失任何显示器的所有权。
//millis - 以毫秒为单位的睡眠时间长度
//nanos - 0-999999额外的纳秒睡眠
public static void sleep(long millis,int nanos)
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread("线程一");
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
try {
this.sleep(1000);//阻塞1秒
//Thread类中run方法没有抛异常 那么重写的方法也不能抛异常只能自己处理
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public MyThread(String name) {
super(name);
}
}