并发编程(1)-基础

并行和并发

并行:同时执行多个任务。比如两核的CPU并行任务量是两个。

并发:单位时间可以执行的任务数。假如CPU为单核,单位时间为1秒,并发就是指这1秒内CPU执行的任务数。单核CPU在同一时刻时能执行一个线程。那在1秒内可能会交替执行100个线程。即并发量是100。

高并发的好处

1、充分利用CPU的资源

2、提高响应时间

3、可以使代码模块化、异步化、简单化

多线程注意事项

1、线程安全

2、死锁

3、过多的线程会过渡消耗服务器资源,甚至宕机

Java中的多线程

Java打印运行的线程信息:

public class Test {
    public static void main(String[] args) {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 打印线程信息
        System.out.println("打印结果:");
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId() + ":" + threadInfo.getThreadName());
        }
    }
}

/*
 * 打印结果:
 * [6] Monitor Ctrl-Break
 * [5] Attach Listener
 * [4] Signal Dispatcher
 * [3] Finalizer
 * [2] Reference Handler
 * [1] main
*/

Java中有两种创建线程的方式

继承Thread类、实现Runnable接口然后交给Thread运行

Thread是对线程的抽象,Runnable只是对任务的抽象。

Thread中的说明

线程相关方法

start()

启动线程。让线程进入就绪队列等待分配CPU,分到CPU后才调用实现的run()方法。start()方法不能重复调用,不然报【java.lang.IllegalThreadStateException】异常。

run()

线程的主体逻辑方法

yield()

使当前线程让出CPU占有权,让出时间不可设定,也不会释放锁资源,也可能不会持有锁。可以在释放锁资源后再调用yield方法。所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。

sleep()

线程休眠指定时间,不会释放锁资源。

join()

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。比如在线程B 中调用了线程A 的Join()方法,直到线程A 执行完毕后,才会继续执行线程B。

interrupt()

标识中断,线程的isInterrupted()默认为false,比如调用某线程的interrupt()方法则将状态置为true,线程通过判断isInterrupted()中断标识是否被置为true,如果是,则中断线程。Thread.interrupted()方法也可以用来判断中断标识是否被置为true,但它最终会把中断标识重置为false。

wait()

使线程进入WAITING状态,只要等待另外线程的通知或被中断才会返回。

notify() | notifyAll()

通知【一个 | 所有】等待在该对象上的线程,使其从wait方法返回,返回的前提是该线程获取到了对象的锁。没获取到锁的线程重新进入WAITING状态。

死锁状态的线程无法被中断。

线程间的共享和协作

synchronized内置锁

可重入锁。

它确保多个线程在同一时刻,只有一个线程处于方法或同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

对象锁和类锁:

对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class 对象上的。类的对象实例可以有很多个,但是每个类只有一个class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

volatile

保证了不同线程对变量进行操作时的可见性。当一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的。

适用场景:一个线程写,多个线程读

// volatile多线程写不安全示例
public class TestVolatileSafe {
    private volatile Integer count = 0;

    public void add() {
        count++;
    }

    public static void main(String[] args) throws Exception{
        TestVolatileSafe testVolatileSafe = new TestVolatileSafe();

        new ThreadClass(testVolatileSafe).start();
        new ThreadClass(testVolatileSafe).start();

        Thread.sleep(5000);

        System.out.println(testVolatileSafe.count);// 每个线程对count加1万次,最终结果不是2万
    }
}

class ThreadClass extends Thread {
    private TestVolatileSafe testVolatileSafe;

    public ThreadClass(TestVolatileSafe testVolatileSafe){
        this.testVolatileSafe = testVolatileSafe;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            testVolatileSafe.add();
        }
    }
}

ThreadLocal

ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。

spring事务即借助了ThreadLocal类,防止多线程下每个线程的数据库连接对象被修改。

ThreadLocal的方法:

void set(Object value);

Object get();

void remove();

protected Object initialValue();

返回该线程局部变量的初始值,该方法是一个protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1 次调用get()或set(Object)时才执行,并且仅执行1 次。ThreadLocal 中的缺省实现直接返回一个null。

说明:

ThreadLocalMap是ThreadLocal的静态内部类,ThreadLocal内部又有个Entry静态内部类,Entry静态内部类继承了

WeakReference[弱引用]。

ThreadLocalMap是每个线程独有的,ThreadLocalMap的key为ThreadLocal本身,Entry的key即为线程本身。

ThreadLocal引发内存泄漏:

强引用:类似"Object o = new Object();",只要强引用还存在,垃圾收集器永远不会回收掉引用的对象实例。

软引用:是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2 之后,提供了SoftReference 类来实现软引用。

弱引用:也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在JDK 1.2 之后,提供了WeakReference 类来实现弱引用。

虚引用:也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象实例被收集器回收时收到一个系统通知。在JDK 1.2 之后,提供了PhantomReference 类来实现虚引用。

原因分析:

    每个Thread 维护一个ThreadLocalMap,这个映射表的key 是ThreadLocal 实例本身,value 是真正需要存储的Object,也就是说ThreadLocal 本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap 获取value。仔细观察ThreadLocalMap,这个map是使用ThreadLocal 的弱引用作为Key 的,弱引用的对象在GC 时会被回收。这样,当把threadlocal 变量置为null 以后,没有任何强引用指向threadlocal实例,所以threadlocal 将会被gc 回收。这样一来,ThreadLocalMap 中就会出现key 为null 的Entry,就没有办法访问这些key 为null 的Entry 的value,如果当前线程再迟迟不结束的话,这些key 为null 的Entry 的value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value 永远不会被访问到了,所以存在着内存泄露。

解决:

    只有当前thread 结束以后,current thread 就不会存在栈中,强引用断开,Current Thread、Map value 将全部被GC 回收。最好的做法是不在需要使用ThreadLocal 变量后,都调用它的remove()方法,清除数据。

等待/通知机制

nofity()

    通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入waiting状态。

nodifyAll()

    通知所有等待在该对象上的线程

wait()

    调用该方法的线程进入waiting状态,只有等待另外线程的通知或被中断才会返回。调用wait()方法后会释放对象的锁。

wait(long)

    超时等待一段时间,这里的参数时间是毫秒,也就是等待n毫秒,如果没有通知就超时返回。

wait(long, int)

    对于超时时间更细粒度的控制,可以达到纳秒。

等待/通知的范式

    等待方:

        获取对象的锁

        如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件

        条件满足则执行对应的逻辑

    通知方:

        获得对象的锁

        改变条件

        通知所有等待在对象上的线程

发布了5 篇原创文章 · 获赞 0 · 访问量 119

猜你喜欢

转载自blog.csdn.net/u010636239/article/details/105131012