Java高并发程序设计学习笔记(一)一些基本概念

 

并发级别:阻塞、无饥饿、无障碍、无锁、无等待

阻塞:一个线程是阻塞的,就是必须等待其他线程释放资源。使用synchronization关键字,或者在重入锁时就能得到阻塞线程。(悲观锁)

无饥饿:有优先级线程时,线程调度会优先满足优先级高的线程。。非公平锁的情况下系统允许高优先级的线程插队,可能导致低优先级的线程产生饥饿。

无障碍:最弱的非阻塞调度。可以同时进入临界区,但是如果检测到其它线程也在修改资源,就会对自己所做的修改进行回滚。(乐观锁),可能存在的问题,所有线程都在临界区不断的回滚。

一种无障碍实现方式,依赖一个“一致性标记”,线程在操作之前先读取并保存这个标记,在操作完成之后,再次读取。检查标记是否被修改。如果一致说明没有资源访问冲突。否则与其他写线程冲突,需要重试。任何对资源有修改操作的线程在修改数据前都需要更新这个标记。

无锁:无锁的并行都是无障碍的。无锁的情况下所有的线程都能尝试对临界区进行访问,但是,无锁的并发保证必然有一个线程能在有限步内完成操作离开临界区。

无锁的调用中可能会包含一个无穷循环,循环内线程会不断的尝试修改共享变量,没有冲突->修改成功->退出。否则继续尝试,总会有一个线程胜出。

无等待:在无锁的基础上更进一步,要求所有的线程都必须在有限步内完成,不会引起饥饿。进一步细分根据循环次数(有界无等待和线程无关等待)

 

JMM的关键技术:原子性,可见性和有序性

原子性:一个操作是不可中断的。即使多线程一起执行,一个操作一旦开始就不会被其他线程干扰。

可见性:一个线程修改了某一个共享变量的值,其他线程是否能立即直到这个修改。

有序性:一个线程内而言,代码总是从先往后依次执行。但是在并发时,程序的执行可能会出现乱序。

 

 

Happen-Before原则

重排的原则

1.程序顺序原则:一个线程内保证语义的串行性

2.volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性

3.锁规则:解锁(ulock)必然发生在随后的加锁(lock)前

4.传递性:A先于B,B先于C,那么A必然先于C

5.线程的start()方法先于它的每一个动作

6.线程的所有操作先于线程的终结(Thread.jion)

7.线程的中断(interrupt)先于被中断线程的代码

8.对象的构造函数执行、结束先于finalize()方法

 

 

线程中断

线程中断不会使线程立即退出,而是给线程发送一个通知,由目标线程自行决定。

以下三个方法:

public void Thread.interrupt() // 中断线程
public boolean Thread.isInterrupt() // 判断线程是否被中断
public static boolean Thread.inerrupt() //判断是否被中断,并清除当前中断状态

等待和通知

wait()方法和notif()方法。这两个方法并不在Thread类中,而是在输出objec类中。意味着任何对象都可以调用这两个方法。

public final void wait() throws Interuptedexception
public final native void notify()

例如线程A中调用了obj.wait()方法,那么线程A就会停止继续执行。直到其他线程调用了obj.notify()方法。这时obj对象就成为了两个线程之间的通信手段。

 

工作方式:

一个线程调用了obj.wait()方法后,就会进入object对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当obj.notify()被调用时,它就会从这个等待队列中随机选一个线程唤醒,这个选择是完全随机的。与之类似的有notifyAl()。会唤醒这个等待队列中的所有等待线程。

obj.wait()方法并不是可以随便调用,必须包含在对应的synchronization语句中。无论是wait()还是notify()都需要首先获取目标对象的一个监视器。一个对象只有一个监视器,必须得由其他对象释放后当前线程才能获得。

public class SimpleWN {
    final static Object object = new Object();
    public static class T1 extends Thread {
        public void run() {
           synchronized(object) {
            try{
                System.out.println(System.currentTimeMillis() + "T1 start!");
                object.wait();
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
           System.out.println(System.currentTimeMillis() + ":T1 end!")
        }
    } 
    }
    public static class T2 extends Thread{
    public void run() {
            synchronized(object) {
                System.out.println(System.currentTimeMillis() + "T2 start!");
                object.notify();
                System.out.println(Syetem.currentTimeMillis() + "T2 end");
                try{
                    Thread.sleep(2000);
                }catch(InterruptedException e) {
                }
            }
        }
    }      
    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
        
    }
}
执行结果会是:
T1 start!
T1 wait for object
T2 start! notify one thread
T2 end
T1 end

