多线程快速入门

简介: 多线程快速入门

 开启多线程的方法

第一种:继承Thread

继承Thread类,重写run方法

public class MyThread extends Thread {
    @Override
    public void run() {
        // 输出100次helloworld
        for (int i = 0; i < 100; i++){
            System.out.println(getName() + "HelloWorld");
        }
    }
}

image.gif

第二种:实现Runable

实现Runnable,重写run方法

public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            // 获取到当前线程的对象
            /*Thread t = Thread.currentThread();
            System.out.println(t.getName() + "HelloWorld");*/
            System.out.println(Thread.currentThread().getName() + "HelloWorld");
        }
    }
}

image.gif

第三种:实现Callable(有返回值)

实现Callable,重写call(是有返回值的,表示多线程运行的结果)

import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 求1~100之间的和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum = sum + i;
        }java
        return sum;
    }
}

image.gif

启动多线程的方法

第一种:Thread启动方法

1、创建子类对象,这里的子类叫MyThread(MyThread:多线程的类名)

2、开启线程

public class ThreadDemo {
    public static void main(String[] args) {
    // 创建子类对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        // 给线程设置名称
        t1.setName("线程1");
        t2.setName("线程2");
        // 开启线程
        t1.start();
        t2.start();
    }
}

image.gif

第二种:Runnable启动方法

1、创建子类对象,这里的子类叫MyRun(MyRun:多线程的类名)

2、创建多线程对象Thread

3、开启线程

public class ThreadDemo {
    public static void main(String[] args) {
        // 创建MyRun的对象
        // 表示多线程要执行的任务
        MyRun mr = new MyRun();
        // 创建多线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        // 给线程设置名称
        t1.setName("线程1");
        t2.setName("线程2");
        // 开启线程
        t1.start();
        t2.start();
    }
}

image.gif

第三种:Callable启动方法

1、创建子类对象,这里子类叫MyCallable(MyCallable:多线程的类名)

2、创建FutureTask对象(管理多线程运行的结果)

3、创建多线程对象Thread,

4、启动线程

5、获取多线程运行结果

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建MyCallable对象(表示多线程要执行的结果)
        MyCallable mc = new MyCallable();
        // 创建FutureTask对象(作用管理多线成运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建线程的对象java
        Thread t1 = new Thread(ft);
        // 启动线程
        t1.start();
        // 获取多线程运行的结果
        Integer result = ft.get();  // 有异常直接抛出
        System.out.println(result);
    }
}

image.gif

多线程的常用方法

1、设置线程的名称void setName(String name)

[子类对象名].setName(String name) 设置现成的名字(构造方法也可以设置名字)

细节:如果没有给线程设置名字,线程也是有默认的名字的——格式:Thread-X(X序号,从0开始)

// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
    // 创建子类对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        // 给线程设置名称
        t1.setName("线程1");
        t2.setName("线程2");java
        // 开启线程
        t1.start();
        t2.start();
    }
}

image.gif

2、返回线程的名称String getName()

String getName() 返回此线程的名称(仅限于继承Thread的类中使用)

// 子类 线程类
public class MyThread extends Thread{
    public MyThread() {
    }
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(getName());  // 返回此线程的名称
    }
}

image.gif

// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
        //1、创建线程的对象
        MyThread t1 = new MyThread("飞机"); // 通过构造参数设置线程名称
        MyThread t2 = new MyThread("坦克");
        // 2、开启线程
        t1.start();
        t2.start();
    }
}

image.gif

3、获取当前线程的对象static Thread currentThread()

Thread.currentThread(); 获取当前线程的对象(哪条线程执行到这个方法,此时获取的就是哪条线程的对象)

细节:当JVM虚拟机启动之后,会自动的启动多条线程

         其中有一条线程就叫做main线程

         他的作用就是去调用main方法,并执行里面的代码

         以前写的所有的代码,都是运行在main线程当中

// 测试类
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name);
        String nameOne = Thread.currentThread.getName();  // 获取当前线程对象的名字
        System.out.println(nameOne);
    }
}

image.gif

4、让线程休眠指定的时间,单位为毫秒static void sleep(long time)

Thread.sleep(1000); 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间

细节:单位是毫秒:1 秒 = 1000毫秒

// 子类 线程类
public class MyThread extends Thread{
    public MyThread() {
    }
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                // 让线程休眠1000毫秒
                Thread.sleep(1000); // 这个地方会有异常,要么try——catch,要么直接抛出
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(getName() + " " + i);
        }
    }
}

image.gif

// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
        //1、创建线程的对象
        MyThread t1 = new MyThread("飞机"); // 通过构造参数设置线程名称
        MyThread t2 = new MyThread("坦克");
        // 2、开启线程
        t1.start();
        t2.start();
    }
}

image.gif

5、设置线程的优先级setPriority(int newPriority) 获取线程的优先级 final int getPrioriry()

// 子类 线程类
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

image.gif

