Java多线程

2023-05-16

一、基础概念

1、CPU核心数和线程数

多核心指的是单芯片多处理器,将多个CPU集成到同一个芯片内,不同的CPU可以单独的运行程序。目前主流的CPU有四核、六核、八核。增加核心数目的是为了增加线程数,一般情况下它们是1:1对应关系,也就是说八核CPU一般拥有八个线程。但Intel引入超线程技术后,使核心数与线程数形成1:2的关系。

2、CPU时间片轮转机制

我们平时在开发的时候,感觉并没有受CPU核心数的限制,想启动线程就启动线程,哪怕是在单核CPU上,是因为操作系统提供了一种CPU时间片轮转机制,时间片轮转调度使用的是RR调度算法。时间片轮转机制要注意的时间片的长度,从一个线程切换到另一个线程是需要时间的,因为需要保存核装入寄存器的值及内存映像,更新各种表格和队列等。我们把线程间的切换称为上下文切换,假设上下文切换需要的时间是5ms,如果设定的时间片是20ms,则在每次做完20ms的有用工作后,还需要花5ms来进行线程的切换,CPU时间的20%被浪费在管理开销上;可是如果设定的时间片过长,假设是500ms,这是浪费的时间只有1%,如果同时有10个交互用户按下回车键,最不幸的一个进程要等5s才能获取运行机会。所以时间片设得太短会导致过多的进程切换,降低了CPU效率;而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100毫秒通常是一个比较合理的折中。

3、进程和线程

进程是操作系统进行资源分配的最小单位,资源包括CPU、内存空间、磁盘IO等。线程是进程的一个实体,线程是CPU调度和分派的最小单位。同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程和CPU之间是没有必然关系的,因为线程是CPU调度的单位。

4、并行和并发

并行:指应用能够同时执行不同的任务。

并发:指应用能够交替执行不同的任务。当谈论并发的时候,一定要加个单位时间,也就是单位时间内并发量是多少。

两者区别:一个是同时执行,一个是交替执行。

5、高并发编程的好处

1)充分利用CPU的资源;

2)加快相应用户的时间;

3)使你的代码模块化、异步化、简单化。

注意事项:

1)线程之间的安全性(因为同一进程里面的线程资源是共享的);

2)线程之间的死循环过程;

3)线程太多会将服务器资源耗尽形成死机。

 

二、Java里的线程

1、线程的启动与中止

启动:

1)X extends Thread,重写run()方法;

2)X implements Runnable,实现run()方法,然后交给Thread运行;

3)X implements Callable,实现call()方法,然后交给Thread运行。

public class NewThread {

    //1、拓展Thread类,覆盖run()方法
    private static class UseThread extends Thread {

        @Override
        public void run() {
            super.run();
            System.out.println("I am extends Thread");
        }
    }

    //2、实现接口
    //2.1、实现Runnable接口
    private static class UseRun implements Runnable {

        @Override
        public void run() {
            System.out.println("I am implements Runnable");
        }
    }


    //2.2、实现Callable接口,允许有返回值
    private static class UseCall implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println("I am implements Callable<String>");
            return "CallResult";
        }
    }

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

        //1
        UseThread useThread = new UseThread();
        useThread.start();

        //2
        UseRun useRun = new UseRun();
        new Thread(useRun).start();

        //3
        UseCall useCall = new UseCall();
        FutureTask<String> futureTask = new FutureTask<>(useCall);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}

1)、2)方式都有的一个缺陷是在执行完任务之后无法获取执行结果。从Java1.5开始,提供了Callable和Future,通过它们可以在任务执行完毕后得到任务执行的结果。Callable是一个接口,在它里面只声明了一个call()方法,call()方法返回值类型就是传递进来的V类型。

Future是对具体的Runnable或Callable任务的执行结果进行取消、查询是否完成、获取结果的。

因为Future是一个接口,所以无法直接用来创建对象使用的,因此就有了FutureTask,FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。事实上,FutureTask是Future接口的一个唯一实现类。

注意:Thread类是java语言唯一对线程的抽象,Runnable、Callable不是线程,是对任务的抽象。

中止:

如果run()方法执行完了,或者抛出了一个未处理的异常导致线程提前结束属于线程的自然终止。如果调用线程的suspend()、resume()、stop()方法来执行暂停、恢复和停止操作,属于线程的手动中止,但是这些API是过期的,也就是不建议使用,因为这些方法不会释放已经占有的资源(如锁),容易引发死锁问题,带来副作用。

