Java线程基础、线程之间的共享和协作(三)

对 Java 里的线程再多一点认识

深入理解run()和start()

Thead类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread(),其实只是new出来一个Thread的实例,还没有真正和操作系统线程挂钩起来。只有执行了start()方法后,才实现了真正意义上的启动线程。
start()方法让一个线程进入就绪队列等待分配CPU,分到CPU后才调用实现的run()方法,start()方法不能重复调用,如果重复调用会抛异常。
而run()方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法没有任何区别,可以重复执行,也可以被单独调用。

线程的其他相关方法

yeild()方法:使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源。
注意:并不是每个线程都需要这个锁的,而且执行yeild()的线程不一定就会持有锁,我们完全可以在线程释放锁之后调用yeild()方法。
所有执行yeild()方法的线程有可能在进入就绪态后被操作系统再次选中马上又执行了。
wait()/notify()/notifyAll():后面会单独讲述。

join 方法

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

线程的优先级

在 Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范围从 1~10,在线程构建的时候可以通过 setPriority(int)方法来修改优先级,默认优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

守护线程

Deamon(守护)线程是一种支持型线程,因为它主要备用于程序中后台调度以及支持型工作。这意味者,当一个java虚拟机中不存在守护线程的时候,Java虚拟机将会退出,可以通过调用Thread.setDeamon(true)将线程设置为守护线程。我们一般用不到,想垃圾回收线程就是Deamon线程。
Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑。

线程的共享和协作

线程间的共享

synchronized 内置锁

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的SQL语句一步一步的执行,直到终止。但是每个运行中的线程,如果仅仅是孤立的运行,那么没有一点价值或者说价值很少,如果多个线程能够相互配合的完成工作,包括数据间的共享、协作处理事情,这将带来巨大的价值。
Java支持多个线程同时访问一个对象或者对象的成员变量,关键字synchronized可以修饰方法或者以同步代码块的形式来使用,它主要保证多个线程在同一时刻,只能有一个线程处于方法或者同步代码块中,它保证了线程对变量访问的可见性及排他性,又称为内置锁机制。

对象锁和类锁

对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的 class 对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的 class 对象。类锁和对象锁之间也是互不干扰的。

错误的加锁和原因分析

我们看如下代码:

private static class Worker implements Runnable{

        private Integer i;

        public Worker(Integer i) {
            this.i=i;
        }

        public void run() {
            synchronized (i) {
                Thread thread=Thread.currentThread();
                System.out.println(thread.getName()+"--@"+System.identityHashCode(i));
                i++;
                System.out.println(thread.getName()+"-------"+i+"-@"+System.identityHashCode(i));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName()+"-------"+i+"--@"+System.identityHashCode(i));
            }

        }

    }

原因:虽然我们对 i 进行了加锁,但是当我们反编译这个类的 class 文件后,可以看到 i++实际是Integer.valueOf(this.i.intValue() + 1)
Java线程基础、线程之间的共享和协作(三)_第1张图片
本质上是返回了一个新的 Integer 对象。也就是每个线程实际加锁的是不同的 Integer 对象。源码如下:

    /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

volatile,最轻量级的同步机制

volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。不加 volatile 时,子线程无法感知主线程修改了 ready 的值,从而不会退出循环,而加了 volatile 后,子线程可以感知主线程修改了 ready 的值,迅速退出循环。但是 volatile 不能保证数据在多个线程下同时写时的线程安全
volatile 最适用的场景:一个线程写,多个线程读。

你可能感兴趣的:(java,线程间协作,线程基础)