【多线程--进程和线程】

性能开销的最大问题:

1、内核态到用户态的的切换
2、上下文的保存(数据的保存)

在内核态与用户态的切换中同时伴随着 上下文的保存

​ 线程是由进程创建的,是进程的一个实体,是具体干活的人,一个进程可能有多个线程。线程不独立分配内存,而是共享进程的内存资源,线程可以共享cpu的计算资源。

多线程创建的四种方式
1,实现Runnable接口

public class UseRunable implements Runnable{
    @Override
    public void run() {
        System.out.println(2);
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(1);
        new Thread(new UseRunable()).start();
        new Thread().sleep(10);
        System.out.println(3);
    }
}

2,继承Thread类

public class UseThread extends Thread{
    public void run(){
        System.out.println(2);
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(1);
        new Thread().sleep(12);
        new UseThread().start();

        System.out.println(3);
    }
}

3,箭头函数


    public static void main(String[] args) throws InterruptedException {
        System.out.println(1);
      new Thread(//new UserThread() {
//            @Override
//            public void run() {
//                System.out.println(1);
//            }
//        }箭头函数
              ()-> System.out.println(2)
        ).start();
        Thread.sleep(15);
        System.out.println(3);
    }


4,实现Callable接口传给FutureTask(可以有返回值)
开启多线程有利于做同一件事的时候可以采取多个分支的作用
提高运行效率

public class UseCallable implements Callable<Long> {
    int from;
    int to;
    public UseCallable() {
    }
    public UseCallable(int from, int to) {
        this.from = from;
        this.to = to;
    }
    @Override
    public Long call() throws Exception {
        long res=0;
        for (int i = from; i <to ; i++) {
            res+=i;
        }
        return res;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //单线程创建的方式
        long start = System.currentTimeMillis();
        long  tager = 0L;
        for (int i = 0; i < 1000000000; i++) {
            tager+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println(tager);
        System.out.println(end-start);

        //多线程的创建方式
        start=System.currentTimeMillis();
        long sum=0l;
        FutureTask[] futureTasks=new FutureTask[50];
        for (int i = 0; i < 50; i++) {
            FutureTask futureTask=new FutureTask(new UseCallable(i*20000000,(i+1)*20000000));
            new Thread(futureTask).start();
            futureTasks[i]=futureTask;
        }
        for (int i = 0; i < futureTasks.length; i++) {
            long res= (long) futureTasks[i].get();
             sum+=res;
        }
        end=System.currentTimeMillis();
        System.out.println(sum);
        System.out.println(end-start);
    }
}

守护线程的意义(随着主线程的存在而存在,主线程的消亡而消亡)

public class Deamon {
    public static void main(String[] args) {
        Thread thread1=new Thread(()->
        {   int count=10;
            Thread thread2=new Thread(()->{
                while (true){
                    ThreadUtils.sleep(100);
                    System.out.println("我是守护线程");

                }
            });
            /**
             *  如果不设置为用户thread1的守护线程会进行一直打印
             *  但是如果设置为thread1的守护线程会在thread1结束的时候毁
             * 直接设置为thread1为守护线程会直接挂掉
             */
           
            thread2.setDaemon(true);
            thread2.start();
            while (count>=0) {
                ThreadUtils.sleep(300);
                System.out.println("我是用户线程");
                count--;
            }
            System.out.println("用户线程结束");
            });
//            thread1.setDaemon(true);
            thread1.start();

    }

}

线程的生命周期(创建->就绪->运行->终结)
线程的生命周期

不管等待还是阻塞都会重新返回就绪状态

多线程JMM存在的问题

  • 1、指令重排(对编译的可以打乱)

一个对象的半初始化状态
Cow cow= new Cow();

new 创建

invokespecial init 初始化

ldc 入栈
可以先初始化再new

  • 2、可见性(线程之间不可见的问题)

加入volatile可以强制转到主存区,而不是缓存区(避免了重排和可见性的问题)

