多线程介绍
进程和线程
进程和线程都是实现并发的基本单位。
进程 :进程是指程序运行的一生,它包括了代码的加载、代码的执行、代码执行完毕,进程结束。对于电脑来说,几核的电脑就相当于有几个脑子,每一个进程都能获取CPU的时间片。每一个进程都有自己的独立的储存空间
线程 :线程比进程的执行单位更小,多线程是指在一个进程下面进行着多个线程,这些线程共享一个进程空间并且同时运行。所有线程可以溜溜使用CPU的使用权,但也可以设置调度机制,把一些线程设置优先级更高,这样可以使这个线程更加容易的抢到CPU的使用权。
同步和异步
同步: 程序排队执行,特点: 效率低但是安全
异步: 程序同时执行,特点: 效率高但是不安全,极容易发生数据的错乱
并发和并行
并发: 指两个或者多个事件在同一个时间段内发生
并行: 指两个或者多个事件同时发生
多线程的三种实现方式
Thread类:
Thread类的特点就在于线程对象是交错运行的,谁抢到了就让谁运行,所以运行的过程是无序的,每次的结果可能都不一样。
第一步: 继承Thread类;第二步: 重写run方法;第三步: 调用start方法运行程序
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
for (int i = 0; i < 5; i++) {
System.out.println("冬瓜" + i);
}
}
static class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("馒头" + i);
}
}
}
运行结果:
Runnable接口:
第一步: 创建一个类来实现Runnable接口;第二步:创建Runnable类的实例作为Thread构造参数,构造Thread类实例;第三步: 调用Thrad的start方法;第四步: 多线程代码最终都是通过调用Thread的start方法来运行
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
for(int i=0;i<3;i++){
System.out.println("慢头"+i);
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<3;i++) {
System.out.println("茄子"+i);
}
}
}
运行结果:
Callable接口
Callable是一个带返回值的多线程接口
第一步: 创建Callable类实现接口; 第二步: 以该类实例作为FutureTask构造参数,构造FutureTask类实例;第三步: 以该FutureTask类实例作为Thread构造参数,构造Thread类实例;第四部: 调用Thrad的start方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
Integer j = task.get();
System.out.println(j);
for (int i=0;i<3;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implements Callable{
@Override
public Object call() throws Exception {
Thread.sleep(1000);
return 123;
}
}
运行结果:
联系:
通过上面的代码,我们可以看出Thread接口和Runnable接口都可以实现多线程,Thread虽然是Runnable接口下的子类,多线程靠的就是Runnable接口里的run()方法来实现的。
区别:
Thread类与Runnable接口
如果一个类继承 Thread类,会有线程不安全的问题,不适合于多个线程数据共享,而 Runnable 接口,就可以方便的实现数据共享。
Callable接口与Runnable接口
Callable接口是有返回值的,而Runnabale接口没有返回值。Callable接口里的call()方法可以抛出异常,而Runnable不能抛出异常,但可以自己定义异常。
多线程的操作方法
设置和获取线程的名字:
public static void main(String[] args) {
MyThread t = new MyThread();
new Thread(t).start();
new Thread(t).start();
}
static class MyThread implements Runnable {
@Override
public void run() {
for (int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
运行结果
线程休眠:
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep()即可实现休眠。
public static void main(String[] args) {
MyThread t = new MyThread();
new Thread(t).start();
}
static class MyThread implements Runnable {
@Override
public void run() {
for (int i=0;i<3;i++){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+i);
}
}
}
运行结果:
(这里是每个线程都会休眠3秒钟)
线程的强制运行:
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行的时候,其他线程必须等待此线程完成之后才可以继续执行。
public static void main(String[] args) throws InterruptedException {
new MyThread("新线程").start();
for (int i = 0; i < 10; i++) {
if (i == 5) {
MyThread t = new MyThread("关系户");
t.start();
//main线程调用了t线程的join()方法,所以他必须等待t执行结束后才会向下执行.
t.join();
}
}
}
public static class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
线程的礼让:
Thread类中提供了一种礼让方法,调用yield() 方法来实现给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅只是一种简易,没有任何强制力来保证当前线程会将资源礼让。
yield()方法使具有同样优先级的线程有进入运行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。
(对于支持多任务的操作系统来说,不需要调用yeild() 方法,因为操作系统会为线程自动分配CPU时间片来执行。)
线程中断:
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
守护线程:
用户线程:当一个进程不包含任何用户线程时,进程结束。
守护线程:守护线程是为了守护用户线程,当最后一个用户线程结束后所有守护线程自动死亡。
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
//在启动之前设置守护线程
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
try {
System.out.println(Thread.currentThread().getName() + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
线程安全问题:
在多线程的执行中,经常会有多个线程勇同时对一个变量进行操作,就会出现数据不符合常理的问题,比如在if判断里面控制变量大于0,但往往输出的数字会出现-1、-2,这是在程序运行中不允许出现的问题。
原因在于第一个线程进入了判断但还没有运行完毕,第二个线程、第三个线程也进来办事了,这时候三个线程都进入了if语句,把数据进行了一顿安排,这时候就会出现线程不安全的问题。
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int t = 10;
@Override
public void run() {
while (t>0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票中");
t--;
System.out.println("售票成功,还剩:" + t);
}
}
}
运行结果:
公平锁和不公平锁
公平锁就是先来先运行,Java默认方法都是是不公平锁,即谁抢到算谁的。
可以用显式锁:private lock = new ReetrantLock(); 传入true表示为公平锁,false为不公平锁
后台线程:
在 Java 程序中,只要前台还有一个线程在运行,那么整个 Java 的进程就不会消失,使用 setDaemon() 方法可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行。
线程池
在线程运行中,最费时的是创建线程和关闭线程,如果这些步骤多了,会大大的浪费时间,降低了效率。线程池的创建,会直接提供多个线程,可以反复使用。在不能扩容的线程池中,程序没有线程执行时,会进行排队。在能扩容的线程池中,会进行扩容来保证任务的正常运行,一个线程长时间不用会被删除。
线程的六种状态
创建状态
在程序中调用构造方法创建了一个线程对象后,这个新创建出的线程对象便处于新建状态,但还需要调用Thread类的方法来使线程变成可运行状态。 Thread thread = new Thread()
就绪状态
创建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,准备着 CPU 的资源来完成使命,这个时候就称线程在就绪状态。
运行状态
当就绪状态被调用并获得处理器资源完成使命的时候,线程就进入了运行状态。此时,自动调用该线程对象根据需求被重写过的run() 方法。
阻塞状态
一个正在执行的线程在某些特殊情况下,如被人为调用sleep()、suspend()、wait()使线程进入挂起状态或需要执行输入操作时,会让 CPU 暂时中止自己的执行,进入阻塞状态。发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程就会转变成就绪状态。
死亡状态
线程调用 stop() 方法强制结束线程或 run() 方法执行结束后,就会变成死亡状态。处于死亡状态的线程不具有继续运行的能力。
我们在运行程序的时候,至少会启动两条线程,一个是main() 方法的线程,另一个就是垃圾回收器GC。
Lambda表达式:
函数式编程,在t2里面直接写入方法,调用start()方法,两种方式结果都一样,但Lanbda方法代码量更少。
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
Thread t2 = new Thread(() -> System.out.println("冬瓜!"));
t2.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("馒头!");
}
}
运行结果:
()方法,两种方式结果都一样,但Lanbda方法代码量更少。
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
Thread t2 = new Thread(() -> System.out.println("冬瓜!"));
t2.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("馒头!");
}
}
运行结果: