多线程&并发(二)

来源:互联网 发布:西班牙菜 知乎 编辑:程序博客网 时间:2024/05/18 13:10

上次遗留问题:

  1. System.out.print()函数是线程安全的吗?

    //字符串打印是线程安全的   public synchronized void print(String str) {}//类似以下这些都不是public void print(char c) {    print(String.valueOf(c));}public void print(char[] chars) {    print(new String(chars, 0, chars.length));}
    • 同一个线程再次进入锁,锁记录加1操作是在哪?

    • 集合迭代操作时remove操作引发concurrent异常的问题?

    • java支持并行操作吗?

    并发&并行 在实际执行层面依赖于单核还是多核

    • 64位和32位操作系统java double,int是否为原子类型?

要了解的一些概念:

  • 处理器:

8位处理器、16位处理器、32位处理器和64位处理器,其计数都是8的倍数。它表示一个时钟周期里,处理器处理的二进制代码数

建立在64位操作系统上才能发挥作用

64位的优点:

可以进行更大范围的整数运算

一个32位整数可以表示2的32次方也就是4GB的数值,而一个64位整数,即2的64次方也就是1800万TB,可以看做是无限大。64位整数数据的应用程序在64位的硬件上进行运算可以大幅提高计算性能,在同一周期内可以处理更多的数据,从而大大减少运算时间,也使得某些超大数运算得以更好的解决。

可以支持更大的内存

另一个优点便是64位处理器可以支持64位的内存寻址。同样的原理,内存地址也是整数,ALU和寄存器既然能够存储更多的整数,那同样也能够容下更多的内存地址,打破了32位下4GB的限制。实际上64位处理器究竟需要多大的物理和虚拟内存寻址完全取决于不同处理器的需求。

  • JVM:

官方说明:

17.7. Non-atomic Treatment of double and long

For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

Writes and reads of volatile long and double values are always atomic.

Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency’s sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.

Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.

即:官方对非32位的long和double类型读取是否为原子类型不做限制,不同的JVM实现不同,但是volatile标识后必须是原子的操作

线程间通信

等待通知机制

  • start()

调用后,线程处于可运行状态(Runnable),如果抢占到CPU资源,线程处于运行状态(Running)

  • wait()、notify()、notifyAll()、

Object的方法,必须执行于同步块或同步方法中,且方法调用时线程必须持有适当的锁,否则会报IllegalMonitorStateException异常

