Java - 多线程

多线程指从软硬件上实现的多条执行流程的技术(多线程有CPU复制调度执行)

创建方式一:继承Thread类

// 定义线程类
public class MyThread extends Thread{
	public void run(){
	
	}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();

注意:

  1. 启动线程必须是调用start方法,不是run方法(用run就是单线程)
  2. 不要把主线程任务放在启动子线程之前。

创建方式二:实现Runnable接口

// 定义一个可运行的类
public class MyRunnable implements Runnable {
	public void run(){
	
	}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();




package ThreadTest;

public class ThreadTest02 {
    public static void main(String[] args) {
        //3.创建任务对象
        Runnable target = new MyRunnable();
        //4.把任务对象交给一个线程对象处理
        new Thread(target).start();

        for (int i = 1; i < 5; i++) {
            System.out.println("主线程main输出~~~~" + i);

        }
    }
}

创建方式二的匿名内部类写法:

public class ThreadTest04 {
    public static void main(String[] args) {
        // 创建线程对象,采用匿名内部类方式。
        Runable target = new Runable(){
         @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println("t线程---> " + i);
            }
        }
        new Thread(target).start();

        //简写
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println("t线程---> " + i);
                }
            }
        });

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

        for(int i = 0; i < 100; i++){
            System.out.println("main线程---> " + i);
        }
    }
}

创建方式三:利用Callable接口、FutureTask类实现

前两种的问题:线程执行完毕后不能用run方法直接返回结果

未来任务对象的作用?

1.是一个任务对象,实现了Runnable对象

2.可以再线程执行完毕之后,和未来任务对象调用get方法获取线程执行完毕后的结果

ThreadTest.java:

