java线程基础

什么是线程:

* 线程是进程执行的一条路径, 一个进程中可以包含多条线程,从而可以并发的执行

为什么使用:

Cpu和外设之间运行速度不匹配,提高cpu的利用率

将大任务划分多个小任务,从而提高运行效率

业务并行的需要(如有的编辑器边运行,边保存)

应用场景:

* 迅雷开启多条线程一起下载
* QQ同时和多个人一起视频
* 服务器同时处理多个客户端请求

线程特点:

 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)

* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。

怎么实现:

1.继承Thread

* 定义类继承Thread

* 重写run方法

* 把新线程要做的事写在run方法中(在这个方法里实现自己的业务逻辑)

 2.实现Runnable

* 定义类实现Runnable接口

* 实现run方法

* 把新线程要做的事写在run方法中

* 创建自定义的Runnable的子类对象

* 创建Thread对象, 传入Runnable子类对象

* 调用start()开启新线程, 内部会自动调用Runnable的run()方法

3.实现cannable接口,可以返回

多线程(实现Runnable的原理):

线程对象的构造函数传入runnable的引用,给该对象的成员变量赋值,当run方法运行时判断成员变量是否为空,若不为空则执行引用所对应的子类方法的run方

两种方法的优劣:

 继承Thread
    * 好处是:可以直接使用Thread类中的方法,代码简单
    * 弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口
    * 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    * 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码相对复杂

怎么用多线程:

通过thread对象调用其start()方法;只是注意run方法里面的代码有没有调用其他对象,以及代码有没有设置同步

  1. 通过 声明thread对象调用start方法
  2. 通过匿名内部类的方式

线程类使用的常见方法:

成员方法:

getName()方法获取线程对象的名字

setName(String)方法可以设置线程对象的名字

setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

 join(),前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续

静态方法:

Thread.currentThread(), 获取当前线程的对象,主线程也可以获取22地方2.

Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 

thread.interrupt(),中断某个线程,有两种情况,一种是线程正在运行(Thread.currentThread().isInterrupted()这个状态默认为false,thread.interrupt()会将其设置成true,本线程中有这个判断,从而自己结束线程),另一种是线程在阻塞状态(首先被阻塞的会被唤醒,会将Thread.currentThread().isInterrupted()重新设置成false并抛出InterruptedException,让用户决定捕获异常后继续怎么操作)

什么是同步:

是多个线程间的关系,当其中的一个线程执行同步的代码块时,与其同步的其他线程不能执行其代码块.

应用场景:

 当多线程并发, 有多段代码同时执行时, 操作相同的数据,引发线程安全,我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.

怎么实现:

使用synchronize关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

多个同步代码块如果使用相同的锁对象, 那么他们就是同步的(相同代码块,不同代码块相互之间的),并且锁对象可以是任意但不能是匿名的;

一般 非静态同步函数的锁是:this

  * 静态的同步函数的锁是:字节码对象

 同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。

注意事项:

* 同步方法只能保证当前方法的原子性,不能保证多个业务方法之间的互相访问的原子性。

 * 注意在商业开发中,多方法要求结果访问原子操作,需要多个方法都加锁,且锁定统一个资源。

* 锁可重入.同一个线程,多次调用同步代码,锁定同一个锁对象,可重入(相当于一个同步方法)。其实重入锁就是避免调用时产生死锁的问题

 * 子类同步方法覆盖父类同步方法。可以指定调用父类的同步方法,可重入。

* 当同步方法中发生异常的时候自动释放锁资源。不会影响其他线程的执行。注意,同步业务逻辑中,如果发生异常如何处理。

* 尽量在商业开发中避免同步方法,使用同步代码块。细粒度解决同步问题,可以提高效率(不需要同步的代码放在同步中)。

 * 锁对象变更问题, 同步代码一旦加锁后,那么会有一个临时的锁引用应用锁对象,和真实的引用无直接关联,在锁未释放之前,修改锁对象引用,不会影响同步代码的执行(该同步代码块锁对象没有变)。

* 在定义同步代码块时,不要使用常量对象作为锁对象。

锁的种类:
*

synchronized(this)和synchronized加在方法都是锁当前对象。

* private Object o = new Object(); synchronized(o){}

* 静态同步方法,锁的是当前类型的类对象。

线程通信:

什么时候需要通信
    * 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
    * 如果我们希望他们有**规律的执行**, 就可以使用通信, 例如每个线程执行一次打印
