Java多线程_Java多线程技能(1)

进程与线程

通过查看“Windows任务管理器”中的列表,完全可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元线程可以理解成是在进程中独立运行的子任务

使用多线程技术后,可以在同一时间运行更多不同种类的任务
在这里插入图片描述
多线程是异步的,所以千万不要把 Eclipse里代码的顺序当成线程执行的顺序,线程调用的时机是随机的。执行start()方法的顺序不代表线程启动的顺序。

使用多线程

一个进程在运行时候至少有一个线程,比如Java中的main函数。多线程的创建:

  • 继承Thread类
  • 实现Runnable接口
  • JDK 5.0新增:实现Callable接口

继承自Thread类

  • 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法。
  • 创建MyThread类的对象。
  • 调用线程对象的start()方法启动线程(启动后还是执行run方法的)。

优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。

package multiply.com.test;

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        System.out.println("MyThread");
    }
}
package multiply.com.test;

public class Run {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("运行结束!");
    }
}

运行结束!
MyThread

为什么不直接调用了run方法,而是调用start启动线程。

  • 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
  • 只有调用start方法才是启动一个新的线程执行。

把主线程任务放在子线程之前了:这样主线程一直是先跑完的,相当于是一个单线程的效果了。

package multiply.com.test;

public class MyThread extends Thread{
	private int i;
	public MyThread(int i)
	{
		super();
		this.i = i;
	}
    @Override
    public void run() {
        super.run();
        System.out.println(i);
    }
}
package multiply.com.test;

public class Run {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread(1);
        MyThread myThread2 = new MyThread(2);
        MyThread myThread3  = new MyThread(3);
        MyThread myThread4 = new MyThread(4);
        MyThread myThread5 = new MyThread(5);
        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread4.start();
        myThread5.start();
    }
}

1
2
5
4
3

实现Runnable接口

  • 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。
  • 创建MyRunnable任务对象。
  • 把MyRunnable任务对象交给Thread处理。
  • 调用线程对象的start()方法启动线程。

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

package multiply.com.test;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("运行中");
    }
}
package multiply.com.test;

public class Run {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("运行结束");
    }
}

运行结束
运行中

构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用。

实现Runnable接口(匿名内部类形式):

  • 可以创建Runnable的匿名内部类对象。
  • 交给Thread处理。
  • 调用线程对象的start()启动线程。
package com.test;

public class ThreadDemo2Other {
    public static void main(String[] args) {
        //创建Runnable的匿名内部类对象
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println("子线程1执行输出:" + i);
                }
            }
        };
        Thread t = new Thread(target);
        t.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println("子线程2执行输出:" + i);
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("子线程3执行输出:" + i);
            }
        }).start();

        for (int i = 0; i < 3; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

在这里插入图片描述

多线程的实现方案三:利用Callable、FutureTask接口实现。

  • 得到任务对象:定义类实现Callable接口,重写call方法,封装要做的事情;用FutureTask把Callable对象封装成线程任务对象。
  • 线程任务对象交给Thread处理。
  • 调用Thread的start方法启动线程,执行任务
  • 程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。
package com.test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo3 {
    public static void main(String[] args) {
        // 3、创建Callable任务对象
        Callable<String> call = new MyCallable(100);
        // 4、把Callable任务对象 交给 FutureTask 对象
        //  FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了
        //  FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
        FutureTask<String> f1 = new FutureTask<>(call);
        // 5、交给线程处理
        Thread t1 = new Thread(f1);
        // 6、启动线程
        t1.start();

        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            // 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。
            String rs1 = f1.get();
            System.out.println("第一个结果:" + rs1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            // 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。
            String rs2 = f2.get();
            System.out.println("第二个结果:" + rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    // 2、重写call方法(任务方法)
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

在这里插入图片描述
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕后去获取线程执行的结果
缺点:编码复杂一点。

实例变量与线程安全

不共享数据的情况:一共创建了3个线程,每个线程都有各自的 count变量。
在这里插入图片描述
在这里插入图片描述

package multiply.com.test;

public class MyThread extends Thread {
    private int count = 3;
    public MyThread(String s)
    {
    	super();
    	this.setName(s);//设置当前线程的名字
    }

    @Override
    public void run() {
        super.run();
        while(count>0)
        {
        	count--;
            System.out.println("由" + Thread.currentThread().getName() + "计算,count=" + count);
        }
    }
}
package multiply.com.test;

public class Run {
    public static void main(String[] args) {
        MyThread a = new MyThread("A");
        MyThread b = new MyThread("B");
        MyThread c = new MyThread("C");
        a.start();
        b.start();
        c.start();
    }
}

由C计算,count=2
由C计算,count=1
由A计算,count=2
由B计算,count=2
由A计算,count=1
由C计算,count=0
由A计算,count=0
由B计算,count=1
由B计算,count=0

共享数据的情况:同一个对象,不同的名称。

package multiply.com.test;

public class MyThread extends Thread {
    private int count = 5;
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + Thread.currentThread().getName() + "计算,count=" + count);
    }
}
package multiply.com.test;

public class Run {
    public static void main(String[] args) {
    	MyThread myThread = new MyThread();
        Thread a = new Thread(myThread, "A");
        Thread b = new Thread(myThread, "B");
        Thread c = new Thread(myThread, "C");
        Thread d = new Thread(myThread, "D");
        Thread e = new Thread(myThread, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

由A计算,count=3
由D计算,count=0
由E计算,count=0
由B计算,count=2
由C计算,count=2
多个线程同时访问,一定会出现非线程安全问题,这里就要用到线程同步,让减一操作连续。

package multiply.com.test;

public class MyThread extends Thread {
    private int count = 5;
    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println("由" + Thread.currentThread().getName() + "计算,count=" + count);
    }
}

由A计算,count=4
由D计算,count=3
由C计算,count=2
由E计算,count=1
由B计算,count=0
synchronized 可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

留意i–与System.out.println()的异常

package multiply.com.test;

public class MyThread extends Thread {
    private int count = 5;
    @Override
    synchronized public void run() {
        super.run();
        System.out.println("由" + Thread.currentThread().getName() + "计算,count=" + count--);
    }
}

本实验的测试目的是:虽然println()方法在内部是同步的,但i–的操作却是在进入println()之前发生的,所以有发生非线程安全问题的概率。

currentThread()方法

package multiply.com.test;

public class MyThread extends Thread {
	public MyThread()
	{
		System.out.println("构造方法的打印:"+Thread.currentThread().getName());
		System.out.println("this.getName():"+this.getName());
	}
    @Override
    public void run() {
        System.out.println("run方法的打印:"+Thread.currentThread().getName());
        System.out.println("this.getName():"+this.getName());
    }
}
package multiply.com.test;

public class Run {
    public static void main(String[] args) {
    	MyThread myThread = new MyThread();
    	//myThread.start();
    	myThread.run();
    }
}

构造方法的打印:main
this.getName():Thread-0
run方法的打印:main
this.getName():Thread-0

package multiply.com.test;

public class Run {
    public static void main(String[] args) {
    	MyThread myThread = new MyThread();
    	myThread.start();
    	//myThread.run();
    }
}

构造方法的打印:main
this.getName():Thread-0
run方法的打印:Thread-0
this.getName():Thread-0
MyThread.java类的构造函数是破main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是自动调用的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

身影王座

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值