美文网首页
Java多线程

Java多线程

作者: 小的橘子 | 来源:发表于2019-03-04 16:29 被阅读0次

    开启线程方式

    1. 继承自Thread类的线程

    public class ThreadExer {
        public static void main(String[] args){
            Thread thread = new MyThread();
            thread.setName("sub thread");//设置子线程的名称
            thread.start();
            for(int i=0;i<20;i++){
                try {
                    Thread.sleep(100);
                    System.out.println("thread name:"+Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            for(int i=0;i<20;i++){
                try {
                    Thread.sleep(100);
                    System.out.println("thread name:"+Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    2. 实现Runnable接口的线程

    public class ThreadExer {
        public static void main(String[] args){
            Thread thread = new Thread(new MyRunnable(),"sub thread(sub thread)");
            thread.start();
            for(int i=0;i<20;i++){
                try {
                    Thread.sleep(100);
                    System.out.println("thread name:"+Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            for(int i=0;i<20;i++){
                try {
                    Thread.sleep(100);
                    System.out.println("thread name:"+Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    实现Runnable接口和继承自Thread的区别

    1. 实现Runnable接口的方法,只要传入同一个Runnable对象即可实现资源共享
    2. 可以避免java中单继承的限制

    线程状态

    线程内部状态

    线程内部定义了线程状态的枚举值如下

    public static enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    
        private State() {
        }
    }
    
    • NEW 新建状态
      ==Thread thread = new Thread(runnable);== 使用new操作符创建Thread对象则处于NEW状态

    • RUNNABLE 可运行状态
      ==thead.start()==

    • BLOCKED 阻塞状态

    • WAITING 等待状态

    • TIMED_WAITING 休眠状态

    • TERMINATED 终止状态

    一般所说的5种状态

    1. 新建状态(New) :新创建了一个线程对象。
    2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
      1. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
      2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
      3. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
    5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    线程状态转化

    image

    等待Blocked,锁定Blocked都属于阻塞状态。

    Thead常用Api

    1. sleep()

    // Thread.java
    public static void sleep(long millis) throws InterruptedException
    

    使当前线程主动让出CPU,CPU去执行其他线程,在sleep指定的时间过后,该线程进入就绪状态,线程重新抢夺执行权,如果当前线程进入运行状态会接着sleep后的代码继续执行。如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了CPU,但其他被同步锁挡住了的线程也无法得到执行

    Thread.sleep(0)的作用是"触发操作系统立刻重新进行一次CPU竞争,重新计算优先级

    2. yield()

    // Thread.java
    public static native void yield();
    

    使当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用Thead.yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

    3. join()

    // Thread.java
    public final void join() throws InterruptedException
    public final void join(long millis) throws InterruptedException
    

    在A线程中调用b.join()方法,会使A线程进入阻塞状态,直到B结束生命周期或到达了给定时间

    4. interrupt相关方法

    interrupt相关方法如下

    // Thread.java
    1. public void interrupt()
    3. public native boolean isInterrupted()
    2. public static native boolean interrupted()
    
    1. interrupt()
      当B线程调用wait,sleep,join等方法会使线程进入阻塞状态,在A线程中调用b.interrupt()方法就会打断B线程阻塞状态,wait,sleep,join等方法就会抛出InterruptedException。一个线程内部存在interrupt flag,默认false,当被打断时interrupt会被置为true,当捕获InterruptedException异常时,该interrupt又会被置false。
    2. isInterrupted()
      b.isInterrupted()会判断B线程是否被中断,仅仅通过interrupt flag判断,并不会影响interrupt的改变
    3. interrupted()
      调用Thread.interrupted()方法会首先返回当前线程的interrupt的状态,如果interrupt为true,则会置interrupt为false

    5. wait/notify/notifyAll()

    Object类中定义了wait(),notify(),notifyAll()方法

    1.  public final native void wait() throws InterruptedException;
        public final void wait(long millis) throws InterruptedException
    2.  public final native void notify();
    3.  public final native void notifyAll();
    
    1. wait() o.wait()锁对象o调用wait方法使当前线程进入阻塞状态,jvm会使调用者所属线程进入waitset,并立刻释放锁对象,直到通过调用该锁对象o的notify()或notifyAll()方法将其唤醒,或者阻塞时间到指定时间时自动唤醒
    2. notify() 通过锁对象调用notify后会将waitset中的一个线程弹出,具体弹出哪个取决于jvm,弹出的线程将可以重新获得锁
    3. notifyAll() 通过锁对象调用notifyAll后会弹出waitset中的所有线程,这些线程会争夺锁,得到锁的线程才可以继续执行。

    wait,notify,notifyAll方法必须在同步方法中使用,且调用者就是锁对象,对于同步方法来说就是this,对于同步代码块来说就是指定的锁对象。

    有下面两个问题需要注意

    1. 首先为什么wait、notify和notifyAll方法要和synchronized关键字一起使用?

    因为wait方法是使一个线程进入等待状态,并且释放其所持有的锁对象,notify方法是通知等待该锁对象的线程重新获得锁对象,然而如果没有获得锁对象,wait方法和notify方法都是没有意义的,因此必须先获得锁对象再对锁对象进行进一步操作于是才要把wait方法和notify方法写到同步方法和同步代码块中了。

    由此可知,wait和notify、notifyAll方法是由确定的对象即锁对象来调用的,锁对象就像一个传话的人,他对某个线程说停下来等待,然后对另一个线程说你可以执行了(实质上是被捕获了),这一过程是线程通信。sleep方法是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,运行的主动权是由当前线程来控制(拥有CPU的执行权)。

    2. 锁(monitor)池和等待池(waitset)的区别?

    • 锁池 假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。简单点说,如果有个线程已拿到某个对象锁,其他线程要执行含有该锁的synchronized方法时就会进入锁池
    • 等待池 假设当前A线程持有锁对象后,调用了这个锁对象的 wait() 方法,则 A线程就会释放该对象的锁(因为 wait() 方法必须出现在 synchronized 中,所以在执行 wait() 方法之前 A 线程就已经拥有了该对象的锁),同时线程 A会进入该锁对象的等待池中。如果有其它线程持有上述锁对象后,并调用了该锁对象的 notifyAll() 方法,那么处于该锁对象的等待池中的线程就会全部进入该锁对象的锁池中,从新争夺锁的拥有权。如果另外的一个线程调用了相同对象的 notify() 方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池。

    线程调度

    线程优先级

    Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量

    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;
    

    Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

    每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
    线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

    JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

    如果CPU比较忙,设置优先级可能会获得更多的CPU时间,但如果CPU比较空闲,优先级几乎不会起任何作用。所以不要指望依赖优先级来绑定某些特定业务,实际一般不会设置优先级,采用默认优先级为5.

    线程的停止

    1. ~thread.stop()
      stop方法已被废弃,原因是什么?
    2. thread.setDaemon(true);
    public class StopThreadExer {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            // 调用该方法设置守护线程
            thread.setDaemon(true);
            thread.start();
            for (int i = 0; i < 5; i++) {
                try {
                    thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + i);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    输出结果

    Thread-00
    main0
    main1
    main2
    main3
    main4
    

    在不调用setDaemon情况下,子线程会输出5次,上述输出可见主线程停止了守护线程也停止了,即使是在阻塞的情况下。守护线程是为其他线程服务,当所有的非守护线程结束时, 程序也就终止了,同时会杀死进程中的所有守护线程,所以不要在守护线程中去访问文件等资源

    1. 标志位(使用最多)
      可以根据业务逻辑决定什么时候结束子线程
    public class StopThreadExer {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
            for (int i = 0; i < 5; i++) {
                try {
                    thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + i);
            }
            MyRunnable.running = false;// 使标志量为false,则子线程不会继续执行下一次循环,但有有可能正在延时,故需要调用interrupt()方法使打断阻塞的线程
            thread.interrupt();
        }
    }
    
    class MyRunnable implements Runnable {
        public static boolean running = true;
        @Override
        public void run() {
            for (int i = 0; i < 5&&running; i++) {
                System.out.println(Thread.currentThread().getName() + i);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    注意 MyRunnable.running = false;thread.interrupt();

    1.线程生命周期正常结束

    任务耗时短时或者时间可控放任正常结束即可

    2. 通过中断信号interrupt关闭线程

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                super.run();
                System.out.println("I will start work");
                while (!isInterrupted()) {
                    // working
                }
                System.out.println("I will be exiting");
            }
        };
        thread.start();
        TimeUnit.SECONDS.sleep(5);
        // 1.
        thread.interrupt();
    }
    

    注释1处调用interrupt后,thread线程的interrupt标识就会返回true,isInterrupted()方法就会返回true,则while循环条件为false,接着执行下一句输出生命周期结束,线程结束。

    Thread thread = new Thread() {
        @Override
        public void run() {
            super.run();
            System.out.println("I will start work");
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    // 1.
                    break;
                }
            }
            System.out.println("I will be exiting");
        }
    };
    thread.start();
    TimeUnit.SECONDS.sleep(5);
    // 2.
    thread.interrupt();
    

    注释2处调用interrupt后,thread线程注释1处会捕获InterruptedException异常,捕获到该异常时退出循环即可,进而执行下个输出语句,最后线程停止。

    3. volatile 变量控制开关

    由于interrupt标识很有可能被擦除,故加入flag来控制,注意需要使用volatile关键字修饰,保证每个线程可以读取到主内存中最新的值,否则可能会引起线程的无线循环

    static class MyThread extends Thread {
        private volatile boolean closed;
    
        @Override
        public void run() {
            super.run();
            System.out.println("I will start work");
            while (!closed && !isInterrupted()) {
                // working
            }
            System.out.println("I will be exiting");
        }
    
        public void close() {
            closed = true;
            this.interrupt();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        TimeUnit.SECONDS.sleep(5);
        thread.close();
    }
    

    同步

    同步是为了解决多线程共同访问数据导致数据出现问题,它使多线程可以实现对一段代码互斥访问,也就是在同步代码中只会存在一个线程执行

    • 同步方法
    private int count;
    public synchronized void count(){
        count++;
    }
    

    使用synchronized修饰符修饰成员方法,此时锁对象默认为当前对象

    private static int count;
    public static synchronized void count(){
        count++;
    }
    

    修改在静态方法上,锁对象默认为当前类对象(类名.class)

    • 同步代码块
    synchronized (this){
        count++;
    }
    

    括号中可以是任意对象。

    线程间的协作

    1. Object类中的成员方法

    • wait()
      使当前持有该锁的线程释放锁,并变为阻塞状态,然后加入锁对象的等待队列(等待池)中
    • notify()
      由当前持有同步锁的线程调用,以唤醒锁对象的等待队列中的一个线程,使其变为就绪状态。直到当前线程释放锁,被唤醒的线程才可能被执行,被唤醒后且得到了锁的线程,才会从wait方法的下一句继续执行。
    • notifyAll()
      由当前持有同步锁的线程调用,以唤醒锁对象的等待队列中的所有线程,被唤醒的线程将竞争锁

    2. 生产者和消费者

    public class ThreadExer {
        public static void main(String[] args) {
            List list = new ArrayList();
            Runnable producerRunnable = new ProducerRunnable(list);
            Runnable consumerRunnable = new ConsumerRunnable(list);
    
            Thread producer = new Thread(producerRunnable, "producer1");
            Thread producer1 = new Thread(producerRunnable, "producer2");
            Thread producer2 = new Thread(producerRunnable, "producer3");
    
            Thread consumer = new Thread(consumerRunnable, "consumer1");
            Thread consumer1 = new Thread(consumerRunnable, "consumer2");
            Thread consumer2 = new Thread(consumerRunnable, "consumer3");
            Thread consumer3 = new Thread(consumerRunnable, "consumer4");
            producer.start();
            producer1.start();
            producer2.start();
    
            consumer.start();
            consumer1.start();
            consumer2.start();
            consumer3.start();
        }
    }
    
    class ProducerRunnable implements Runnable {
        private static final int MAX_CONTAINER_SIZE = 10;
    
        List container;
        String produceProductName;
        int count;
    
        public ProducerRunnable(List container) {
            this.container = container;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                synchronized (container) {
                    // 由于生产者被唤醒后会接着wait()后的一句代码进行执行,当一个被唤醒的生产者抢夺锁后生产一个后释放锁,当第二个生产者拥有锁后也会接着wait方法后执行,故无法判断容器已满,故采用循环判断。
                    while (container.size() == MAX_CONTAINER_SIZE) {
                        System.out.println("容器已满,等待消费");
                        try {
                            container.wait();// 让当前生产者线程进入该锁对象的等待池,并释放该锁,
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    produceProductName = (count++) + "号产品";
                    System.out.println(Thread.currentThread().getName()+"生产 " + produceProductName);
                    container.add(produceProductName);
                    container.notifyAll(); // 会唤醒等待池中的所有线程,代码走到此处说明容器没满,意味着没有生产者处于等待池,则意思就是唤醒所有消费者线程去竞争锁
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class ConsumerRunnable implements Runnable {
        private static final int MIN_CONTAINER_SIZE = 0;
    
        List container;
        String consumeProductName;
    
        public ConsumerRunnable(List container) {
            this.container = container;
        }
    
        @Override
        public void run() {
            while (true) {
                synchronized (container) {
                    while (container.size() == MIN_CONTAINER_SIZE) {
                        System.out.println("容器为空,等待生产");
                        try {
                            container.wait(); // 容器为空,消费者加入等待池
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    consumeProductName = (String) container.remove(0);
                    System.out.println(Thread.currentThread().getName() + " 消费" + consumeProductName);
                    container.notifyAll(); // 代码走到此处说明所有等待池中的消费者都被唤醒,故此处会唤醒处于等待池中的所有生产者
                }
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    下面log为截取了一段输出

    producer1生产 0号产品
    consumer2 消费0号产品
    容器为空,等待生产
    容器为空,等待生产
    容器为空,等待生产
    producer3生产 1号产品
    producer2生产 2号产品
    consumer1 消费1号产品
    consumer4 消费2号产品
    容器为空,等待生产
    producer1生产 3号产品
    consumer3 消费3号产品
    producer2生产 4号产品
    producer3生产 5号产品
    producer3生产 6号产品
    producer2生产 7号产品
    producer1生产 8号产品
    

    这个示例3个生产者总共会生产60个产品,但容器里最多只能放入10个产品,放满时生产者等待,当容器产品被消费消费完时则消费者等待。使用wait和notify实现了生产者和消费者的协作。

    死锁

    实际写代码要避免死锁

    public class DeadLock {
        public static Object obj1 = new Object();
        public static Object obj2 = new Object();
    
        public static void main(String[] args) {
            Runnable aRunnable = new ARunnable();
            Runnable bRunnable = new BRunnable();
            Thread thread1 = new Thread(aRunnable);
            Thread thread2 = new Thread(bRunnable);
            thread1.start();
            thread2.start();
        }
    
    }
    
    class ARunnable implements Runnable {
    
        @Override
        public void run() {
            synchronized (DeadLock.obj1) {
                System.out.println("A Runnable obj1 lock");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (DeadLock.obj2) {
                    System.out.println("A Runnable obj2 lock");
                }
            }
        }
    }
    
    class BRunnable implements Runnable {
    
        @Override
        public void run() {
            synchronized (DeadLock.obj2) {
                System.out.println("B Runnable obj2 lock");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (DeadLock.obj1) {
                    System.out.println("B Runnable obj1 lock");
                }
            }
        }
    }
    

    输出

    A Runnable obj1 lock
    B Runnable obj2 lock
    

    A线程获得到obj1锁后睡眠1s不会释放锁,1s没有到达时,B线程此时获得到obj2锁,接着A线程去尝试获得B线程已经持有的obj2锁,而B线程又在等待A线程中的obj1锁,从而导致了死锁。

    死锁检查分析

    1. jconsole 运行工具点击detect lock
    2. jstack [pid] 即可在stack信息中查看对应进程的死锁情况

    jps 命令查看java程序pid。截取主要信息如下

    Java stack information for the threads listed above:
    ===================================================
    "Thread-1":
        at com.nan.mylibrary.BRunnable.run(DeadLock.java:48)
        - waiting to lock <0x000000076d674558> (a java.lang.Object)
        - locked <0x000000076d674568> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)
    "Thread-0":
        at com.nan.mylibrary.ARunnable.run(DeadLock.java:30)
        - waiting to lock <0x000000076d674568> (a java.lang.Object)
        - locked <0x000000076d674558> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)
    
    Found 1 deadlock.
    

    知识点

    1. 守护线程
      hread.setDaemon(true);
      通过设置该属性则线程为守护线程,守护线程是为其他线程服务的,如果其他线程停止运行了,虚拟机中只剩守护线程,则虚拟机回退出
    2. 线程名称
      通过setName可以设置线程名称,不同的线程可以有相同的线程名称,不能使用线程名称来区分线程
    3. 线程ID
      每个线程创建时则会分配线程ID,通过getId()即可拿到线程ID,无法修改==系统通过ID来区分不同的线程==
    4. Java中程序运行至少会启动2个线程。一个是main线程,一个是垃圾收集线程

    特性

    1 使用同一个runnable接口创建的线程可以变量资源的共享

    2 线程在创建后就被分配内存空间,该空间也在JVM中堆内存中,其私有数据已被初始化

    3 异常无法跨线程抛出或捕获

    4 调用yield()方法后调度器会将该线程放入等待队列的末尾

    5 同步方法锁对象为this,同步块锁对象为传入的对象,对于加锁,释放锁是有JVM自动完成的

    6 当使用不是同一个Runnable接口或继承Thread创建的线程所加的同步方法,持有的锁不是同一把锁,因为对象发生了变化

    如果对于继承Thread类想要互斥访问同一个变量,则需使变量变为static,并且同步锁需加在static方法上,此时锁对象为Class对象,从而具有同一把锁,实现了互斥访问
    

    Java内存模型

    java内存模型(JMM):

    • 所有的变量都存储在主内存中

    • 每个线程都有自己独立的工作内存,里面保存了该线程使用到的变量的副本(主内存中该变量的一份拷贝)

    JMM规定

    1)线程对共享变量的所有操作都必须在自己的工作内存中进行, 不能直接从主内存中读写

    2)不同线程之间无法直接访问其它线程工作内存中的变量,线程间变量值的传递是需要通过主内存来完成的。

    synchronized:具有可见性,有序性,也具有原子性

    JMM关于synchronized的两条规定

    1. 线程解锁前,必须把共享变量的最新值刷新到主内存中
    2. 线程加锁时,将清空工作内存中的共享变量的值,从而使用共享变量时要从主内存中重新读取最新的变量的值(加锁和解锁必须是同一把锁)

    线程执行互斥代码的过程:

    (1)获得互斥锁
    (2)清空工作内存
    (3)从主内存拷贝变量最新的值给工作内存中的副本
    (4)执行代码
    (5)将修改后的共享变量的值刷新到主内存中
    (6)释放互斥锁
    

    volatile关键字

    • 具有可见性、有序性,不具备原子性。其不会进行锁操作,所以性能更好。

    注意,volatile不具备原子性,这是volatile与java中的synchronized、java.util.concurrent.locks.Lock最大的功能差异

    下面来分别看下可见性、有序性、原子性:

    • 原子性:如果你了解事务,那这个概念应该好理解。原子性通常指多个操作不存在只执行一部分的情况,如果全部执行完成那没毛病,如果只执行了一部分,那对不起,你得撤销(即事务中的回滚)已经执行的部分。

    • 可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1、线程2...线程n能够立即读取到线程1修改后的值。

      • 写操作:就是修改线程的工作内存中副本的值,然后刷新到主内存中,
      • 读操作:从主内存中读取值到工作内存中的副本,然后再取副本中的值
    • 有序性:即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。(本文不对指令重排作介绍,但不代表它不重要,它是理解JAVA并发原理时非常重要的一个概念)。

    • 不具有原子性
      number++不具有原子性:eg:具有A,B线程,number = 5,然后A,B线程分别对其进行加一操作

    (1)A线程执行number++读到值时阻塞,B线程执行
    (2)B线程进行加1操作后为6,并会把修改的值刷新到主内存中,A线程执行
    (3)A线程继续进行加1操作,修改后的值为6,从而产生问题
    

    线程问题整理

    1. 如果调用Thread的start()方法两次则会报如下异常
    Exception in thread "main" java.lang.IllegalThreadStateException
        at java.lang.Thread.start(Thread.java:705)
        at com.example.ThreadExer.main(ThreadExer.java:24)
    thread name:Thread-0
    thread name:Thread-0
    

    相关文章

      网友评论

          本文标题:Java多线程

          本文链接:https://www.haomeiwen.com/subject/gmmiuqtx.html