java多线程(juc)
一.基本知识
1.1什么是juc
java.util.concurrent 包的缩写
1.2.进程与线程
cpu个数、核数、线程数的关系?
cpu个数:是指物理上,也及硬件上的核心数;
核数:是逻辑上的,简单理解为逻辑上模拟出的核心数;
线程数:是同一时刻设备能并行执行的程序个数,线程数=cpu个数 * 核数
1.3.什么是进程?
是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
1.4什么是线程?
是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1.5java中线程的几个状态?
New(新建状态),
RUNNABLE(准备就绪,正在运行中),
BLOCKED,(等待另一个线程释放锁)
WAITING,(等待)
TIMED_WAITING,(过时不侯)
TERMANATED,(终结)
线程状态的切换:
- 新建状态(New):新创建了一个线程对象。
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁) - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.6wait(),sleep(),yield()的区别?
1.6.1 sleep():让线程睡眠
Thread类中的方法
sleep()方法是Thread类的静态方法,调用线程会暂时让出指定时间的CPU执行权,把CPU执行权让给其他线程,等到睡眠时间一到,该函数就会正常返回,此线程会自动苏醒。苏醒后,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。
注意:sleep方法会让出CPU资源,但是不会释放锁.
如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回。
1.6.2 wait():释放锁进入阻塞状态
Object类中的方法
当前线程进入阻塞状态,释放锁,在使用notify() 或 notifyAll()方法后在再次进入准备状态,继续在wait()方法后执行
需要注意的是:被唤醒并不代表立刻获得对象的锁,要等待执行notify()方法的线程执行完,即退出synchronized代码块后,当前线程才会释放锁,而呈wait状态的线程才可以获取该对象锁。
而且被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。
如果调用wait()或者nofity()方法时没有事先获取该对象的监视器锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕获异常。
notify方法只会(随机)唤醒一个正在等待的线程,而notifyAll方法会唤醒所有正在等待的线程。如果一个对象之前没有调用wait方法,那么调用notify方法是没有任何影响的。
1.6.3 yield():让出CPU执行权
Thread类中的方法
yield()方法是Thread的静态方法,它的作用是放弃当前的CPU资源,将它让给其他优先级更高的线程去占用CPU执行时间。但放弃时间不确定,有可能刚刚放弃,马上又获得CPU时间片。这里需要注意的是yield()方法和sleep方法一样,线程并不会让出锁,和wait方法不同
sleep与yield方法的区别在于,当线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程.而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行 。
下面是在其他网站摘抄的使用对象
栈长没用过 yield,感觉没什么鸟用。
如果一定要用它的话,一句话解释就是:yield 方法可以很好的控制多线程,如执行某项复杂的任务时,如果担心占用资源过多,可以在完成某个重要的工作后使用 yield 方法让掉当前 CPU 的调度权,等下次获取到再继续执行,这样不但能完成自己的重要工作,也能给其他线程一些运行的机会,避免一个线程长时间占有 CPU 资源。
1.6.4 join():等待线程执行终止
Thread类中的方法
在线程操作中,可以使用join()方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
如果在一个线程A中执行了thread.join()语句,其含义是:当前线程A必须等待thread线程终止之后才从thread.join()返回。
1.6.5 interrupt():
不要以为它是中断某个线程!它只是线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么 这个线程还是不会中断的!
1.7 线程优先级
线程的调度
1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
- static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。 - static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。 - static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
二.Lock接口
1.synchronized复习
多线程编程模版
创建资源类
在资源类中创建同步方法同步代码块
2.Lock是什么
Lock接口的实现提供了比同步代码块和同步方法更好的更广泛的锁操作,它允许更灵活的接口和更广泛的锁操作,并支持多个关联操作对象
3.Lock的使用
Lock lock = new ReentrantLock();
lock.lock();
try{
method body
}finally{
lock.unlock();
}
4.创建多线程的方式
继承Thread类 ----new Thread().start()
实现Runnable接口 ---- new Thread(new Runnable(){ 重写run方法 }).start();
实现Callable接口 ----FutureTask ft = new FutureTask(new Callable(){ 重写call() }); ft.get()
线程池
5.线程间通信
判断–>干活–>通知
使用synchronized实现多个线程交替加减某一个值
//分别起两个对number进行交替加减1的操作
class ThreadDemo{
int number = 0;
public synchronized void incr()throws Exception{
//使用while循环判断number的值 避免使用if多个线程操作的时候 某个线程在等待后抢到资源直接执行下方语句,线程wait后应再次判断是否符合条件在执行(多线程之间的虚假唤醒)
while(number != 0){
this.wait();
}
number++;
System.out.println(number);
this.notifyAll();
}
public synchronized void decr()throws Exception{
while(number != 1){
this.wait();
}
number--;
System.out.println(number);
this.notifyAll();
}
}
使用Lock实现多个线程交替加减某一个值
public class ThreadTest {
int number = 0;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
public void incr(){
lock.lock();
try {
while (number != 0){
condition1.await();
}
number++;
System.out.println(number);
}catch (Exception e){
e.printStackTrace();
}finally {
condition2.signal();
}
}
public void decr(){
lock.lock();
try {
while (number != 1){
condition2.await();
}
number--;
System.out.println(number);
}catch (Exception e){
e.printStackTrace();
}finally {
condition1.signal();
}
}
@Test
public void test1(){
User u = new User();
new Thread(()->{
for (int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName());
u.incr();
}
},"thread1").start();
new Thread(()->{
for (int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName());
u.decr();
}
},"thread2").start();
}
}
6.list集合线程不安全
多线程操作list集合是时报java.util.ConcurrentModificationException 并发修改异常
解决方案:
使用Vector()
或者List list = Collections.synchronizedList(new ArrayList());
但是上面两种解决方案的list集合中的方法都为 synchronized的方法是串行的 无法并行操作list集合
优化方案:
使用 CopyOnWriteArrayList();
CopyOnWriteArrayList();使用写时复制技术
对数组中的元素进行操作时 会加锁 复制一个新的集合进行操作 操作完毕会替换原有的集合
当查询集合中的元素的时候 不会加锁 直接返回;
这样保证了查询不加锁 修改加锁 可能数据实时性稍差 但是性能增加了。
Set的同步集合 CopyOnWriteArraySet() map的同步集合 ConcurrentHashMap()