安全的中止其他线程是通过调用某个线程A的interrupt()方法对其进行中断操作,但是调用了interrupt()方法并不意味着A线程就中止了,只是给A发送了中断请求,A线程是否要停止自己的工作,完全取决于A,所以A线程也可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过方法isInterrupted()或静态方法Thread.interrupted()检查自身的中断标志位是否为true来进行响应。isInterrupted()和Thread.interrupted()方法的区别是Thread.interrupted()在判断完中断标志位位true以后会再将中断标识位改为false。

public class EndThread {

    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" interrupt flag="+isInterrupted());
            /**
             * isInterrupted()和static方法interrupted()方法的区别:
             * Thread.interrupted()在判定完中断标志位以后又把isInterrupted()置为false
             */
            while (!isInterrupted()){
            //while (!Thread.interrupted()){
                System.out.println(threadName+" inner interrupt flag="+isInterrupted());
            }
            System.out.println(threadName+" interrupt flag="+isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //这里只是创建了一个线程对象
        Thread endThread = new UseThread("endThread");
        //执行了start()方法才真正的开启了线程,start()方法只能调用一次,多次调用会报一个运行时的错误
        endThread.start();
        Thread.sleep(20);
        endThread.interrupt();

    }
}

isInterrupted()是线程中的方法,所以在Runnable或Callable中是不能调用这个方法的。

  private static class UseRunnable implements Runnable{

        @Override
        public void run() {
            String threadName=Thread.currentThread().getName();
            //isInterrupted()方法是Thread的方法,这样调用会找不到这个方法
            //while (!isInterrupted()){
            while (Thread.currentThread().isInterrupted()){
                System.out.println("********");
            }
        }
    }

2、run()方法和start()方法

我们通过new Thread()其实只是new出来了一个Thread的实例,线程还没有启动起来,只有执行了start()方法,线程才真正的启动起来。start()方法调用了start0()native方法,start()方法让一个线程从新建状态进入就绪状态等待分配CPU,分到CPU以后才调用run()方法,start()方法不能重复调用。run()方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,可以被单独调用。

public class StartAndRun {

    public static class ThreadRun extends Thread {

