多线程

多线程的生命周期:

  1. 新建态:创建一个线程对象。
  2. 就绪态:线程对象创建完成之后,加入到可运行的线程池中,等待系统调用,获取cpu的使用权。
  3. 运行态:获取了cpu的使用权,执行代码。
  4. 阻塞态:在运行的过程中,由于某些原因放弃了cpu的使用权,停止运行,直到回到就绪态。
  5. 死亡态:线程执行完毕,或因为异常结束运行,即退出run()。

多线程的四种创建方式

  • 继承Thread类

创建一个类继承自Thread,重写run方法。

public class Thread1 extends Thread implements StartThread {//StartThread接口提供了一个启动线程的抽象方法
    @Override
    public void run() {//重写run方法
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }

    @Override
    public void startThread() {
        Thread1 th = new Thread1();//创建当前类的实例
        th.start();//启动线程调用Thread类的start()方法
    }
}

创建测试类,查看运行结果

public class ThreadTest{
    public static void main(String[] args) {
        StartThread ct1 = new Thread1();//实例化一个Thread1对象
        ct1.startThread();//启动线程
    }
}

 

  • 实现Runnable接口

创建一个类实现Runnable接口,重写run()方法

public class RunnableTest1 implements Runnable, StartThread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }

    @Override
    public void StartThread() {
        /**
         *  创建一个Thread类的实例。
         *  参数是实现了Runnable接口中的run()方法的一个实例对象
         *  启动线程调用start()方法
         */
        Thread th = new Thread(new RunnableTest1());
        th.start();
    }
}

创建测试类,查看运行效果

public class ThreadTest{
    public static void main(String[] args) {
        StartThread ct1 = new Thread1();
        ct1.startThread();
    }
}
  • 实现Callable接口,与FutureTask类结合使用

创建StartThread接口

public interface StartThread {
    public void startThread() throws ExecutionException, InterruptedException;//开启线程
    public void getResult();//获取线程执行完毕返回的结果
    public boolean aliveThread();//判断当前线程是否存活
}

创建CallableTest1类,实现Callable、StartThread接口

public class CallableTest1 implements Callable<List<String>> ,StartThread{
    FutureTask<List<String>> ft = null;//Callable接口创建线程需要与FutureTask类一起使用
    Thread th = null;
    List<String> list = null;//储存线程执行结果
    @Override
    public List<String> call() throws Exception {
        List<String> list = new ArrayList<>();
        String str = null;
        Date date = new Date();
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                str = "运行时间:" + date.getTime() + "----" + Thread.currentThread().getName() + "--->" + i;
                System.out.println("运行中..." + str);
                list.add(str);
            }
        }
        return list;
    }
    @Override
    public void startThread() throws ExecutionException, InterruptedException {
        ft = new FutureTask<>(new CallableTest1());
        th = new Thread(ft);
        th.start();
    }
    public void getResult() {
        try {
            list = ft.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        for (String str : list) {
            System.out.println("结果..." + str);
        }
    }
    @Override
    public boolean aliveThread() {
        return th.isAlive();
    }
}

 创建Test测试类,查看运行结果

public class Test {
    public static void main(String[] args) {
        StartThread st1 = new CallableTest1();
        try {
            st1.startThread();
            while(st1.aliveThread()){

            }
            st1.getResult();
            
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意:

我在这里犯过几个错误,在这里为大家展示一下希望大家能够避免少走弯路。

抽象出的方法功能要尽量单一,比如这里的StartThread()方法

    @Override
    public void startThread() throws ExecutionException, InterruptedException {
        /**
         *  启动线程方法只负责启动线程
         */
        ft = new FutureTask<>(new CallableTest1());
        th = new Thread(ft);
        th.start();
        /**
         *  如果在启动线程之后还有耗时的操作发生,那么在启动其他线程时就会有问题
         *  例如,我在这里再实现一个对于执行结果的接收和遍历。
         *  那么当我在测试类中调用该方法时,线程启动后还需要执行完剩余的代码才能够退出该方法
         *  如果同时要启动多个Callable接口实现类的对象启动线程,其执行效果就是单线程的效果
         */
//        list = ft.get();
//        for (String str : list) {
//            System.out.println(str);
//        }
    }

 在实现类中,FutureTask类设计为全局变量时,尽量只声明该类的引用,不要直接创建实例对象。(会造成栈空间溢出的异常,这一点不仅在本实验中会发生,所以希望大家能在实现类的时候也注意这一点)

public class CallableTest1 implements Callable<List<String>> ,StartThread{
//    FutureTask<List<String>> ft = new FutureTask<>(new CallableTest1());
    FutureTask<List<String>> ft = null;//Callable接口创建线程需要与FutureTask类一起使用

  • 使用线程池创建线程

使用线程池的好处:

  1. 线程在创建线程池的时候就创建好,在使用线程池时直接提供创建好的线程,节省了创建线程的时间,提高了效率。
  2. 线程在使用完毕后不会直接销毁,而是放回到线程池中,这样就不需要每次都去创建一个线程,减少了资源的消耗。
  3. 可以设置线程池的大小、最大线程数、线程多久没有任务以后会关闭等等,方便对线程进行管理
package Demo3;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.lang.Thread.sleep;

public class ThreadDemo implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i % 2 == 0 ? Thread.currentThread().getName() + "----" + i : "");
        }
    }
}
class ThreadPool{
    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);//提供线程数量的线程池
        /**
         *  有两种参数:
         *  一.实现Runnable接口的实现类
         *  二.实现Callable接口的实现类
         */
        service.submit(new ThreadDemo());
    }
}

