备忘:每一个JAVA对象都有一个与之关联的监视器对象(加解锁)和一个包含线程的等待集合(wait)


一、多线程有什么用?

多线程可以用来做多件事情(同时)

二、线程运行的主要几种状态

创建:new thread()

运行(被激活或唤醒):start()

等待或唤醒:  

     object.wait()                                  必须用在synchronized方法中,因为需要用到锁     

     object.notify(唤醒当前单个线程)                必须用在synchronized方法中,因为需要用到锁

     object.notifyall(唤醒所有等待集合中的线程)   必须用在synchronized方法中,因为需要用到锁        

     sleep()指定睡眠时间已到被唤醒                  不能用在synchronized方法中,因为不会释放锁

     join()(其它线程等待,也能达到同步可见性)join执行完唤醒

     suspend(线程挂起,已废弃)、resume(挂起后重新开始,已废弃)、

结束:stop()已废弃

三、线程的启动方式:

1:

class Ctest extends Thread(继承thread类)

public void run(){}  //覆盖thread中的方法run

Ctest cat = new Ctest();

通过调用cat.start()方法,该方法在计划表中登陆这个线程,当该线程开始运行时,run方法自动被调用,实现多线程

2:

class Ctest implements Runnable

public void run(){}  //覆盖thread中的方法run

Ctest cat = new Ctest();

Thread t1 = new Thread(cat);

t1.start();

3:线程池执行

taskExecutor.execute(thread)

四:原子性

eg:对于非long型和double型域的读取和写入操作是原子操作,对象引用的读取和写入操作也是原子操作。eg:int

对于非volatile的long型和double型的域值时,分成两次操作完成,每次写入32位,所以有可能被其它线程干扰,所以需要声明为volatile.

五:线程中断,interrupt是线程之间的一种通信方式。一个典型应用场景:当线程在执行任务时会使用一个无限循环来重复执行,这时需要一种方式来结束线程的执行。

一种做法是使用volatile变量作为结束标记,一种就是向线程发出中断请求。isinterrupted:是否收到了终止请求。

六、多线程运行

1:如果线程不需要共享资源,那么各自跑各自的

2:如果需要共享资源

2.1:如果共享资源不需要锁:

     volatile用来对共享变量的访问进行同步,即下一次执行需要用到上一次执行的结果。使用场景:使用volatile变量作为循环结束的判断条件,用自身线程对象控制线程。

2.2:阻塞运行:

     synchronized,同步关键字,在线程对象上进行加锁解锁,以达到原子操作和线程之间的互斥,保证数据同步和数据可见性。即同一时刻只有一个线程允许执行特定的代码

     为了不太影响性能,正确的做法是把方法中需要同步的代码用synchronized代码块包围即可,而不必要扩大同步范围

eg:public class testSyn{

private int value = 0;

public synchronized int getNext(){   //同步加到方法上面,对象加锁

return value ++

}

public int getNext2(){

  synchronized(this){     //同步加到代码块上面,this代表当前对象实例,对象加锁

 return value++;

}

}

}

synchronized同步存在的问题是:阻塞线程的同步,限制吞吐量和性能,会带来死锁和优先级倒置的问题。

    另外,synchronized可以获取监视器对象上的锁,不过这种锁的获取和释放都是隐式进行的,自动完成,加锁的范围是固定的,无法把锁在对象之间进行传递,

使用不灵活。但好处是使用起来简单,不容易出现错误,如果需要使用更加灵活的锁机制,可以使用java.util.concurrent.locks包中提供的API(非阻塞运行)

2.3:非阻塞运行:既能保证准确性,又能提高性能

     Java平台利用了CPU提供的CAS指令来实现非阻塞操作,比较底层的实现。AtomicInteger和AtomicLong在实现线程安全的计数器时是最佳的选择。不是所有的CPU都支持CAS,在某些平台上,atomic仍然是通过内部的锁机制来实现的。

2.4:当多个线程需要同时访问一个共享变量时,不可避免产生数据竞争。通常的做法是利用同步机制解决,另外一种做法是使用线程局部变量ThreadLocal,相当于是线程的私有对象。应用场景:获取数据库连接

 

3:如果共享变量是集合类的对象,不适合直接使用java.util包中已有的集合类,这些集合类有些不是线程安全的,有些在多线程环境下性能比较差,

   更好的选择是java.util.concurrent包中的集合类,eg:concurrentMap 

七:BlockingQueue:队列

是多线程中比较常用的一种数据结构。多个线程对同一个队列进行操作,进行数据传递。典型的应用场景是生产者和消费者。支持阻塞方式和非阻塞方式

阻塞:put():放数据、take():取数据

非阻塞:offer():放数据、poll():取数据


八:Semaphore:信号量,管理资源许可。

使用java.util.concurrent.atomic包和java.util.concurrent.locks包提供的JAVA类可以满足基本的互斥和同步访问的需求,但是这些类抽象层次较低,使用起来比较复杂,更简单的做法是使用java.util.concurrent包中的高级同步对象,eg:信号量

"公平信号量"和"非公平信号量"的释放信号量的机制是一样的!不同的是它们获取信号量的机制:线程在尝试获取信号量许可时,对于公平信号量而言,如果当前线程不在队列的头部,则排队等候;而对于非公平信号量而言,无论当前线程是不是在队列的头部,它都会直接获取信号量


acquire方法:阻塞(线程等待)获取许可

tryAcquire:非阻塞(线程不等待,过段时间再检查资源是否可用)获取许可

acquireUninterruptibly:获取许可的过程中不允许被中断