多线程与高并发编程

多线程与高并发编程一

线程

什么是线程

程序的一条执行路径

public class ThreadDemo {
    public static void main(String[] args) {
        new T1().start();
        for (int i = 0; i<10; i++){
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("main");
        }
    }
    private static class T1 extends Thread{
        @Override
        public void run(){
            for (int i = 0; i<10; i++){
                try {
                    TimeUnit.MICROSECONDS.sleep(1);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("T1");
            }
        }
    }
}

多线程与高并发编程_第1张图片

新建一个线程

public class NewThread {
    //继承thread
    static class MyThread extends Thread{
        @Override
        public void run(){
            System.out.println("HELLO MyThrwad");
        }

    }
    //实现Runnabe
    static class MyRun implements Runnable{

        @Override
        public void run() {
            System.out.println("HELLO MyRun");
        }
    }
    //@Function
    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRun()).start();
        new Thread(()->{
            System.out.println("Hello Lambda");
        }).start();
    }

启动一个线程

//启动线程的方式 1:Thread 2.Runable 3.Executors.newCachedThrad(线程池)实际上也是1、2的一种

多线程与高并发编程_第2张图片

状态新建一个线程是 NEW状态(没屌用start),调用start之后之后线程 会被线程调度器来执行整体的状态是Runable状态(内部有ready和Runing俩状态),其中ready是就绪状态, 继续状态就是将线程放到cpu的等待队列当中,真正的放到cpu中运行的是Runing状态,处于Runing状态的线程可以通过yield方法重新回到Ready,直到线程被调度器选中执行重新到Runing状态,执行结束后Teminated状态,到达Teminated不能再次start,也不能回到runable状态

多线程与高并发编程_第3张图片

阻塞状态:Blocked 进入synchronized代码块没有获得没有获得锁之前是阻塞状态

等待:waiting 在runable状态是调用了o.wait(time)、t.join(time)、LockSupport.park()进入waiting状态,再调用o.nitify()、o.nitifyAll()、LockSupport.unpark(),又回到Runable状态

时间等待:Timewaiting(时间结束后会自动回去) 在runable状态是调用了Thread.sleep(time)
o.wait(time)、t.join(time)、LockSupport.parkNanos()、LockSupport.parkUntill()时间结束之后自动回到Runable状态

stop()废除不使用

interrupt 底层会使用没有使用它控制业务逻辑的

synchronized

1.在同一个classloader中的classLoader都是单例模式。

2.一个object只能锁住一个对象

什么时候用到多线程

1.起一个线程 加载图片下载文件

  1. MQ
  2. 小程序体现 防止用户脚本刷

模拟单例模式产生的脏数据

import java.util.concurrent.TimeUnit;

/**
 * @Author:
 * @Date: 2022/08/08/22:09
 * @Description:
 **/
//singletonMode 单例模式
public class Account {
    String name;
    double balance;