[子类对象名].setPriority(1~10); 设置线程的优先级

[子类对象名].getPrioriry(); 获取线程的优先级

细节:默认线程优先级是:5

// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
        // 创建线程对象
        Thread t1 = new Thread(mr,"飞机");
        Thread t2 = new Thread(mr,"坦克");
        // 获取线程优先级
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        // 设置线程优先级(1~10,数字越大越容易抢到执行权)
        t1.setPriority(1);
        t2.setPriority(10);
        // 启动线程
        t1.start();
        t2.start();
    }
}

image.gif

6、设置为守护线程final void setDaemon(bollean on)

当其他非守护线程执行完毕之后,守护线程会陆续的结束

当qq聊天的时候,同时给好友发送文件,当聊天界面关闭了,文件也没必要继续发送了(发送文件会停止)

// 子类 线程类
public class MyThread01 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}

image.gif

// 子类 线程类
public class MyThread02 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}

image.gif

[子类对象名].setDaemon(true); 设置为守护线程

// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
        // 创建子类对象
        MyThread01 t1 = new MyThread01();
        MyThread02 t2 = new MyThread02();
        // 设置线程名称
        t1.setName("女神");
        t2.setName("备胎");
        // 把第二个线程设置为守护线程()
        t2.setDaemon(true);
        // 启动线程
        t1.start();
        t2.start();
    }
}

image.gif

7、出让线程/礼让线程public static void yield()

Thread.yield(); 表示出让当前CPU的执行权

// 子类 线程类
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "---" + i);
            // 表示出让当前CPU的执行权
            Thread.yield();
        }
    }
}

image.gif

细节:让出CPU的执行权以后,两条线程重新开始抢夺执行权,所以让CPU执行权的线程还可能会抢到线程执行权

// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
        // 创建子类对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        // 给线程设置名称
        t1.setName("飞机");
        t2.setName("坦克");
        // 启动线程
        t1.start();
        t2.start();
    }
}

image.gif

8、插入线程/插队线程public final void join()

// 子类 线程类
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}

image.gif

[子类对象名].join(); 表示把这个线程插入到当前线程之前

// 测试类
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建子类对象
        MyThread t = new MyThread();
        // 设置线程名称
        t.setName("土豆");
        // 启动线程
        t.start();
        // 表示把t这个线程插入到当前线程之前
        // t:土豆
        // 当前线程:main线程
        t.join();
        // 执行在main线程当中的
        for (int i = 1; i <= 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}

image.gif

线程安全的问题

线程安全性问题通常出现在多线程环境中,当多个线程同时访问和修改同一份数据时,如果没有适当的同步控制,可能会导致数据的不一致性,这就是线程安全性问题。

常见的安全性问题:

第一种:竞态条件

电影院售票:电影院有三个窗口共卖100张票

重复的票:由于线程在执行过程中随时会被别的线程抢走执行权,所以可能还没来得及输出结果,就被别的线程抢走了执行权,然后别的线程又执行,该条线程抢到执行权以后,就会打印重复的票。

超出范围的票:当售票到99张以后,窗口1抢到执行权,还没来得及输出,又被窗口二抢到执行权,这时候的值就变成了101,就会打印超出范围的票。

解决方法:使用同步代码块或同步方法

第二种:死锁

两个人抢筷子吃饭:两个人抢筷子吃饭,一次只能抢一只筷子,当一个人抢到一双筷子,另一个人抢一个筷子,这时候双方都会等对方先放下筷子,就会造成死锁。

解决方法:控制锁顺序,避免嵌套

第三种:活锁

夫妻用勺子吃饭:一对夫妻用一把勺子吃饭,双方都很有礼貌,总是让对方先吃,当发现对方饿了,就会把勺子让给对方,这会导致出现活锁。

解决方法:synchronized同步代码块,同步方法,Lock锁

synchronized同步代码块

同步代码块,当线程抢到执行权,进入同步代码块后,只有执行完同步代码块中的代码,别的线程才能重新抢夺CPU执行权

package com.zsh.threaddemo.threadtest01;
public class MyThread extends Thread{
    // 表示这个类所有的对象,都共享num数据
    static int num = 0;
    // 锁对象,一定要是唯一的
    static Object obj = new Object();
    @Override
    public void run() {
        while (true){
            // 同步代码块
            synchronized (obj){ // obj:锁对象,任意对象都可以,但必须是唯一的(一般情况下用类名.class)
                if (num < 100){
                    num++;
                    System.out.println(getName() + "正在出售第" + num + "张票!!!");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }else {
                    break;
                }
            }
        }
    }
}

image.gif

