java多线程进阶(下)

线程相关类

ThreadLocal

ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内其作用,减少同一个线程内多个函数或者组件之间的一些公共变量的传递的复杂度。

每个线程只能操作自己线程的数据,而不会影响其他线程的数据

基本方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

举个例子:

import java.util.Random;

public class ThreadLocalTest {
    public static void main(String[] args) {
        //循环创建两个线程
        for(int i=0;i<2;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + "get data:" + data);
                    //取得当前线程的示例,并存放数据
                    MyThreadScopeData.getThreadInstance().setName("" + data);
                    MyThreadScopeData.getThreadInstance().setAge("" + data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }

    static class A implements Runnable{
        public void get() {
            MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
            System.out.println("A from " + Thread.currentThread().getName() + " Name:"
                    + myData.getName() + ",Age:" + myData.getAge());
        }

        @Override
        public void run() {
            MyThreadScopeData myData=MyThreadScopeData.getThreadInstance();
            System.out.println("A from " + Thread.currentThread().getName() + " Name:"
                    + myData.getName() + ",Age:" + myData.getAge());
        }
    }

    static class B {
        public void get() {
            MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
            System.out.println("B from " + Thread.currentThread().getName() + " Name:"
                    + myData.getName() + ",Age:" + myData.getAge());
        }
    }
}
//此类的设计很关键,单例模式
class MyThreadScopeData {
    private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<>();
    private MyThreadScopeData() {
    }

    public static MyThreadScopeData getThreadInstance() {
        MyThreadScopeData instance = map.get();
        if (instance == null) {
            instance = new MyThreadScopeData();
            map.set(instance);
        }
        return instance;
    }

    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

运行结果:


以上便实现了不同线程之间多个变量的共享(Name、Age)


Lock接口

lock和synchronized的比较:

Lock提供了比synchronized方法和synchronized代码块更广发的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并支持多个相关的Condition对象。

Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁。

Lock的两个实现类:ReentrantLock(可重入锁)和ReentrantReadWriteLock(可重入读写锁)

这里采用上一篇中的火车售票例子进行修改,代码如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSaleTickets implements Runnable {
    private static int tickets = 100;
    private static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (tickets > 0) {//余票充足
            try {
                Thread.sleep(150);//延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
        if (tickets == 0) {
            System.out.println("票已售完!");
        }
    }

    static void sale() {
        lock.lock();//加锁
        try {
            tickets--;
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "当前余票:" + tickets);
            }
        } finally {
            lock.unlock();//释放锁
        }
    }

    public static void main(String[] args) {
        LockSaleTickets sst = new LockSaleTickets();
        new Thread(sst).start();
        new Thread(sst).start();
        new Thread(sst).start();
        new Thread(sst).start();

    }
}

Condition实现通信

Condition是用来替代传统Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。

·调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

·Conditon中的await()对应Object的wait();

·Condition中的signal()对应Object的notify();

前面说了通过wait和notify实现线程间的通信,这里仍然是修改上一篇的那个例子,子线程循环10次,接着主线程循环100次,又接着回到子线程循环10次,再接着回到主线程又循环100次,如此循环50次。

(上篇地址:https://blog.csdn.net/qq_37094660/article/details/80472657#t8)

代码如下:

package cn.thread;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionCommunication {

    public static void main(String[] args) {
        final Business business = new Business();
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 1; i <= 50; i++) {
                            business.sub(i);
                        }
                    }
                }
        ).start();
        for (int i = 1; i <= 50; i++) {
            business.main(i);
        }
    }

    static class Business {
        Lock lock = new ReentrantLock();//定义一个可重入锁
        Condition condition = lock.newCondition();//condition对象依赖于Lock对象
        private boolean bShouldSub = true;

        public void sub(int i) {
            lock.lock();
            try {
                while (!bShouldSub) {
                    try {
                        condition.await();//当前线程进入等待状态
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 10; j++) {
                    System.out.println("sub thread sequence of " + j + ",loop of " + i);
                }
                bShouldSub = false;
                condition.signal();//唤醒一个等待在Condition上的线程
            } finally {
                lock.unlock();
            }
        }

        public void main(int i) {
            lock.lock();
            try {
                while (bShouldSub) {
                    try {
                        condition.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 100; j++) {
                    System.out.println("main thread sequence of " + j + ",loop of " + i);
                }
                bShouldSub = true;
                condition.signal();
            } finally {
                lock.unlock();
            }
        }
    }
}

利用Lock实现缓存池

原理:数据存放在map对象中检查方法内部是否有此数据,如果没有就数据库中查询,如果有则直接传给调用者

初代缓存:

private Map<String,Object> cache=new HashMap<>();
public Object getData(String key){
    Object value=cache.get(key);
    if(value==null){
        value = "aaa";//实际是去数据库取数据,这里简化了
        cache.put(key,value);
    }
    return value;
}

分析:如果三个用户(线程)同时来查数据,而且此时缓存中没有数据,代码执行value==null过后,三个用户对应的线程则都要去数据库中进行取数据,这样同样对数据库造成了压力,没起到缓存的作用,因此在getData()方法前加上关键字synchronized(同步),表示读数据必须同步执行


public synchronized Object getDate(String key){

但是由于多线程读取数据的时候不需要互斥(如果缓存里面有数据),大家自己读自己的互不影响,写数据的时候为了保护数据才需要互斥,而这里的getData()方法相当于读取数据,加上synchronized关键字反而多余,但是方法内部有写数据的过程,如果不加synchronized关键字修饰,那么该怎么做呢?
声明一个读写锁

private ReadWriteLock rwl=new ReentrantReadWriteLock();
通过读锁写锁来控制读数据间的共享与写数据间的互斥

代码如下:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

//缓存系统
public class CacheDemo {
    private Map<String, Object> cache = new HashMap<>();
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
    }

    /*
    数据存放在map对象中
    检查方法内部是否有此数据,如果没有就数据库中查询,如果有则直接传给调用者
     */
    public Object getData(String key) {
        rwl.readLock().lock();//首先上一个读锁,多个线程来读数据时,
        Object value = null;
        try {
            value = cache.get(key);//从缓存中取数据
            if (value == null) {//如果读数据的时候发现此数据为null,则释放读锁,加写锁
                rwl.readLock().unlock();
                rwl.writeLock().lock();//若有多个线程执行至此步,只有第一个线程能lock,后面的线程都被堵住
                //只有当第一个线程的写操作读操作都执行完后,后面的线程才继续执行
                try {
                    if (value == null) {//这个value==null是判断第一个线程之后的线程,因为第一个线程写数据后,value已经有值了
                        value = "aaa";//实际是去queryDB(),数据库操作
                    }
                } finally {
                    rwl.writeLock().unlock();//数据写完后,释放写锁
                }
                rwl.readLock().lock();
            }
        } finally {
            rwl.readLock().unlock();
        }
        return value;
    }
}

这样就实现了读和写之间互斥,写和写之间互斥




原文出自:https://my.csdn.net/qq_37094660(如需转载请注明出处)

猜你喜欢

转载自blog.csdn.net/qq_37094660/article/details/80483808