多线程快速入门

开启多线程的方法

第一种:继承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");
        }
    }
}

第二种:实现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");
        }
    }
}

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

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

import java.util.concurrent.Callable;

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

启动多线程的方法

第一种: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();
    }
}

第二种: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();
    }
}

第三种: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 ft = new FutureTask<>(mc);

        // 创建线程的对象java
        Thread t1 = new Thread(ft);

        // 启动线程
        t1.start();

        // 获取多线程运行的结果
        Integer result = ft.get();  // 有异常直接抛出
        System.out.println(result);
        
    }
}

多线程的常用方法

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();
    }
}

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());	// 返回此线程的名称
    }
}
// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
     
        //1、创建线程的对象
        MyThread t1 = new MyThread("飞机");	// 通过构造参数设置线程名称
        MyThread t2 = new MyThread("坦克");

        // 2、开启线程
        t1.start();
        t2.start();
    }
}

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);
    }
}

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);
        }
    }
}
// 测试类
public class ThreadDemo {
    public static void main(String[] args) {
     
        //1、创建线程的对象
        MyThread t1 = new MyThread("飞机");	// 通过构造参数设置线程名称
        MyThread t2 = new MyThread("坦克");

        // 2、开启线程
        t1.start();
        t2.start();

    }
}

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);
        }
    }
}

[子类对象名].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();
    }
}

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);
        }
    }
}
// 子类	线程类
public class MyThread02 extends Thread{

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}

[子类对象名].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();
    }
}

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();
        }
    }
}

细节:让出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();
    }
}

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);
        }
    }
}

[子类对象名].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);
        }

    }
}

线程安全的问题

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

常见的安全性问题:

第一种:竞态条件

电影院售票:电影院有三个窗口共卖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;
                }
            }
        }
    }
}
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();
    }
}

同步方法

细节:

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

选中同步代码块中的代码按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;
    }
}
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();
    }
}

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);
                }
            }
        }
    }


}
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();
    }
}

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(); // 释放锁
        }
    }
}

线程池

线程池的概念

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

线程池的优势

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

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

提交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() + "任务执行完成");
    }
}

提交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 future = pool.submit(mc1);

        MyCallable mc2 = new MyCallable(200);
        Future 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 {

    // 求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;
    }
}

你可能感兴趣的:(java,开发语言)