  • List item as-if-serial语义 防止单线程结果被改变

  • happens-before语义 防止多线程结果被改变

读写操作的问题,线程之间不可见
在写入时volatile会变得线程可见从而使得线程之间互通
改写isOver 的值为ture跳出循环


public class visibility {
        private *volatile* static boolean isOver = false;

        private static int number = 0;

        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!isOver) {
                    }
                    System.out.println(number);
                }
            });
            thread.start();
            Thread.sleep(1000);
            number = 50;
            isOver = true;
        }


}

3、线程争抢(线程与线程之间抢夺资源的问题)关于锁在下一篇博客中

增加方法同步锁synchronized

4、线程安全的实现方法
在并发量比较大的情况下还是用悲观锁解决
(1)数据不可变(final修饰)
​ 在Java当中,一切不可变的对象(immutable)一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再进行任何线程安全保障的措施,比如final关键字修饰的基础数据类型,再比如说咱们的Java字符串。只要一个不可变的对象被正确的构建出来,那外部的可见状态永远都不会改变,永远都不会看到它在多个线程之中处于不一致的状态,带来的安全性是最直接最纯粹的。比如使用final修饰的基础数据类型(引用数据类型不可以)、比如java字符串,而一旦被创建就永远不能改变。

(2)互斥同步(悲观锁并发策略)
​ 互斥同步是常见的一种并发正确性的保障手段,同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用,互斥是实现同步的一种手段,互斥是因、同步是果,互斥是方法,同步是目的。
​ 在Java中最基本的互斥同步手段,就是synchronized字段,除了synchronize的之外,我们还可以使用ReentrantLock等工具类实现。
(3)非阻塞同步(乐观锁并发策略)
​ 互斥同步面临的主要问题是,进行线程阻塞和唤醒带来的性能开销,因此这种同步也被称为阻塞同步,从解决问题的方式上来看互斥同步是一种【悲观的并发策略】,其总是认为,只要不去做正确的同步措施,那就肯定会出现问题,无论共享的数据是否真的出现,都会进行加锁。这将会导致用户态到内核态的转化、维护锁计数器和检查是否被阻塞的线程需要被唤醒等等开销。

​ 随着硬件指令级的发展,我们已经有了另外的选择,基于【冲突检测的乐观并发策略】。通俗的说,就是不管有没有风险,先进行操作,如果没有其他线程征用共享数据,那就直接成功,如果共享数据确实被征用产生了冲突,那就再进行补偿策略,常见的补偿策略就是不断的重试,直到出现没有竞争的共享数据为止,这种乐观并发策略的实现,不再需要把线程阻塞挂起,因此同步操作也被称为非阻塞同步,这种措施的代码也常常被称之为【无锁编程】,也就是咱们说的自旋锁。我们用cas来实现这种非阻塞同步。

(4)无同步方案(线程各干各的变量到本地)
多个线程需要共享数据,但是这些数据又可以在单独的线程当中计算,得出结果,而不被其他的线程所影响,如果能保证这一点,我们就可以把共享数据的可见范围限制在一个线程之内,这样就无需同步,也能够保证个个线程之间不出现数据征用的问题,说人话就是数据拿过来,我用我的,你用你的,从而保证线程安全,比如说ThreadLocal。

​ ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

public class Test {
    private  static  Integer number=0;
    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<Integer> threadLocal=new ThreadLocal<>();
            Thread thread1=new Thread(new Runnable() {
                @Override
                public void run() {
                    threadLocal.set(number);
                    for (int i = 0; i < 1000; i++) {
                        threadLocal.set(threadLocal.get()+1);
                            System.out.println("线程1打印出"+threadLocal.get());
                }
            }});
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(number);
                    for (int i = 0; i < 1000; i++) {
                        threadLocal.set(threadLocal.get()+1);
                        System.out.println("线程2打印出" + threadLocal.get());
                }
            }});
            thread1.start();
            thread2.start();
        }

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长安归故里♬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值