2.怎么通信
    * 如果希望线程等待, 就通过锁对象调用wait()
    * 如果两个方法之间希望唤醒等待的线程, 就调用通过锁对象notify();两个以上的线程随机唤醒,执行的结果就是随机的,而不是想要的效果.
    * 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

notifyAll()方法是唤醒所有线程
    * JDK5之前无法唤醒指定的一个线程
    * 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件(当有唤醒时,因为所有的线程都被唤醒了,需要在回到判断条件处决定是否该它执行了,若是if唤醒了就会都执行)

因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法都定义在Object这个类中

sleep方法和wait方法的区别


 * a,sleep方法必须传入参数,参数就是时间,时间到了自动醒来
 *   wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
 * b,sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡
 *   wait方法在同步函数或者同步代码块中,释放锁

调用的对象不同,sleep是Thread类的静态方法,wait是Object的方法

wait方法依赖于同步,而sleep方法可以直接调用。而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁

其他相关对象应用

volatile应用:

* 通知OS操作系统底层,在CPU计算过程中,都要检查内存中数据的有效性。保证最新的内存数据被使用(若是变量没用指定volatile一般是线程直接从cpu的缓存中读取,若是其他线程已经改了内存中的数据,就会出现数据不一致的情况,一般用在非同步代码的情况下)。volatile只能保证可见性,不能保证原子性。如下面的例子t1线程是不会终止的

其实这个一个线程需要将缓存写入内存,一个线程能够将内存的最新值读取到缓存

volatile是具体怎么实现的?

要达到,更变值的线程,写到内存后,其他的线程需要感知到,并获得最新的,从而保证了数据正确性,java中提供了jmm规范由虚拟机来实现。

AtomicXxx:

AtomicInteger count = new AtomicInteger(0);//有多种相关的类型,保证多线程操作此数据的安全性

void m(){

for(int i = 0; i < 10000; i++){

/*if(count.get() < 1000)*/

count.incrementAndGet();

}

}

* 同步类型

 * 原子操作类型。 其中的每个方法都是原子操作。可以保证线程安全。

门闩 - CountDownLatch

门闩相当于在一个门上加多个锁,当线程调用 await 方法时,会检查门闩数量,如果门闩数量大于 0,线程会阻塞等待。当线程调用 countDown 时,会递减门闩的数量,当门闩数

量为 0 时,await 阻塞线程可执行。

CountDownLatch latch = new CountDownLatch(5);//门闩定义,参数n表示加几个锁