    public synchronized void set(String name,double balance){
        this.name = name;
       /* try{
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        this.balance = balance;
    }
    public /*synchronized*/ double getBalance(String name){
        return this.balance;
    }
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(() -> account.set("zkj", 100.0)).start();
        try{
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zkj"));
        try{
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zkj"));
    }

}

多线程与高并发编程_第4张图片

new Account的时候就设置了balance这样1s后能读到正确的值100.0,再过2s后能读到正确的值100.0

import java.util.concurrent.TimeUnit;

/**
 * @Author: 
 * @Date: 2022/08/08/22:09
 * @Description:
 **/
//singletonMode 单例模式
public class Account {
    String name;
    double balance;

    public synchronized void set(String name,double balance){
        this.name = name;
        try{
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public /*synchronized*/ double getBalance(String name){
        return this.balance;
    }
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(() -> account.set("zkj", 100.0)).start();
        try{
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zkj"));
        try{
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zkj"));
    }

}

多线程与高并发编程_第5张图片

new Account的时候就没有设置balance,而是等待1s后进行设置balance,这样1s后能不读到正确的值100.0,而是默认值0,再过2s后能读到正确的值100.0

import java.util.concurrent.TimeUnit;

/**
 * @Date: 2022/08/08/22:09
 * @Description:
 **/
//singletonMode 单例模式
public class Account {
    String name;
    double balance;

    public synchronized void set(String name,double balance){
        this.name = name;
        try{
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public synchronized double getBalance(String name){
        return this.balance;
    }
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(() -> account.set("zkj", 100.0)).start();
        try{
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zkj"));
        try{
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zkj"));
    }

}

new Account的时候就没有设置balance,而是等待1s后进行设置balance,但是由于getBalance操作设置的了锁,这样无法读取数据,只能等待设置了值之后,再进行读操作,这样1s后能读到正确的值100.0,而是默认值0,再过2s后能读到正确的值100.0

业务逻辑允许不枷锁就不枷锁 枷锁会影响效率百倍的差距

synchronized可重入的锁

一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍会得到该对象的锁,也就是说synchronized获得的是可重入的锁

import java.util.concurrent.TimeUnit;

/**
 * @Date: 2022/08/09/00:18
 * @Description: 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候
 * 仍会得到该对象的锁,也就是说synchronized获得的是可重入的锁
 **/
public class reentrant {
    synchronized void m1(){
        System.out.println("m1 start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        m2();
        System.out.println("m1 end");
    }

    synchronized void m2() {
        System.out.println("m1 start");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1 end");
    }

    public static void main(String[] args) {
        new reentrant().m1();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uc1vByy-1660233819593)(…/Library/Application Support/typora-user-images/image-20220809214249757.png)]

M1()方法由某线程创建,m2()也需要枷锁是同一个线程的同一个锁,m2()也会得到这把锁

eg:多线程与高并发编程_第6张图片

如果synchronize不是可重入的锁子类调用父类的方法会死锁

程序在执行过程中,如果出现异常,默认情况锁会被释放

import java.util.concurrent.TimeUnit;

/**
 * @Date: 2022/08/10/21:29
 * @Description:
 **/
public class synceexception {
    /**
     * 程序在执行过程中,如果出现异常,默认情况锁会被释放
     * 所以,在并发处理的过程,有异常要多加小心,不然可能会发生不一致的情况,
     * 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适
     * 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据
     * 因此要非常小心的处理同步业务逻辑中的异常
     */
    int count = 0;
    synchronized void m() {
        System.out.println(Thread.currentThread().getName() + "start");
        while (true) {
            count ++;
            System.out.println(Thread.currentThread().getName() + "count =" + count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5){
                int i = 1/0; //此处抛出异常,锁将被释放,要不想被释放,可以在此处加一个catch,之后让循环继续
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        synceexception synceexception = new synceexception();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                synceexception.m();
            }
        };
        new Thread(runnable,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(runnable,"t2").start();
    }
}

多线程与高并发编程_第7张图片

可见t1抛出异常的同时释放了锁,t2获得了锁

Synchronized 的底层实现(hotspot)

jdk早期的是重量级锁是向操作系统申请的(OS),后来随着java开始有越来越多的高并发的项目,Synchronized的效率就太低了,经过改造才有了现在的锁

现在的锁是有锁升级的概念

偏向锁:sync(Object)如果只有一个线程 只markwoed 记录这个线程ID

自选锁 :此时如果出现了线程争用这个锁 争用的线程就会自旋起来(默认的次数是10)此时锁升级为自旋锁

重量级锁:10次之后会升级为重量级锁,向OS操作系统申请资源,此时的锁不在占用cpu,变为等待状态,但是锁只能升级不能降级

Atomicl 、lock都是自旋锁,占用cpu但是不访问操作系统,是在用户态处理效率要高于内核态

什么时候使用自旋锁,什么时候用重量级锁(OS进入等待队列)

执行时间长的使用重量级锁,

你可能感兴趣的:(Java基础,java,面试,开发语言)