        @Override
        public void run() {
            System.out.println(" I am " + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {

        ThreadRun thread = new ThreadRun();
        //thread.run();//执行完以后打印的还是main线程的内容,打印结果: I am main
        thread.start();//打印的是新创建的线程的内容 打印结果:I am Thread-0
    }
}

3、线程常用方法

join()方法:把指定的线程加入到当前线程,可以将交替执行的线程合并为顺序执行的线程。join()方法可以不断反复插队的。比如A线程正在执行的过程中,调用了B线程的join()方法,然后B线程开始执行,在B线程执行的过程中,又调用了C线程的join()方法,然后C线程开始执行,等C线程执行结束了继续执行B线程,等B线程执行结束后再继续执行A线程。

yield()方法:是当前线程让出CPU占有权,但让出的时间是不可设定的,也不会释放锁资源,也有可能执行了yield()方法的线程在进入就绪状态后又马上进入运行状态。

 

sleep()方法:让当前线程休眠,休眠以后进入阻塞状态,休眠过程中不释放锁资源

wait()方法、notify()、notifyAll()方法后边会详细讲解。

三、线程间的共享和协作

asynchronized关键字:Java支持多个线程同时访问一个对象或者对象的成员变量,asynchronized关键字可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一时刻只能有一个线程处于方法或者同步块中,保证线程对变量访问的可见性和排他性,又称为内置锁机制。

1、线程间的共享

对象锁和类锁:

对象锁是用于对象实例方法或一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。类的对象实例可以有多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁,所以不同对象实例的类锁之间是互相影响的。需要注意的是:类锁只是一个概念上的东西,类锁其实锁的是每个类对应的class对象。

对象锁:

public class SynClzAndInst {

    private static class SynObject implements Runnable {
        private SynClzAndInst synClzAndInst;

        public SynObject(SynClzAndInst synClzAndInst) {
            this.synClzAndInst = synClzAndInst;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..." + synClzAndInst);
            //synClzAndInst.instance();
            synClzAndInst.instance2();
        }
    }

    /**
     * 对象锁
     */
    private synchronized void instance() {
        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " synInstance is going..." + this.toString());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " synInstance ended " + this.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 对象锁
     * 和上边instance()方法的效果是一样的
     */
    private Object obj2 = new Object();
    private void instance2() {
        synchronized (obj2) {
            try {
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " synInstance is going..." + this.toString());
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " synInstance ended " + this.toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 对象锁
     * 和上边instance()方法的效果是一样的
     */
    private void instance3() {
        synchronized (this) {
            try {
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " synInstance is going..." + this.toString());
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " synInstance ended " + this.toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        //这样执行t1和t2之间不会相互影响,因为使用的是对象锁
        //执行结果:
        //TestInstance is running...com.practice.generic.thread.SynClzAndInst@33fb22d2
        //TestInstance is running...com.practice.generic.thread.SynClzAndInst@29fa0068
        //Thread-0 synInstance is going...com.practice.generic.thread.SynClzAndInst@33fb22d2
        //Thread-1 synInstance is going...com.practice.generic.thread.SynClzAndInst@29fa0068
        //Thread-0 synInstance ended com.practice.generic.thread.SynClzAndInst@33fb22d2
        //Thread-1 synInstance ended com.practice.generic.thread.SynClzAndInst@29fa0068
//        SynClzAndInst synClzAndInst1 = new SynClzAndInst();
//        Thread t1 = new Thread(new SynObject(synClzAndInst1));
//        SynClzAndInst synClzAndInst2 = new SynClzAndInst();
//        Thread t2 = new Thread(new SynObject(synClzAndInst2));
//        t1.start();
//        t2.start();

        //这样执行t1和t2之间会有影响,t1执行了才执行t2
        //执行结果:
        //TestInstance is running...com.practice.generic.thread.SynClzAndInst@29fa0068
        //TestInstance is running...com.practice.generic.thread.SynClzAndInst@29fa0068
        //Thread-1 synInstance is going...com.practice.generic.thread.SynClzAndInst@29fa0068
        //Thread-1 synInstance ended com.practice.generic.thread.SynClzAndInst@29fa0068
        //Thread-0 synInstance is going...com.practice.generic.thread.SynClzAndInst@29fa0068
        //Thread-0 synInstance ended com.practice.generic.thread.SynClzAndInst@29fa0068
        SynClzAndInst synClzAndInst1 = new SynClzAndInst();
        Thread t1 = new Thread(new SynObject(synClzAndInst1));
        Thread t2 = new Thread(new SynObject(synClzAndInst1));
        t1.start();
        t2.start();
    }
}

类锁:

class SynClass extends Thread {

    @Override
    public void run() {
        System.out.println("TestClass is running...");
        //synClass();
        synStaticObject();
    }

    /**
     * 类锁
     */
    private static synchronized void synClass() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " synclass going...");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " synClass end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 类锁
     * 和上边synClass()方法的效果是一样的
     * 类似于类锁,因为obj在全虚拟机只有一份
     */
    private static Object obj = new Object();

    public void synStaticObject() {
        synchronized (obj) {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " synclass going...");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " synClass end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class SynClzAndInst {

    public static void main(String[] args) {
        //相互之间会有影响
        //执行结果:
        //TestClass is running...
        //TestClass is running...
        //Thread-0 synclass going...
        //Thread-0 synClass end
        //Thread-1 synclass going...
        //Thread-1 synClass end
        SynClass synClass = new SynClass();
        synClass.start();
        SynClass synClass2 = new SynClass();
        synClass2.start();
    }
}

2、线程间的协作

等待/通知机制

等待和通知的标准范式:

等待方:

1)获取对象的锁

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

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

synchronized(对象){

   while(条件不满足){

      对象.wait();

}

    处理对应的逻辑

}

通知方:

1)获得对象的锁。

2)改变条件。

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

synchronized(对象){

    改变条件

    对象.notifyAll();

}

wait()和notify()、notifyAll()方法只能在同步方法或同步块中调用。在调用wait()之前,线程必须要获得该对象的对象级别锁,进入wait()方法后,当前线程释放锁,通知方和其他线程进行竞争获取锁,通知方获取到锁并改变条件,通知方释放锁(通知方线程退出调用了notifyAll的synchronized代码块后才释放锁)后,被唤醒的线程将会竞争获取该锁。

尽量使用notifyAll,notify只唤起一个线程,如果同时开启着多个线程,可能唤醒的不是自己希望唤醒的线程。只有一个线程的时候,才能保证唤醒的是自己希望的线程。

public class Express {

    private static Express express = new Express(0, Express.CITY);

    public final static String CITY = "ShangHai";
    private int km;//快递运输里程数
    private String site;//快递到达地点

    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    public synchronized void changeKm() {
        this.km = 101;
        notifyAll();
    }

    public synchronized void changeSite() {
        this.site = "BeiJing";
        notifyAll();
    }

    public synchronized void waitKm() {
        while (this.km < 100) {
            try {
                System.out.println("等待里程数大于100中。。。");
                wait();
                System.out.println(" check km thread [" + Thread.currentThread().getName() + "] is be notifyed");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(" the km is " + this.km + ", i will change db");
    }

    public synchronized void waitSite() {
        while (CITY.equals(this.site)) {
            try {
                System.out.println("等待城市不再是ShangHai中。。。");
                wait();
                System.out.println(" check site thread [" + Thread.currentThread().getName() + "] is be notifyed");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(" the site is " + this.site + ", i will call user");
    }

    private static class CheckKm extends Thread {
        @Override
        public void run() {
            express.waitKm();
        }
    }

    private static class CheckSite extends Thread {
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new CheckSite().start();
        new CheckKm().start();

        Thread.sleep(1000);

        /**
         * 这里如果只是更改站点,waitKm()方法也会被唤醒,
         * 只不过检查了一遍条件,发现不满足,重新进入wait()方法
         * 因为notifyAll()方法会唤醒所有的
         */
        express.changeSite();
        //express.changeKm();
    }
}

最终的执行结果是:

因为使用的是notifyAll方法,所以即使代码中没有执行changeKm()方法执行更改里程数的操作,监听里程数的线程也会被唤醒,唤醒之后执行wait()方法后边的代码,然后再一次检查条件。

四、ThreadLocal

多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的另一种规避多线程访问出现线程不安全问题的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

如下,在没有使用ThreadLocal的时候,三个线程同时访问count变量,导致最后的打印结果每次都不一样。

public class NoThreadLocal {

    static Integer count=new Integer(1);

    public static class TestTask implements Runnable{

        int num;

        public TestTask(int num) {
            this.num = num;
        }

        @Override
        public void run() {
            count=count+num;
            System.out.println(Thread.currentThread().getName()+":"+count);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            Thread thread=new Thread(new TestTask(i));
            thread.start();
        }
    }
}

这是两次执行上边代码的结果,两次结果不一样,因为上一次线程对count的修改会影响下一次线程对count的使用。 

下边是使用了ThreadLocal的代码:

public class UseThreadLocal {

    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static class TestTask implements Runnable {

        int num;

        public TestTask(int num) {
            this.num = num;
        }

        @Override
        public void run() {
            Integer value = threadLocal.get();
            value = value + num;
            System.out.println(Thread.currentThread().getName() + ":" + value);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            Thread thread=new Thread(new TestTask(i));
            thread.start();
        }
    }
}

上边代码的执行结果是:

使用了ThreadLocal以后,每一个线程会根据一个ThreadLocal对象查询到绑定在这个线程上的一个值,ThreadLocal往往用来实现变量在线程之间的隔离。ThreadLocal类比较简单,我们常用的有四个方法:

-void set(Object value):设置当前线程的线程局部变量的值。

-Object get():获取当前线程所对应的线程局部变量的值。

-void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用。需要注意的是,当线程结束后,对应线程的局部变量将自动被垃圾回收机制回收,所以不用显式的调用该方法清楚内存。

-Object initialValue():返回该线程局部变量的初始值。

五、显示锁Lock

synchronized关键字是Java语言层面的锁,称之为内置锁,它的缺点是:1、不能中断,一旦某个线程进入就绪阶段,就会等待其他线程释放锁,其他线程将锁释放后,就会去抢锁,这个过程不能中断。2、没有尝试获取锁的机制,要么就是抢到了,要么就是没抢到,继续等待,不能说没抢到锁,先去处理其他事,其他事处理完了再来尝试获取锁的过程。

Lock是有Java语法层面提供的,锁的获取和释放需要我们明显的去获取。需要注意的是,一定要在finally关键字里面释放锁,如果不在finally里面,一旦抛了异常,锁就没法释放了。

Lock是一个接口,里面的几个核心方法是:

方法名称描述
void lock()

获取锁,调用该方法当前线程将会获取锁;

获取到锁后,从该方法返回

void lockInterruptibly() throws InterruptedException

可中断的获取锁,和lock的区别在于该方法会

响应中断,即在锁的获取中可以中断当前线程,抛出异常

boolean tryLock()

尝试非阻塞的获取锁,调用该方法后立刻返回。

如果能获取到返回true,否则返回false。

boolean tryLock(long time,TimeUnit unit) throws InterruptedException

超时的获取锁:

1、当前线程超时时间内获取到锁,返回true

2、超时时间内被中断,抛出异常

3、超时时间结束,返回false

void unlock()释放锁

 

1、可重入锁ReentrantLock

允许一个线程反复多次的去拿同一把锁,一般在加锁的递归方法中会用到。ReentrantLock和synchronized都是可重入锁。

public class LockDemo {

    private int count = 0;
    private Lock lock = new ReentrantLock();

    /**
     * 对于非可重入锁,如果这样反复的去拿同一把锁,这个线程会自己把自己锁死。
     */
    public void incr() {
        lock.lock();
        //这里一定要加一个try catch,如果不加的话lock()和unlock()方法之间报错了就不执行unlock()方法了
        try {
            while (count < 10) {
                System.out.println(count);
                count++;
                incr();
            }
        } finally {
            lock.unlock();
        }
    }

    public synchronized void incr2(){
        while (count<10){
            System.out.println(count);
            count++;
            incr2();
        }
    }

    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        //lockDemo.incr();
        lockDemo.incr2();

    }
}

2、公平和非公平锁

如果在时间上,先对锁进行获取的请求一定先获取到锁,那么这个锁是公平的,反之,是不公平的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。synchronized是非公平锁。

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

3、读写锁ReentrantReadWriteLock

上边提到的那些锁,都是排他锁,这些锁在同一时刻只允许一个线程进行访问。而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程均被阻塞。读写锁维护了两个锁,一个读锁和一个写锁。

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

class Student{
    private String name="default";
    private int age=0;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public interface StudentService {
    public  Student getStudent();
    public void setStudentName(String name);
}
public class UseSyn implements StudentService {

    private Student student;

    public UseSyn(Student student) {
        this.student = student;
    }
    
    @Override
    public synchronized Student getStudent() {
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return this.student;
    }

    @Override
    public synchronized  void setStudentName(String name) {
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        student.setName(name);
    }
}
public class UseRwLock implements StudentService{

    private Student student;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock getLock = lock.readLock();//读锁
    private final Lock setLock = lock.writeLock();//写锁

    public UseRwLock(Student student) {
        this.student = student;
    }

    @Override
    public Student getStudent() {
        getLock.lock();
        try {
            Thread.sleep(5);
            return this.student;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        } finally {
            getLock.unlock();
        }
    }

    @Override
    public void setStudentName(String name) {
        setLock.lock();
        try {
            Thread.sleep(5);
            student.setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            setLock.unlock();
        }
    }
}
public class BusiApp {

    //读操作
    private static class GetThread implements Runnable {

        private StudentService studentService;

        public GetThread(StudentService studentService) {
            this.studentService = studentService;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            studentService.getStudent();
            System.out.println(Thread.currentThread().getName() + "读学生信息耗时:" + (System.currentTimeMillis() - start) + "ms");
        }
    }

    //写操作
    private static class SetThread implements Runnable {

        private StudentService studentService;

        public SetThread(StudentService studentService) {
            this.studentService = studentService;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            studentService.setStudentName("张韶涵");
            System.out.println(Thread.currentThread().getName() + "写学生信息耗时:" + (System.currentTimeMillis() - start) + "ms");
        }
    }

    public static void main(String[] args) {
        Student student = new Student();
        StudentService studentService = new UseSyn(student);
        //StudentService studentService = new UseRwLock(student);
        for (int i = 0; i < 3; i++) {
            Thread setT = new Thread(new SetThread(studentService));
            for (int j = 0; j < 10; j++) {
                Thread getT = new Thread(new GetThread(studentService));
                getT.start();
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            setT.start();
        }
    }
}

运行BusiApp类,创建UseSyn对象和创建UseRwLock对象所需要的时间对比:

排他锁读线程所需时间越来越多
排他锁读线程花费的时间越来越多

 

读写锁读线程花费的时间几乎无变化

4、Condition接口

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。

public class ExpressCond {

    public final static String CITY = "ShangHai";
    private int km;
    private String site;
    private Lock lock = new ReentrantLock();
    private Condition kmCond = lock.newCondition();
    private Condition siteCond = lock.newCondition();

    public ExpressCond(int km, String site) {
        this.km = km;
        this.site = site;
    }

    public void changeKm() {
        lock.lock();
        try {
            this.km = 101;
            kmCond.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void changeSite() {
        lock.lock();
        try {
            this.site = "BeiJing";
            siteCond.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void waitKm() {
        lock.lock();
        try {
            while (this.km < 100) {
                System.out.println("等待里程数大于100中。。。");
                kmCond.await();
                System.out.println(" check km thread[" + Thread.currentThread().getName() + "] is be notifyed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println(" the Km is " + this.km + " , I will change db");
    }

    public void waitSite() {
        lock.lock();
        try {
            while (CITY.equals(this.site)) {
                System.out.println("等待城市不再是ShangHai中。。。");
                siteCond.await();
                System.out.println(" check site thread[" + Thread.currentThread().getName() + "] is be notifed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println(" the site is " + this.site + ", I will call user");
    }
}
public class TestCond {

    private static ExpressCond expressCond = new ExpressCond(0, ExpressCond.CITY);

    private static class CheckKm extends Thread {

        @Override
        public void run() {
            expressCond.waitKm();
        }
    }

    private static class CheckSite extends Thread {

        @Override
        public void run() {
            expressCond.waitSite();
        }
    }

    public static void main(String[] args) {

        new CheckKm().start();
        new CheckSite().start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        expressCond.changeSite();
        //expressCond.changeKm();
    }
}

上边代码的执行结果是:

执行结果没有像上边三.2的结果一样监听里程数变化的线程也被唤醒是因为这里等待、改变里程数和等待、改变地点用的是不同的等待/通知Condition对象。

同样,如果把signalAll改为signal,就会只有一个线程被唤醒。

将ExpressCond类中的changeSite()方法中的signalAll改为signal

 public void changeSite() {
        lock.lock();
        try {
            this.site = "BeiJing";
            siteCond.signal();
        } finally {
            lock.unlock();
        }
 }

TestCond类中的main方法中多开几个监听里程数变化的线程,

public static void main(String[] args) {

        for(int i=0;i<3;i++){
            new CheckSite().start();
        }
        new CheckKm().start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        expressCond.changeSite();
        //expressCond.changeKm();
}

最后的执行结果是:

可以看到,最终只有一个线程被唤醒。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程 的相关文章

  • 编译mission planner地面站

    编译mp地面站步骤 xff1a 下载最新mp源码 xff1a https github com ArduPilot MissionPlanner 下载最新地面站msi xff08 编译需要引用 xff09 xff1a http firmwa
  • DJI Lightbridge2接收端数据解析

    因为XXXX xff0c 需要对DJI的指令系统做研究 xff0c 首先解析的是 LB2 xff0c 也拆解了七七八八了 xff0c 现在对LB2天空端DBUS口的数据协议进行解析 xff0c 供电子 嵌入式同学进行学习研究 DBUS采用4
  • 室内定位TDOA-UWB实现无线同步的一种方法

    定位中 xff0c 如采用TDOA方式进行坐标解算 xff0c UWB基站间就需要进行时间同步 xff0c 时间同步可以采用有线同步或无线同步 xff1b 找到一篇论文中 xff0c 对无线同步有着巧妙处理 xff0c 特写博客记录一下 U
  • 安装Visual Assit 可在VS2010/VC6.0中使用

    1 1 vs2010中安装 下载Visual Assist X10 6 1823 0 rar破解版 第一步 xff1a 关闭VS2010 第二步 安装 双击安装包中的VA X Setup1823 vsix xff0c 安装 xff1b 第三
  • C++空指针调用类成员函数

    C 43 43 空指针可以调用类成员函数 xff0c 但是 不能调用 类中的 虚函数 我们知道 xff0c 一个对象的指针可以调用它的成员函数和虚函数 xff0c 那么如果一个指向空nullptr的指针 xff0c 能不能调用它的成员函数和
  • MissionPlanner日志保存方法

    闪存日志 目录 闪存日志 日志类型 闪存 VS 数传日志 设置你想要记录的数据 用Mission Planner下载日志 查看内容 详细信息 针对APM Copter 查看KMZ文件 视频教程 日志类型 闪存 VS 数传日志 有两种方法可以
  • Layui的laydate日期组件限制只能选择工作日

    如题 xff0c 在使用Layui的laydate日期组件时 xff0c layui只给我们提供了日期组件的min max配置 xff0c 分别对应最小可选时间和最大可选时间 xff0c 但是如果我们需求是只能选择工作日 xff08 周一至
  • python(5):TypeError: xxx() got an unexpected keyword argument ‘xxx‘

    定义了一个python函数 xff0c 调用时出现报错如下 xff1a Traceback most recent call last File 34 gaussian kernel py 34 line 18 in lt module g
  • 对《Java编程思想》读者的一点建议

    Java 编程思想 这本书在豆瓣的评分高达 9 1 分 xff0c 但我总觉得有点虚高 记得刚上大学那会 xff0c 就在某宝上买了一本影印版的 Java 编程思想 xff0c 但由于初学 Java xff0c 对编程极度缺乏信心 xff0
  • 强烈推荐10本程序员必读的书

    经常有读者私下问我 xff0c 能否推荐几本书 xff0c 以便空闲的时间读一读 于是我跑去自己的书架上筛选了 10 本我最喜欢的书 xff0c 你可以挑选感兴趣的来读一读 01 代码整洁之道 我可以这么肯定地说 xff1a 代码整洁之道
  • 教妹学Java(二十 七):this 关键字的用法

    你好呀 xff0c 我是沉默王二 xff0c xff08 目前是 xff09 CSDN 周排名前十的博客专家 这是 教妹学 Java 专栏的第二十七篇 xff0c 今天我们来谈谈 Java 的 this 关键字 this 关键字有哪些用法
  • PID控制原理

    PID控制原理 PID即 xff1a Proportional xff08 比例 xff09 Integral xff08 积分 xff09 Differential xff08 微分 xff09 的缩写 xff0c PID控制算法是结合比
  • ROS入门:IMU&GPS融合定位实例

    1 声明 xff1a a 本文主要针对IMU amp GPS融合定位仿真环境的搭建过程进行讲解 xff0c 而没有对具体原理的介绍 b 本人作为技术小白 xff0c 完全参考了https zhuanlan zhihu com p 15266
  • vSLAM研究综述:2010-2016

    作为vSLAM领域小白 xff0c 学习完 视觉SLAM十四讲 后 xff0c 抱着学习的心态研究了论文Visual SLAM algorithms a survey from 2010 to 2016 作为入门的第一步 xff0c 会有很
  • ROS学习:cv_bridge与opencv版本冲突三种解决方案

    cv bridge与opencv版本冲突三种解决方案 1 问题描述 xff1a 2 解决方案 xff1a 2 1 不使用cv bridge包2 2 令cv bridge使用opencv版本切换为自己工程所使用的版本2 3 下载cv brid
  • Kubernetes实战指南(三十四): 高可用安装K8s集群1.20.x

    文章目录 1 安装说明2 节点规划3 基本配置4 内核配置5 基本组件安装6 高可用组件安装7 集群初始化8 高可用Master9 添加Node节点10 Calico安装11 Metrics Server部署12 Dashboard部署 1
  • 烤四轴

    烤四轴方法 xff0c 先盗图一张 先调试内环后外环 xff08 不明白自行搜索串级PID xff09 1 在飞机的起飞油门基础上进行 PID 参数的调整 xff1b 2 将角度外环去掉 xff0c 将打舵量作为内环的期望 xff1b 3
  • 最全面的linux信号量解析

    2012 06 28 15 08 285人阅读 评论 0 收藏 编辑 删除 信号量 一 xff0e 什么是信号量 信号量的使用主要是用来保护共享资源 xff0c 使得资源在一个时刻只有一个进程 xff08 线程 xff09 所拥有 信号量的
  • ubuntu(20):xargs:clang-format: 没有那个文件或目录与ubuntu18.04安装clang-format

    1 报错排查 xff1a xargs clang format 没有那个文件或目录 运行脚本中的命令如下 xff1b 需要注意这里的clang format后面没有跟数字 修改前脚本 find dogm demo dogm include
  • ORA-28000 the account is locked处理办法

    启动项目的时候提示ORA 28000 the account is locked 这是因为用户被锁定了 oracle11g中默认在default概要文件中设置了 FAILED LOGIN ATTEMPTS 61 10次 xff0c 当输入密

随机推荐

  • 安卓手机 相机和IMU数据获取标定 在VINS-MONO运行自己的数据集(含打包方法) (非常详细一步一步来)

    Android手机上图像和IMU数据采集的方法 网上有相关的教程 xff0c 但都讲的很模糊 xff0c 而且不全 xff0c 甚至还有人要收费 自己完整做了一遍发现还是有些麻烦 xff0c 固记录下来供大家参考 xff0c 希望能帮到大家
  • ros学习笔记--如何看可视化的话题与节点

    输入 rosrun rqt graph rqt graph 可以打开一个界面观察节点与话题的关系 绿色和蓝色的是节点 红色的是话题
  • opencv 环境相关

    拷贝志强服务器的环境需要配置下opencv 安装opencv的一些依赖项 xff0c 防止编译不通过 1 拷贝的库放在 opt下 xff0c 改名字为libs x64 2 安装opencv的依赖项 sudo apt get install
  • ROS CMakeLists 写法

    SET CMAKE BUILD TYPE 34 Debug 34 SET CMAKE CXX FLAGS DEBUG 34 ENV CXXFLAGS O0 Wall g ggdb 34 SET CMAKE CXX FLAGS RELEASE
  • SLAM中的小工具

    g2o中有用的小工具 ifndef G2O STUFF MISC H define G2O STUFF MISC H include 34 macros h 34 include lt cmath gt ifndef M PI define
  • Windows中公用网络与专用网络的区别

    当我们第一次打开一个Windows网络应用程序时 xff0c 会弹出选择网络类型 xff1a 专用网络 xff0c 公用网络 这个的确令人费解 xff0c 相信很多人都不知所措过 有的人干脆都选上 xff0c 这样就避免了被防火墙挡住 这里
  • ubuntu服务器修改ssh登录用户名及端口

    1 如果默认的ssh登录用户名为ubuntu xff0c 需要开通root账户 xff0c 添加密码 xff1a passwd root 还需修改配置 xff0c 具体方法 xff1a vi etc ssh sshd config 确保一下
  • 针对Android MediaCodec解码延时问题的替代解决方案

    如题 xff0c 本人在jni层实现了avc hevc的解码 xff0c 避免了在java上层调用系统的MediaCodec解码出现的延时问题 xff0c 完美支持1080P xff0c 4K xff08 具体看手机性能 xff09 xff
  • 系统环境变量path的列表不见了

    如题 xff0c 在编辑系统环境变量时 xff0c 发现path的环境变量原先是列表显示的 xff0c 看起来比较清晰 xff0c 而现在变成了一个文本框了 xff0c 就不那么一目了然了 于是在网上找到下面这个文章 xff0c 能很好解决
  • gazebo(1):gazebo常见问题及解决办法

    目录 1 将自己创建的gazebo模型导入后 xff0c 模型不停得抖动 xff0c 翻转 2 save world as 之后卡死 3 下载gazebo官方模型 xff1a 4 gazebo更新后无法打开 5 运行gazebo后报错 6
  • Makefile中的$(1)是什么

    Linux工程的编译要用到make工具 xff0c 平台不一样 xff0c 只是工具链不同 xff0c 但Makefile是编译系统的关键所在 xff0c 因此掌握Makefile的编写规则是非常重要的 尽管有了cmake这样更容易使用的编
  • gl的矩阵模式及其相应的矩阵变换函数

    以Android的GL10为例 xff0c 说明一下矩阵模式及其相应的矩阵变换函数 矩阵模式一共分为两种 xff1a gl glMatrixMode GL10 GL MODELVIEW 和 gl glMatrixMode GL10 GL P
  • 对md5sum程序的修改

    linux下自带md5sum工具 xff0c 可以对文件计算md5值 xff0c 但这个命令行工具不能直接对字符串求md5 xff0c 而对一个字符串求md5是一个比较有用的需求 xff0c 比如计算签名 于是对源码md5sum c修改了一
  • 物联网通信协议——比较-MQTT、 DDS、 AMQP、XMPP、 JMS、 REST、 CoAP

    原文链接 xff1a https blog csdn net lightrain0 article details 84343857 AMQP amp MQTT amp DDS https www youtube com watch v 6
  • 门电路逻辑符号大全(三态门,同或门,异或门,或非门,与或非门, 传输门,全加器,半加器等)

    最近要研究一下滤波器设计的无乘法器的实现 xff0c 所以要学习一下加法器的电路 xff0c 丢了一段时间 xff0c 忘的差不多了 xff0c 这里罗列一下常用的门电路的符号 这是一个1位全加器的数字电路组成 xff1a 以下两幅图可以复
  • 实函数傅里叶变换的奇偶虚实特性

    本文内容来源于他人的PPT xff0c 经本人整理而成 xff0c 算是对数字信号处理的复习吧 而实偶函数的傅里叶变换仍然是一个实偶函数的性质正是DCT的基础 xfeff xfeff
  • 多面体及欧拉公式及广义欧拉公式

    像正方体 xff0c 四棱锥这样的平面多面体属于简单多面体 xff0c 它们可以与球拓扑同构 xff0c 即可以连续拓扑变换成一个球 它们满足欧拉公式 xff1a v e 43 f 61 2 其中v是顶点 xff08 vertex xff0
  • mysql在表的某一位置增加一列的命令

    如果想在一个已经建好的表中添加一列 xff0c 可以用诸如 xff1a alter table t1 add column addr varchar 20 not null 这条语句会向已有的表t1中加入一列addr xff0c 这一列在表
  • tar命令中的-C作用

    tar xzvf abc tar gz C tmp 上面的命令将abc tar gz这个压缩包解压到当前目录下的tmp目录下 xff0c 而不是当前目录下 xff0c 这就是 C选项的作用
  • Java多线程

    一 基础概念 1 CPU核心数和线程数 多核心指的是单芯片多处理器 xff0c 将多个CPU集成到同一个芯片内 xff0c 不同的CPU可以单独的运行程序 目前主流的CPU有四核 六核 八核 增加核心数目的是为了增加线程数 xff0c 一般