void m1(){

try {

latch.await();// 等待门闩开放。其后的所有代码才能执行

}

void m2(){

for(int i = 0; i < 10; i++){

if(latch.getCount() != 0){

System.out.println("latch count : " + latch.getCount());

latch.countDown(); // 减门闩上的锁。

}

可以和锁混合使用,或替代锁的功能。在门闩未完全开放之前等待。当门闩完全开放后执行。 避免锁的效率低下问题。

什么是线程安全:

多线程并发操作同一数据时, 就有可能出现线程安全问题

怎么解决:

使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作

同步引发的问题:

死锁: 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁

jdk1.5新特性:

Lock和synchronize主要区别,synchronize是由jvm帮忙实现的,而lock是由java.utils.currentl来实现的

Reentrantlock是包含sync工具类

private ReentrantLock r = new ReentrantLock();//锁对象

private Condition c1 = r.newCondition();

private Condition c2 = r.newCondition();

  1. 同步
        * 使用ReentrantLock类的lock()和unlock()方法进行同步,相同reentrantlock对象的调用lock()方法定义的代码块是同步的
     2.通信
        * 使用ReentrantLock类的newCondition()方法可以获取Condition对象,condition对象有多个,不同的方法对应不同的condition对象
        * 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
        * 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

尝试锁

 尝试锁,isLocked = lock.tryLock(); 如果有锁,无法获取锁标记,返回false。 如果获取锁标记,返回true

可以带有参数,阻塞尝试锁,阻塞参数代表的时长,尝试获取锁标记。 如果超时,不等待。直接返回。

isLocked = lock.tryLock(5, TimeUnit.SECONDS);

if(isLocked){

System.out.println("m2() method synchronized");

}else{

System.out.println("m2() method unsynchronized");

}

4.可打断

只能打断阻塞状态中的线程,用其他线程调用某线程的interrupt()后,该线程就会抛出InterruptedException类型的异常,从而获得执行权力,一般在抛出异常的catch中实现被打断后的相关逻辑

 * 阻塞状态: 包括普通阻塞,等待队列,锁池队列。

 * 普通阻塞: sleep(10000), 可以被打断。调用thread.interrupt()方法,可以打断阻塞状态,抛出异常。

 * 等待队列: wait()方法被调用,也是一种阻塞状态,只能由notify唤醒。无法打断

 * 锁池队列: 无法获取锁标记。不是所有的锁池队列都可被打断。

 *  使用ReentrantLock的lock方法,获取锁标记的时候,如果需要阻塞等待锁标记,无法被打断。

 *  使用ReentrantLock的lockInterruptibly方法(和lock一样只是可以被打断),获取锁标记的时候,如果需要阻塞等待,可以被打断。

  1. 公平锁

在没有使用公平锁的情况下,操作系统随机的选择线程执行;在使用同一公平锁对象的不同线程是根据等待的时长,时长较长的优先被执行,和非公平锁相比,公平锁是将所有的线程放入队列后,从队列前后依次执行;非公平锁加入队列之前都会尝试获得锁,若是获得了就会插队执行

// 定义一个公平锁

private static ReentrantLock lock = new ReentrantLock(true);

读写锁:如果同步代码块加的是读锁,线程之间可以并发执行,若是锁对象用的是写锁,线程之间就是同步运行的

使用场景,一般应用于写少读多的情况下,如对缓存的读写操作,写的时候,让读也变成了同步,从而避免读到了脏数据,但都是读的情况下就是异步的

Lock锁是真么实现的:主要通过其中的内部类AQS,由内部类提供的各种工具来实现

其中AQS中有一个双向链表的储存元素,来存储没有竞争到的线程,这些线程都封装成Node,

在线程尝试过程中没有获得锁的相关过程

程序是怎么保证同步执行:模拟三个线程争抢锁的过程,state是表示锁的状态,当它大于0(因为重入锁,在上面自增;当执行unlock的时候,就会将state复位成0,并且唤醒阻塞中的线程)的时候表示已经有线程获得锁了,其他线程就进入了双向队列中

ThreadLocal:

 * 就是一个Map。key - 》 Thread.getCurrentThread().  value - 》 线程需要保存的变量。

 * ThreadLocal.set(value) -> map.put(Thread.getCurrentThread(), value);

 * ThreadLocal.get() -> map.get(Thread.getCurrentThread());

 * 内存问题 : 在并发量高的时候,可能有内存溢出。因为threadlocal一般指定回收或是所有的线程都回收或是jvm退出时,其才会被回收

 * 使用ThreadLocal的时候,一定注意回收资源问题,每个线程结束之前,将当前线程保存的线程变量一定要删除 。 ThreadLocal.remove();原因见下图

Condition是怎么完成阻塞的,有一个condition阻塞队列存入被阻塞封装了线程的node节点(不同的condition的阻塞队列是不一样的 ),是一个单向链表,同时其会释放锁,并唤醒所队列的某个线程继续执行,注意其与锁队列的区别

若是共享锁,就会唤醒所有的线程,共同获得锁,从而并行的执行

新笔记

Thread 是实现runnable接口的

Run方法是调用runnable的run方法的,所以没有通过继承thread方式重写run方法,二是通过实现runnable接口的方式,传递runnable的相关参数,就是执行重写runnable的run方法

为什么是start启动线程而不是run,run方法和其他普通方法是一样的,就不可能是启动线程的,因为真正的线程是操作系统提供,jvm根据不同的操作系统,启动操作系统的线程,然后又该线程区调用run方法里面的逻辑

为什么sleep wait join等阻塞状态的有编译时异常处理?

若是没有触发解除阻塞的动作,线程这些线程有可能永远停止不了

与上文中4、可打断  知识点相关

其实用的是复位的技术,调用该线程中断,当被中断线程从阻塞的进行唤醒同时获得锁时,知道自己被中断了,就会抛出异常,将线程状态复位成false,让捕获异常的catch进行相关的处理

为什么这样搞:

避免类似Linux里面kill操作,直接将线程终止,而不管线程处于什么运行状态,采取目前复位的方法,让被中断线程自己去处理完相应的逻辑

首先唤醒-复位-catch里执行

线程中断,也是调用操作系统的线程,去修改线程是否中断这个变量的值

线程复位:

由于锁是在对象上,就可以理解普通对象与class对象的范围和对象生命周期相关的

为了提升效率,会使用不同类型的锁,其实只有重量级锁是真正的加锁了,这样会导致没有获得锁的线程挂起,重新调度就会从用户态向内核态切换消耗性能,其他都是无锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值