obj.wait()和thread.slee()都可以使线程等待,wait可以被唤醒,并且会释放目标对象得锁,sleep不会释放任何资源。

 

挂起suspend和继续执行resume

已不推荐,suspend()在导致线程暂停的同时,并不会释放任何资源。此时,任何想要访问被它暂用的锁时都会被牵连。直到对应线程执行resume。但是如果resume意外的在suspend之前执行了,挂起的线程可能会被永久挂起。

 

比较靠谱的方法是通过wait()和notify()方法来设置标志位判断线程是否被挂起

如下代码

public class GoodSuspend {
    public static Object u = new Object();

    public static class ChangeObejctThread extends Thread{
        volatile boolean suspendme = false;
        public void suspendMe() {
            suspendme = true;
        }

        public void resumeMe() {
            suspendme = false;
            synchronized (this) {
                notify();
            }
        }

        @Override
        public void run() {
            while(true) {
                synchronized (this) {
                    while(suspendme) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                synchronized (u) {
                    System.out.println("in ChangeObjectThread");
                }
                Thread.yield();
            }
        }
    }
    public static class ReadObejctThread extends Thread {
        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    System.out.println("in ReadObjectThread");
                }
                Thread.yield();
            }
        }
    }
}

等待线程结束(join)和谦让(yield)

join()和 join 没有参数表示无限等待,有参数表示等待最大时间。join()的本质是让调用线程的wait()在当前线程实例上。yield方法是让当前线程让出CPU,进入cpu资源的争夺,并不是停止执行。

 

volatile关键字与Java内存模型(JMM)

用volatile申明一个变量时,就等于告诉虚拟机,这个变量极有可能被某些程序或者线程修改。为了确保变量修改后,应用程序范围内的所有线程都能“看到”这个改动,虚拟机需要保证其的可见性。

 

volatoile关键字可以在一定程度上保证原子性,但是不能代替锁,也无法保证一些复合操作的原子性。

如下:

static volatile int i = 0;
public static class PlusTask implements Runnable {
    public void run() {
        for(int k = 0; k < 10000; k++) {
            i++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for(int i = 0; i < 10; i++) {
            thread[i] = new Thread(new PlusTask());
            thread[i].start();
            
        }
        for(int i = 0; i < 10; i++) {
            thread[i].join();
        }
        System.out.println(i);
    }
}

 

执行上诉代码,如果第6行i++是原子性的,那么最终的应该是100000,但实际上上诉代码的输出总会小于100000;

此外,volatile也能保证数据的可见性和有序性。

 

守护线程(Daemon),系统的守护者,在后台完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。与之对应的是用户线程,用户线程可以认为是系统的工作线程。如果用户线程全部结束,守护线程需要守护的对象也就不存在了,相应也会结束,只有守护线程时,Java虚拟机会自然退出。

 

synchronized关键字。作用是实现线程间的同步。对同步的代码加锁,使得每一次,只有一个线程进入同步块,从而保证线程间的安全性。

三种用法:

1.指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁

2.直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。

3.直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。

对象加锁:

public class SynchronizedTest extends Thread{
    static SynchronizedTest instance = new SynchronizedTest();
    static int i = 0;
    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            synchronized (instance) {     //加锁对象
                i++;
            }
        }
    }
}

实例方法加锁

public class SynchronizedTest extends Thread{
    static SynchronizedTest instance = new SynchronizedTest();
    static int i = 0;
    public synchronized void increase() {   //给实例方法加锁
        i++;
    }
    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            increase();   //给实例方法加锁
        }
    }
    public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(instance);
    Thread t2 = new Thread(instance);
    t1.start(); t2.start();‘’‘’‘’‘’‘’‘
    t1.join(); t2.join();
    }
}

静态方法加锁(注意这里没有静态对象)

public class SynchronizedTest extends Thread{
    static int i = 0;
    public static synchronized void increase() {
        i++;
    }
    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new SynchronizedTest());
    Thread t2 = new Thread(new SynchronizedTest());
    t1.start(); t2.start();
    t1.join(); t2.join();
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Java)