package com.zsh.threaddemo.threadtest01;
public class ThreadDemo {
    public static void main(String[] args) {
        /*
         * 某电影院目前正在上映一部国产大片,共有100张票,而且有3个售票口,请写出程序模拟该电影售票
         * */
        // 创建子类对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        // 给线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

image.gif

同步方法

细节:

如果不知道怎么写同步方法,那就先写同步代码块,

选中同步代码块中的代码按Ctrl+Alt+M自动创建方法,在修饰符和返回类型之间加上synchronized关键字即可

package com.zsh.threaddemo.threadtest02;
public class MyRunnable implements Runnable{
    int num = 0;
    @Override
    public void run() {
        // 循环
        while (true){
            // 同步代码块(同步方法)
            if (method()) break;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    private synchronized boolean method() {
        if(num == 100){
            return true;
        }else {
            num++;
            System.out.println(Thread.currentThread().getName() + "正在出售第" + num + "张票!!!");
        }
        return false;
    }
}

image.gif

package com.zsh.threaddemo.threadtest02;
public class ThreadDemo {
    public static void main(String[] args) {
        /*
         * 某电影院目前正在上映一部国产大片,共有100张票,而且有3个售票口,请写出程序模拟该电影售票
         * (使用同步方法写)
         * */
        // 创建MyRunnable对象,表示多线程要执行的任务
        MyRunnable mr = new MyRunnable();
        // 创建多线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);
        // 设置线程名称
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

image.gif

Lock锁

Lock锁提供了比Java内置的synchronized关键字更灵活、更强大的同步控制功能。

lock.lock(); 获取锁

lock.unlock(); 释放锁

细节:创建Lock时,不能直接new Lock要new Lock的实现类RenntrantLock

package com.zsh.threaddemo.threadtest03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
    static int num = 0;
    // 为了保证多个线程用的是同一个锁,所以加static静态方法
    static Lock lock = new ReentrantLock(); // 不能直接new Lock要new Lock的实现类RenntrantLock
    @Override
    public void run() {
        while (true){
            // 上锁
            lock.lock();
            try {
                if(num == 100){
                    break;
                }else {
                    num++;
                    System.out.println(getName() + "正在出售第" + num + "张票!!!");
                }
            } finally {
                // 开锁
                lock.unlock();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

image.gif

package com.zsh.threaddemo.threadtest03;
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        * Lock锁
        * */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

image.gif

Lock锁的规范用法

要使用try,finally,来保证释放锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyClass {
    private final Lock lock = new ReentrantLock(); // 使用final修饰Lock锁
    public void doSomething() {
        lock.lock(); // 获取锁
        try {
            // 在这里执行同步操作
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}

image.gif

线程池

线程池的概念

线程池是一个容器,可以保存一些线程对象,这些线程可以反复使用。

线程池的优势

降低资源消耗,重复利用线程池中的线程,不需要每次都创建、销毁。

便于线程管理,线程池可以集中管理并发线程的数量。

提交Runnable任务

// 测试类
package com.zsh.demo10多线程;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        System.out.println("pool = " + pool);
        // 提交Runnable任务
        MyRunnable mr = new MyRunnable();
        pool.submit(mr);
        pool.submit(mr);
        pool.submit(mr);
        pool.submit(mr);
        pool.submit(mr);
        // 关闭线程池
        pool.shutdown();
    }
}
// 子类
package com.zsh.demo10多线程;
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "任务执行完成");
    }
}

image.gif

提交Callable任务

好处:有返回值。

可以抛异常。

// 测试类
package com.zsh.demo10多线程.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 ExecutionException, InterruptedException {
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        // 创建Callable任务
        MyCallable mc1 = new MyCallable(100);
        Future<Integer> future = pool.submit(mc1);
        MyCallable mc2 = new MyCallable(200);
        Future<Integer> future2 = pool.submit(mc2);
        // 拿到返回值
        Integer ret1 = future.get();
        System.out.println("结果1:" + ret1);
        Integer ret2 = future2.get();
        System.out.println("结果2:" + ret2);
        // 关闭线程池
        pool.shutdown();
    }
}
// 子类
package com.zsh.demo10多线程.callable;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
    // 求1-n的值
    int n;
    public MyCallable(int n) {
        this.n = n;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        System.out.println(Thread.currentThread().getName() + "执行任务完毕!");
        return sum;
    }
}

image.gif


相关文章
java 线程快速入门(三) sleep方法的使用
java 线程快速入门(三) sleep方法的使用
155 0
java 线程快速入门(二) 之 给线程改名
java 线程快速入门(二) 之 给线程改名
112 0
java 线程快速入门(一) 之 第一个多线程程序
java 线程快速入门(一) 之 第一个多线程程序
|
安全 Java
java中线程安全,线程死锁,线程通信快速入门
java中线程安全,线程死锁,线程通信快速入门一:多线程安全问题 1 引入 复制代码 /* * 多线程并发访问同一个数据资源 * 3个线程,对一个票资源,出售 */ public class ThreadDemo { public static void main(String[] arg...
870 0
Java多线程 -- 互斥锁/共享锁/读写锁 快速入门
什么是互斥锁? 在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。 如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。
1216 0
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
44 1
C++ 多线程之初识多线程
|
27天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
27天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
17 2
|
27天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
27天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
33 1