package ThreadTest;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadTest03 {
    public static void main(String[] args) {
        //3.创建一个callable对象
        Callable<String> call = new MyCallable(100);
        //4.把callable对象封装成一个FutureTask对象(任务对象)
        //未来任务对象的作用?
        //1.是一个任务对象,实现了Runnable对象
        //2.可以再线程执行完毕之后,和未来任务对象调用get方法获取线程执行完毕后的结果
        FutureTask<String> future = new FutureTask<>(call);
        //5.把任务对象交给一个Thread对象
        Thread thread = new Thread(future);
        thread.start();
        //new Thread(future).start();

        //6.获取线程执行完毕后的结果
        try {
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MyCallable.java:

package ThreadTest;

import java.util.concurrent.Callable;

//1.让这个类实现Callable接口
public class MyCallable implements Callable<String> {
    private  int n;

    public MyCallable(int n) {
        this.n = n;
    }

    //2.重写call方法
    @Override
    public String call() throws Exception {
        //描述线程的任务,返回线程执行返回的结果
        //1-n的和
        int sum = 0;
        for (int i = 1; i < n; i++) {
            sum +=i;
        }
        return "线程求出了1到" + n + "的和是" + sum;
    }
}

Thread常用方法

Thread m = Thread.currentThread();//哪个线程执行它,它就会得到哪个线程对象(主线程执行就得到主线程对象,再m.getName()就可以获得主线程名字)

Thread.sleep(1000);参数是毫秒
作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法:间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。t

Thread.join();
void join()    将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束
void join(long millis)    接上条,等待该线程终止的时间最长为 millis 毫秒
void join(long millis, int nanos)    接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

 一个线程.join(),当前线程会进入”阻塞状态“。等待加入线程执行完

public class ThreadTest13 {
    public static void main(String[] args) {
        System.out.println("main begin");

        Thread t = new Thread(new MyRunnable7());
        t.setName("t");
        t.start();

        //合并线程
        try {
            t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main over");
    }
}

class MyRunnable7 implements Runnable {

    @Override
    public void run() {
        for(int i = 0; i < 10000; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

线程安全

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题

  1. 条件1:多线程并发
  2. 条件2:有共享数据
  3. 条件3:共享数据有修改的行为

线程同步

线程同步常见方案:加锁

方式一:同步代码块

把访问共享资源的核心代码上锁,每次只允许一个线程加锁进入,执行完毕后自动解锁,其他线程才可以进来执行。

synchronized(同步锁){
    // 线程同步代码块。
}

同步锁必须是多线程共享的数据!!

        synchronized ("同步锁") {
            if (this.balance >= money) {
                //假设取钱成功,就从余额中减去
                this.balance -= money;
                System.out.println(name + "取出" + money + "元,余额" + this.balance + "元");
            } else {
                System.out.println(name + "取出" + money + "元,余额不足");
            }
        }

方式二:同步方法

访问资源的核心方法上锁,一次保证线程安全

修饰符 synchronized 返回值类型 方法名称(形参列表){

        操作共享资源的代码

}

public synchronized void drawMoney(double money) {
        //搞清楚谁来取钱
        String name = Thread.currentThread().getName();
        //判断余额是否足够
        if (this.balance >= money) {
            //假设取钱成功,就从余额中减去
            this.balance -= money;
            System.out.println(name + "取出" + money + "元,余额" + this.balance + "元");
        } else {
            System.out.println(name + "取出" + money + "元,余额不足");
        }

    }

方式三:Lock锁

Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活,更方便,更强大

Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

//创建一个锁对象
    private final Lock lk = new ReentrantLock();

...

public void drawMoney(double money) {{
        //搞清楚谁来取钱
        String name = Thread.currentThread().getName();
        try {
            lk.lock();//加锁
            //判断余额是否足够
            if (this.balance >= money) {
                //假设取钱成功,就从余额中减去
                this.balance -= money;
                System.out.println(name + "取出" + money + "元,余额" + this.balance + "元");
            } else {
                System.out.println(name + "取出" + money + "元,余额不足");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lk.unlock();//解锁
        }
    }

线程池

 可以复用线程的技术

ThreadPoolExecutor:

注意事项:

临时线程的创建时间:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

开始拒绝新任务的时间:核心线程和临时线程都在忙,任务队列也满了,新的任务过来是才会拒绝任务。

        //1.通过ThreadPoolExecutor创建一个线程池对象
        //new LinkedBlockingDeque<>(4)四个任务列
        //new ThreadPoolExecutor.AbortPolicy():拒绝策略,新任务来了无法执行则抛出异常
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(4),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Runnable target = new MyRunnable();
        pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行;
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);//复用前面的核心线程
        pool.execute(target);//复用前面的核心线程
        //pool-1-thread-2=====> 输出6666666~
        //pool-1-thread-3=====> 输出6666666~
        //pool-1-thread-1=====> 输出6666666~
        //pool-1-thread-3=====> 输出6666666~
        //pool-1-thread-2=====> 输出6666666~


        pool.shutdown();//等线程执行完,再关闭线程池
        pool.shutdownNow();//不等线程执行完,直接关闭线程池

 ExecutorService常用方法

pool.execute():

        //1.通过ThreadPoolExecutor创建一个线程池对象
        //new LinkedBlockingDeque<>(4)四个任务列
        //new ThreadPoolExecutor.AbortPolicy():拒绝策略,新任务来了无法执行则抛出异常
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(4),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Runnable target = new MyRunnable();
        pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行;
        pool.execute(target);
        pool.execute(target);
        //开始任务列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        //开始临时线程
        pool.execute(target);
        pool.execute(target);
        //新任务的拒绝时机(抛异常)
        pool.execute(target);

        pool.shutdown();//等线程执行完,再关闭线程池
        pool.shutdownNow();//不等线程执行完,直接关闭线程池




package thread_pool;

import java.sql.SQLOutput;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //任务做什么
        System.out.println(Thread.currentThread().getName() + "=====> 输出6666666~");
        try {
            Thread.sleep(Integer.MAX_VALUE);//使核心线程一直被占用
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
新任务拒绝策略

 pool.submit():

        //1.通过ThreadPoolExecutor创建一个线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(4),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        //2.使用线程池处理Callable任务
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());



package thread_pool;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n)
    {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= n;i++)
        {
            sum += i;
        }
        return Thread.currentThread().getName() + "线程求出了1到" + n + "的和是" + sum;
    }
}

pool.shutdown();

等线程执行完,再关闭线程池
pool.shutdownNow();

不等线程执行完,直接关闭线程池

Executors工具类实现线程池

Executors.newFixedThreadPool(3);

  • 计算密集型任务:核心线程数量 = CPU的核数 + 1;
  • IO密集型任务:核心线程数量 = CPU的核数 * 2;

这里的CPU核数为4;

大型并发系统环境种使用Executors如果不注意可能会出现系统风险

并发和并行

线程的通信

​ 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。

​ 出现死锁后,并不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

​ 使用同步时应避免出现死锁。

死锁的解决方法:

1. 专门的算法、原则。
2. 尽量减少同步资源的定义。
3. 尽量避免嵌套同步。

原理:

​ 当一个线程执行完成其所应该执行的代码后,手动让这个线程进入阻塞状态,这样一来,接下来的操作只能由其他线程来操作。当其他线程执行的开始阶段,再手动让已经阻塞的线程停止阻塞,进入就绪状态,虽说这时候阻塞的线程停止了阻塞,但是由于现在正在运行的线程拿着同步锁,所以停止阻塞的线程也无法立马执行。如此操作就可以完成线程间的通信。

所用的到方法:

​ wait():一旦执行此方法,当前线程就会进入阻塞,一旦执行wait()会释放同步监视器。

​ notify():一旦执行此方法,将会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先度最高的。

​ notifyAll() :一旦执行此方法,就会唤醒所有被wait的线程

​ 说明:

​ 这三个方法必须在同步代码块或同步方法中使用。

​ 三个方法的调用者必须是同步代码块或同步方法中的同步监视器。

​ 这三个方法并不时定义在Thread类中的,而是定义在Object类当中的。因为所有的对象都可以作为同步监视器,而这三个方法需要由同步监视器调用,所以任何一个类都要满足,那么只能写在Object类中。

sleep()和wait()的异同:

  1. 相同点:两个方法一旦执行,都可以让线程进入阻塞状态。

  2. 不同点:1) 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()

    ​ 2) 调用要求不同:sleep()可以在任何需要的场景下调用。wait()必须在同步代码块中调用。

    ​ 2) 关于是否释放同步监视器:如果两个方法都使用在同步代码块呵呵同步方法中,sleep不会释放锁,wait会释放锁。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值