备忘:每一个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:获取许可的过程中不允许被中断