多线程基础知识总结二

2020-10-14  本文已影响0人  闫回
image.jpeg

在这张图中

private static class UseRunnable implements Runnable{

    @Override
    public void run() {

        String threadName = Thread.currentThread().getName();

        while(!Thread.currentThread().isInterrupted()){

        }

        System.out.println(threadName+" interrput flag is "

                +Thread.currentThread().isInterrupted());

    }

}

public static void main(String[] args) throws InterruptedException {

    UseRunnable useRunnable = new UseRunnable();

    Thread endThread = new Thread(useRunnable,"endThread");

    endThread.start();

    Thread.sleep(20);

    endThread.interrupt();

}
什么是线程间的共享?
对象锁和类锁:

一旦某个线程开始获取锁的时候,但是这个线程被其它的锁拿到了,它会在这个锁上面先等待,等待其它的线程把锁先释放了以后再拿锁,线程获取锁这个过程是不能中断的!这时你发出中断请求是没有作用的
synchronized关键字要么是我拿到了要么是我在等待,尝试去拿锁的机制是没有的。
类锁和对象锁相关解释
https://zhuanlan.zhihu.com/p/31537595
https://blog.csdn.net/pengweid/article/details/85711970

什么是线程间的协作?
等待/通知机制

在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法,进入wait()方法后,当前线程释放锁,在从wait()返回前,线程与其他线程竞争重新获取锁,notifyAll方法一旦该对象锁被释放,他们就会去竞争,如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

notify和notifyAll我们应该如何使用?

尽可能用notifyAll,谨慎使用notify(),看代码。
notify只能唤醒一个线程,但是并不能使用notify指定唤醒某一个线程
notifyAll可以唤醒全部的线程

Threadlocal

ThreadLocal,即线程变量,是一个以Threadlocal对象为键、任意对象为值的存储结构,这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值,ThreadLocal往往用来实现变量在线程之间的隔离。
ThreadLocal类接口很简单,只有4个方法
void set(Object value)
设置当前线程的线程局部变量的值
public Object get()
该方法返回当前线程所对应的线程局部变量
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法,需要指出是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显示调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接饭后一个null

public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();RESOURCE代表一个能够存放String类型的ThreadLocal对象,此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

如果某一个变量需要多个线程共同使用那么使用ThreadLocal,线程隔离

每个线程会拥有自己独有的值

public class UseThreadLocal {

static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){

@Override

protected Integer initialValue() {

return 1;

}

};

/**

*/

public void StartThreadArray(){

Thread[] runs = new Thread[3];

for(int i=0;i<runs.length;i++){

runs[i]=new Thread(new TestThread(i));

}

for(int i=0;i<runs.length;i++){

runs[i].start();

}

}

/**

*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,

*/

public static class TestThread implements Runnable{

int id;

public TestThread(int id){

this.id = id;

}

public void run() {

System.out.println(Thread.currentThread().getName()+":start");

Integer s = threadLocal.get();

s = s+id;

threadLocal.set(s);

System.out.println(Thread.currentThread().getName()+" :"

+threadLocal.get());

//threadLocal.remove();

}

}

public static void main(String[] args){

UseThreadLocal test = new UseThreadLocal();

test.StartThreadArray();

}

}

显示锁

Lock接口和synchronized的比较

我们一般的Java程序是靠synchronized关键字实现锁功能的,使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放,synchronized属于Java语言层面的锁,也称为内置锁。
synchronized这种机制,一旦开始获取锁,是不能中断的,也不提供尝试获取锁的机制。
而Lock是由Java在语法层面提供的,锁的获取和释放需要我们明显的去获取,因此被称为显示锁,并且提供了synchronized不提供的机制。

image.jpeg

Lock接口和核心方法

在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。

image.jpeg image.jpeg

synchronized是内置锁,我们看不到拿锁的过程,jdk帮我们做了

显示锁Lock 是一个接口

获取锁和释放锁的过程需要我们程序员手动去操作

tryLock()尝试去获取某把锁,获取到了就返回true,获取不到就返回false,也可加上超时时间。

unLock()释放锁

我们一定要在finally关键字里面释放锁,避免出现异常而无法释放锁

可重入锁ReentrantLock、所谓锁的公平和非公平

而synchronized关键字隐式的支持重进入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁。ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

公平和非公平锁

如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的,公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。

ReentrantLock提供了一个构造函数,能够控制锁是否是公平的,事实上,公平的锁机制往往没有非公平的效率高,原因是,在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟,假设线程A持有一个锁,并且线程B请求这个锁,由于这个锁已被线程A持有,因此B将被挂起,当A释放锁时,B将被唤醒,因此会再次尝试获取锁。与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得,使用以及释放这个锁,这样的情况是一种“双赢”的局面,B获取锁的时刻并没有推迟,C更早地获得了锁,并且吞吐量也获得了提高。

ReentrantLock可重入锁,同一个线程可以反复拿同一把锁,防止自己把自己锁死

synchronized关键字也是可重入锁

public class LockDemo {

private int count = 0;

private Lock lock = new ReentrantLock(true);

public void incr(){

lock.lock();

try{

count++;

}finally {

lock.unlock();

}

}

public synchronized void incr2(){

count++;

incr2();

}

public static void main(String[] args) {

LockDemo lockDemo = new LockDemo();

}

}

锁的公平和非公平?

公平锁,线程在获取锁的时候,先申请的一定先拿到

非公平锁,多个线程在申请一把锁的时候,后面的线程反而先拿到锁

synchronized和ReentrantLock都是非公平锁

synchronized只有非公平锁

ReentrantLock()默认就是非公平锁,ReentrantLock(true)就是公平锁

为什么非公平锁比公平锁的性能更好?

因为上下文切换,要恢复一个被挂起的线程更加的延迟

锁的竞争的时候存在一个被挂起的状态,线程被挂起和线程真正运行因为上下文切换存在一个延迟,非公平锁可以尽可能减少上下文切换的时间,所以非公平锁效率更高

读写锁ReentrantReadWriteLock

Lock和synchronized都是排它锁,就是多个线程抢一把锁的时候,同一时刻有且只有一个线程能拿到锁,也就是同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其它的写线程均被阻塞,读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排它锁有了很大的提升。

除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式,假设在程序中定义一个共享的用作缓存数据结构,它大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的,在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。

ReadWriteLock接口和读写锁ReentrantReadWriteLock,什么情况下用读写锁?

ReentrantReadWriteLock同一时刻允许有多个读线程同时访问,读和写进行分开,性能提升了,大多数的场景都是读,写的情况比较少,比用synchronized性能大大提升了,读写分离 ,读的时候是不允许写的,

1.当我使用lock接口该如何使用等待和通知机制呢?使用Condition接口

image.jpeg

await就是等待

signal相当于notify
使用notifyAll是以对象为监听的
使用signal是以单独的Condition为监听的,唤醒全部还是唤醒某一个取决于你的业务
private Condition kmCond = lock.newCondition();
private Condition siteCond = lock.newCondition();
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式,Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
用Lock和Condition实现等待通知


image.jpeg
上一篇 下一篇

猜你喜欢

热点阅读