多线程

注:该文章只做简单记录

线程的状态

原文链接:
https://www.cnblogs.com/wangyichuan/p/5990821.html

实现线程(四种方式)
  • 继承Thread类
public class ThreadTest{

    //main 也为一个线程
    public static void main(String[] args) {
        //创建MyTHread对象(线程)
        MyThread myThread = new MyThread();
        //调用start()
        myThread.start();
    }
}

class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println();    //线程想要做的事
    }
}
  • 实现Runable接口
public class ThreadTest{

    //main 也为一个线程
    public static void main(String[] args) {
        //创建MyTHread对象(线程)
        MyThread myThread = new MyThread();

        Thread thread = new Thread(myThread);
        //调用start()
        thread.start();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println();    //线程想要做的事
    }
}
  • 实现Callable接口(JDK5.0后)
    与使用Runable相比,Callable功能更加强大些
  1. 相比run()方法,可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果
public class CallableTest  {
    public static void main(String[] args) {
        Test test = new Test();
        FutureTask<String> task = new FutureTask<String>(test);
        new Thread(task).start();

        try {
            //获取Callable中的call方法的返回值
            Object object = task.get();
            System.out.println(object);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

class Test implements Callable<String>{
    @Override
    public String call() throws Exception {
        return "...";
    }
}
  • 使用线程池(JDK5.0后,实际开发中常用...)
  1. 背景:经常创建和销毁,使用量特别大的资源,比如开发情况下的线程,对性能影响很大。
  2. 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。
  3. 好处:
  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于线程管理
    1. corePoolSize:核心池的大小
    2. maximumPoolSize:最大线程数
    3. keepAliveTime:线程没有任务时最多保持多长时间后会终止
  • 线程池相关API(内容较多)
  • JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
     ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
     void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行
    Runnable
     <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行
    Callable
     void shutdown() :关闭连接池
     Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
     Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
     Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
     Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
     Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
/**
 * 创建线程的方式四:使用线程池
 *
 * 好处:
 * 1.提高响应速度(减少了创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
 * 3.便于线程管理
 *    corePoolSize:核心池的大小
 *    maximumPoolSize:最大线程数
 *    keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 * 面试题:创建多线程有几种方式?四种!
 * @author shkstart
 * @create 2019-02-15 下午 6:30
 */

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

由于使用Executors创建线程池有相应的弊端
还可以使用以下方法

/**
 * 测试ThreadPoolExecutor对线程的执行顺序
 **/
public class ThreadPoolSerialTest {
    public static void main(String[] args) {
        //核心线程数
        int corePoolSize = 3;
        //最大线程数
        int maximumPoolSize = 6;
        //超过 corePoolSize 线程数量的线程最大空闲时间
        long keepAliveTime = 2;
        //以秒为时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        //创建工作队列,用于存放提交的等待执行任务
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
        ThreadPoolExecutor threadPoolExecutor = null;
        try {
            //创建线程池
            threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    unit,
                    workQueue,
                    new ThreadPoolExecutor.AbortPolicy());

            //循环提交任务
            for (int i = 0; i < 8; i++) {
                //提交任务的索引
                final int index = (i + 1);
                threadPoolExecutor.submit(() -> {
                    //线程打印输出
                    System.out.println("大家好,我是线程:" + index);
                    try {
                        //模拟线程执行时间,10s
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                //每个任务提交后休眠500ms再提交下一个任务,用于保证提交顺序
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}

暂时记一下...

多线程安全问题
  • 比如:经典的买票问题,假如有两个窗口买票,a窗口和b窗口,那么a窗口相当于一个线程,b窗口也是一个线程,而ticket相当于共享数据。
  • 场景:当a线程操作ticket时,操作尚未完成,这个时候b线程参与进来,也操作车票,这时就发生了安全问题。
  • 解决:当a线程在操作ticket时,其他线程不能参与进来,只能等到a线程此次操作完后,其他线程才开始操作ticket。
    在java中,我们通过同步机制解决安全问题

synchronize(同步监视器){
//需要同步的代码
}

  1. 操作共享数据的代码,即为需要被同步的代码。
  2. 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
  3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
    --要求:多个线程必须要共用同一把锁。
    补充:
    同步监视器必须是唯一的
    在继承Thread创建多线程的方式中,我们可以考虑使用当前类充当同步监视器。
    在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
  • 方式:
public class Security {

    public static void main(String[] args) {
        SecurityTest securityTest = new SecurityTest();
        new Thread(securityTest).start();
        new Thread(securityTest).start();
    }
}
  • 同步代码块
class SecurityTest implements Runnable {
    //票 共享数据
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    private synchronized void show() {
        //先判断票是否存在
        if (ticket > 0) {
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //买票
            System.out.println("线程" + Thread.currentThread().getName() + "在买票第" + ticket + "张票!");
            ticket--;
        }
    }
}

单例模式 -- 线程安全

public class Singleton {

    private volatile static Singleton singleton; //volatile 刚开始没有加 出现问题
    //
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • 先说说为什么要双重锁的问题。在早期的jvm中,synchronized存在巨大的性能开销。如果getInstance的竞争很小,甚至没有竞争,那么synchronized就存在很大的冗余性能开销。所以通过双重检查机制避免不必要的锁操作。
    (DCL本质上也就是减少了锁粒度)然而这样的单例还不是最安全的(没加volatile),代码执行到singleton不为null时,可能会出现singleton的引用还没有被完全初始化的情况。
  • 这里涉及到指令重排序
  • 这里有有些地方题主还没有深入......
死锁
  • 死锁
  1. 不同的线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,就形成了现成的死锁
  2. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
  • 解决方法
  1. 专门的算法、原则 2. 尽量减少同步资源的定义 3. 尽量避免嵌套同步
    (这个地方不在多讲)
Lock

JDK1.5之前,我们在多线程的环境下要想保证线程安全,就必须要使用synchronized关键字来实现对象锁或者类锁,以此满足这样的需求,JDK1.5之后则使用Lock来实现更加细粒度的锁。

//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);

//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);
public class Security {

    public static void main(String[] args) {
        SecurityTest securityTest = new SecurityTest();
        new Thread(securityTest).start();
        new Thread(securityTest).start();
        new Thread(securityTest).start();
    }
}

class SecurityTest implements Runnable {
    //票 共享数据
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //买票
                    System.out.println("线程" + Thread.currentThread().getName() + "在买票第" + ticket + "张票!");
                    ticket--;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}
  1. lock是显示锁(手动开启和关闭锁),synchronize是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronize有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)
  • Lock --> 同步代码块(已经进入了方法体,分配了相应资源) --> 同步方法(在方法体外)
线程通信

线程通信涉及到的三个方法 以下方法是定义在java.lang.Object中

  1. wait() 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
  2. notify() 一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤优先级最高的那个。
  3. notify() 一旦执行此方法,就会唤醒所有被wait的一个线程
  • 以上方法必须使用在同步代码块或同步方法中(以上方法其实是同步监视器调用的)

  • 时间:2019-12-5 23:50:44

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,772评论 2 17
  • 标签(空格分隔): java java 5新增机制--->同步锁(Lock) 从java5开始,java提供了一种...
    Sivin阅读 624评论 1 5
  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,266评论 1 18
  • 写在之前 以下是《疯狂Java讲义》中的一些知识,如有错误,烦请指正。 概述 ** 进程特征** 独立性:进程是系...
    hainingwyx阅读 460评论 0 0
  • 以无所得故,菩提萨埵,依般若波罗蜜多故,心无挂碍,无挂碍故,无有恐怖,远离颠倒梦想, 至于刻印事宜,由朵云轩指道以...
    经常想你阅读 470评论 0 1

友情链接更多精彩内容