多线程中的方法:

  • join():

调用join方法,当前线程进入阻塞状态等待加入的线程执行完毕,再继续执行。

 

import static java.lang.Thread.sleep;

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        Thread th = new Thread(mt);
        th.start();
        th.join();//将子线程加入到main线程中,此时main线程等待子线程执行完毕,再恢复执行
        for(int i = 0; i < 20; i ++){
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i < 20; i ++){
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }
}
  • setPriority(int newPriority):

设置线程的优先级,JDK一共提供了3种线程执行的级别,默认优先级是5

    Thread th = new Thread();
    th.setPriority(MAX_PRIORITY);//设置th线程为最高优先级——>10
    th.setPriority(MIN_PRIORITY);//设置th线程为最低优先级——>1
    th.setPriority(NORM_PRIORITY);//设置th线程为默认优先级——>5

注意:设置了线程的优先级只是使得该线程被给予资源的概率提高,并不能够保证该线程一定会被优先执行,说到底还是看脸,概率是一个玄学。。。

  • sleep(long millis):

强迫当前线程进入阻塞状态暂停执行millis毫秒,但是不会丢失任何监视器的所属权。当暂停时间到达之后,该线程回到就绪状态继续执行。

  • yield():

当前线程调用了yield后,该线程不会变成阻塞状态,而是直接回到就绪状态。

  • Sleep(long millis)与yield()的区别

  • 线程安全问题

多个线程操作同一个共享资源的时候就会产生线程安全的问题,比如经典的买票事件。

多个卖票窗口(线程)售卖同一类门票(共享资源),如果在线程不安全的情况下就可能会发生重票的现象。

package Demo1;

import static java.lang.Thread.sleep;

public class Test {
    public static void main(String[] args) {
        /**
         *  通过结果显示,多个窗口会多次售出同一张票,这就是线程安全的问题
         */
        Thread1 t = new Thread1();
        Thread th1 = new Thread(t, "窗口一");
        Thread th2 = new Thread(t, "窗口二");
        th1.start();
        th2.start();
    }
}

class Thread1 implements Runnable{
    int ticket = 20;//多个线程共享的资源
    @Override
    public void run() {
        while (ticket > 0){
            try {
                sleep(100);//当前线程在这里睡眠一会,其他线程进入循环可增加重票概率
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName() + "剩余" + ticket + "张票");
        }
    }
}
  • 线程锁

为了解决线程安全的问题,Java提供了线程锁的方法,阻塞线程,保证在锁空间内单线程执行。

  • synchronized()同步锁:

使用synchronized锁有两种方式,一种是同步代码块,另外一种是同步方法。对于加锁后的区域,可以保证同一时刻只有一个线程在执行,其他线程在线程池中等待该线程释放锁,有效的解决了线程安全的问题。但缺点是,由于加锁后只能单线程执行,所以效率会变差。

同步代码块:在指定的区域内加锁,在使用同步代码块进行加锁时,需要为同步代码块提供锁(也是监视器),要保证需要同步的线程使用同一把锁才能够实现线程安全,监视器可以为任意一对象,同步力度(可以理解为作用区域)小于同步方法。

synchronized(Object obj){
    //执行代码
}

同步方法:在执行方法的返回值前添加synchronized,不用指定监视器,因为监视器指定为当前调用该方法的对象,即为this,同步方法同步力度大于同步代码块。

//synchronized放在返回类型前任意位置即可
public synchronized void method1(){
    //执行代码
}
synchronized public void method2(){
    //执行代码
}

对之前的卖票流程进行修改,实现线程的安全。

package Demo1;

import static java.lang.Thread.sleep;

public class Test {
    public static void main(String[] args) {
        /**
         *  通过结果显示,先获得锁的窗口直到将票卖完,剩下的窗口也不能够卖票
         *  这是因为先得到锁的窗口一直拿着锁将票卖完之后才释放,剩下的窗口自然无票可卖
         */
        Thread1 t = new Thread1();
        Thread th1 = new Thread(t, "窗口一");
        Thread th2 = new Thread(t, "窗口二");
        th1.start();
        th2.start();
    }
}

class Thread1 implements Runnable{
    int ticket = 100;//多个线程共享的资源
    @Override
    public void run() {
        try {
            sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (Thread.class){
            while (ticket > 0){
                try {
                    sleep(100);//当前线程在这里睡眠一会,其他线程进入循环可增加重票概率
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + "剩余" + ticket + "张票");
            }
        }
    }
}
  • Lock接口:

Lock接口是在JDK1.5之后提出的一种锁,使用Lock接口锁的实现类,我们同样可以达到线程安全的效果。在与synchronized效果一致的同时,Lock更加灵活(synchroized是自动加锁、自动释放,Lock是手动加锁、手动释放)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值