wait(): 执行后释放锁

    /**     * 创建两个线程先后执行ThreadWait 第一个线程执行wait后就会释放锁     *     * @author by fengruicong on 16/9/12.     */    class ThreadWait {        void testMethod(Object lock) {            try {                synchronized (lock) {                    System.out.println("begin wait()");                    lock.wait();                    System.out.println("end wait()");                }            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

执行结果

    begin wait()    begin wait()

wait(long): 等待某时间后自动唤醒

    /**     * wait(long)等待设定的时间过后,自动释放锁     *     * @author by fengruicong on 16/9/12.     */    class ThreadWaitTime {        void waitMethod(Object lock) {            try {                synchronized (lock) {                    System.out.println("begin wait() " + System.currentTimeMillis());                    lock.wait(500);                    System.out.println("end wait() " + System.currentTimeMillis());                }            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

notify(): 执行完notify所在同步代码块后释放锁

    /**     * 创建两个线程执行ThreadNotify, 一个执行waitMethod 一个执行notifyMethod     * 执行wait后就会释放锁,执行notify整个方法体执行完才会释放锁     *     * @author by fengruicong on 16/9/12.     */    class ThreadNotify {        void waitMethod(Object lock) {            try {                synchronized (lock) {                    System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName());                    lock.wait();                    System.out.println("end wait() ThreadName=" + Thread.currentThread().getName());                }            } catch (InterruptedException e) {                e.printStackTrace();            }        }        void notifyMethod(Object lock) {            try {                synchronized (lock) {                    System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());                    lock.notify();                    Thread.sleep(500);                    System.out.println("end wait() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());                }            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

执行结果

    begin wait() ThreadName=Thread-0    begin wait() ThreadName=Thread-1 time=1473674522878    end wait() ThreadName=Thread-1 time=1473674523382    end wait() ThreadName=Thread-0

notifyAll():唤醒所有处于等待的线程

  • interrupt()

中断,Java中断是一种协作机制,即调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。每个线程都有一个boolean的中断状态(不一定就是对象的属性,事实上,该状态也确实不是Thread的字段),interrupt方法仅仅只是将该状态置为true。对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true,并不会使程序停止;

wait中的线程,被interrupt后会抛出InterruptException异常,抛出异常后锁自动释放

public class InterruptTest {    public static void main(String[] args) {        try {//            Thread a = new ThreadA();            Object lock = new Object();            Thread a = new ThreadWait(lock);            a.start();            Thread.sleep(500);            a.interrupt();            System.out.println("中断A");        } catch (InterruptedException e) {            e.printStackTrace();        }    }    static class ThreadA extends Thread {        @Override        public void run() {            while (true) {                //通过线程内部判断interrupt状态自己处理中断操作                if (Thread.currentThread().isInterrupted()) {                    System.out.println("Interruted!");                    break;                }                System.out.println(System.currentTimeMillis());            }        }    }    static class ThreadWait extends Thread {        private Object lock;        ThreadWait(Object lock) {            super();            this.lock = lock;        }        @Override        public void run() {            try {                synchronized (lock) {                    System.out.println("begin wait() " + Thread.currentThread().getName());                    lock.wait();                    System.out.println("end wait() " + Thread.currentThread().getName());                }            } catch (InterruptedException e) {                e.printStackTrace();                System.out.println("interrupt " + Thread.currentThread().getName());            }        }    }}
  • stop() 不推荐使用

原因1:调用后马上抛出ThreadDeath错误,在run方法内,try-catch语句甚至finally语句 不可控制

原因2:调用stop方法后会立即释放它持有的所有的锁,但是如果这些锁保护的数据在锁释放之前状态不一致时,会导致数据的不一致问题

public class ThreadStopTest {    public static void main(String[] args) {//        testStop();        testLockRelease();    }    /**     * 测试stop执行后释放锁     */    private static void testLockRelease() {        final Object lock = new Object();        //定义第一个线程,首先该线程拿到锁,而后等待3s,之后释放锁        try {            final int[] data = new int[1];            Thread t0 = new Thread() {                public void run() {                    try {                        synchronized (lock) {                            System.out.println("thread->" + getName() + " acquire lock.");                            sleep(3 * 1000);                            data[0] = data[0] + 1;                            System.out.println("thread->" + getName() + " 等待3s");                            System.out.println("thread->" + getName() + " release lock.");                        }                    } catch (Throwable ex) {                        System.out.println("Caught in run: " + ex);                        ex.printStackTrace();                    }                }            };            //定义第二个线程,等待拿到锁对象            Thread t1 = new Thread() {                public void run() {                    synchronized (lock) {                        System.out.println("thread->" + getName() + " acquire lock.");                        System.out.println("data====" + data[0]);                    }                }            };            //线程一先运行,先拿到lock            t0.start();            //而后主线程等待100ms,为了做延迟            Thread.sleep(100);            //停止线程一            //t0.stop();            //这时候在开启线程二            t1.start();        } catch (Throwable t) {            System.out.println("Caught in main: " + t);            t.printStackTrace();        }    } } //执行结果:thread->Thread-0 acquire lock.thread->Thread-0 等待3sthread->Thread-0 release lock.thread->Thread-1 acquire lock.data====1============================================打开t0.stop()注释,执行结果:thread->Thread-0 acquire lock.thread->Thread-1 acquire lock.Caught in run: java.lang.ThreadDeathdata====0java.lang.ThreadDeath    at java.lang.Thread.stop(Thread.java:849)    at com.example.stop.ThreadStopTest.testLockRelease(ThreadStopTest.java:49)    at com.example.stop.ThreadStopTest.main(ThreadStopTest.java:6)    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)    at java.lang.reflect.Method.invoke(Method.java:483)    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

以上两种执行结果说明了 由于stop方法的调用导致了不可预知的第二个线程得到数据的未知性

  • yield()

yeild是个native静态方法,这个方法是想把自己占有的cpu时间释放掉,然后和其他线程一起竞争(注意yeild的线程还是有可能争夺到cpu,注意与sleep区别)。在javadoc中也说明了,yeild是个基本不会用到的方法,一般在debug和test中使用。

  • join()

执行线程阻塞,直到调用join线程执行完毕。

public class JoinTest {    public volatile static int i = 0;    public static class AddThread extends Thread {        @Override        public void run() {            for (i = 0; i < 10000000; i++) ;        }    }    public static void main(String[] args) throws InterruptedException {        AddThread at = new AddThread();        at.start();        at.join();        System.out.println(i);    }}//执行结果10000000即:主线程在调用at.join()方法后一直阻塞,直到AddThread执行完毕才继续执行
  • 守护线程

理解一: 守护线程同main同生共死,当main退出,它将终止,而普通线程是在任务执行结束才停止。

理解二:用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。

1)thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)

2)在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)

3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

常用使用场景:GC线程

设定线程为守护线程方法: public final void setDaemon(boolean on) ;

public class DaemonTest {    public static void main(String[] args) throws InterruptedException {        Runnable tr = new TestRunnable();        Thread thread = new Thread(tr);        //thread.setDaemon(true); //设置守护线程        thread.start(); //开始执行分进程    }    static class TestRunnable implements Runnable {        public void run() {            try {                Thread.sleep(1000);//守护线程阻塞1秒后运行                File f = new File("daemon.txt");                FileOutputStream os = new FileOutputStream(f, true);                os.write("daemon".getBytes());            } catch (IOException e1) {                e1.printStackTrace();            } catch (InterruptedException e2) {                e2.printStackTrace();            }        }    }}//执行结果:如果设置为守护线程,则没有创建文件或没有正常写入字符串(因为守护线程会随着主线程执行完毕而退出),//如果设置为非守护线程则能正常创建文件并写入字符串
0 0