线程基础
一、线程与进程的区别
1.什么是进程
进程就是在运行的程序,他是线程的集合 (进程中有多个b不同的执行路径,多个线程的集合,进程中一定有一个线程,这个线程就是主线程)
2. 什么是线程
线程就是进程的一个正在独立运行的一条执行路径(一个执行顺序,一个执行流程,执行路径)
3.什么是多线程
就是为了提高程序的i效率
总:使用多线程,是为了提高程序效率,每个线程互不影响,都是自己在独立运行(例:同时下载多个东西)
CPU处理进程的时候是采用时间片轮转的方式
二、多线程创建方式(五种)
1.继承Thread类
class CreateThread extends Thread {
// run方法中编写 多线程需要执行的代码
publicvoid run() {}
}
publicclass ThreadDemo {
publicstaticvoid main(String[] args) {
CreateThread createThread = new CreateThread();
createThread.start();
}
}
2.实现Runnable接口
class CreateRunnable implements Runnable {
@Override
publicvoid run() {}
}
publicclass ThreadDemo2 {
publicstaticvoid main(String[] args) {
CreateRunnable createThread = new CreateRunnable();
Thread thread = new Thread(createThread);
thread.start();
}
}
3.使用匿名内部类方式
public class ThreadDemo3 {
public static void main(String[] args) {
//1)通过thread子类创建匿名内部类
new Thread() {
public void run() {
System.out.println("线程开始执行......");
};
}.start();
//2)通过线程任务的方式创建匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程开始执行......");
}
}).start();
}
4.通过Callable和Future/FutureTask创建线程 (获取有返回值线程)
a. 创建Callable接口的实现类,并实现call()方法;
b. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callback对象的call()方法的返回值;
c. 使用FutureTask对象作为Thread对象的target创建并启动新线程;
d. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
public class CallableDemo implements Callable{
@Override
public Object call() {
//处理逻辑
}
}
public class CallableTest {
public static void main(String[] args) {
//--------------Future---------
//创建线程池
// ExecutorService es = Executors.newSingleThreadExecutor();
// //创建Callable对象任务
// CallableDemo calTask=new CallableDemo();
// //提交任务并获取执行结果
// Future future =es.submit(calTask);
// //关闭线程池
// es.shutdown();
//--------------FutureTask---------
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
CallableDemo calTask=new CallableDemo();
//创建FutureTask
FutureTask futureTask=new FutureTask<>(calTask);
//执行任务
es.submit(futureTask);
//关闭线程池
es.shutdown();
if(future.get()!=null){
//输出获取到的结果
System.out.println("future.get()-->"+future.get());
}else{
//输出获取到的结果
System.out.println("future.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}
5.通过线程池创建线程
ExecutorService pool = Executors.newFixedThreadPool(5);
Future future1 = pool.submit(new MyTask());
pool.shutdown();
//submit和execute区别
主要有三个区别:
1、接收的参数不一样。
ExecutorService的submit(Callable<T> task)参数是一个Callable对象,
submit(Runnable task)也可以接受Runnable对象;
executorService.execute(Runnable command)的参数是一个Runnable对象。
2、submit有返回值,而execute没有。
Method submit extends base method Executor.execute by creating and returning
a Future that can be used to cancel execution and/or wait for completion.
用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,
然后每个task告诉我它的执行结果,是成功还是失败,
如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。
个人觉得cancel execution这个用处不大,很少有需要去取消执行的,而最大的用处应该是第二点。
3、submit方便Exception处理。
---------------------
注:继承Thread和实现Runnable接口哪个好?
使用实现实现Runnable接口好,原因实现了接口还可以继续继承,继承了类不能再继承
常用线程api方法 | |
start() | 启动线程 |
currentThread() | 获取当前线程对象 |
getID() | 获取当前线程ID Thread-编号 该编号从0开始 |
getName() | 获取当前线程名称 |
sleep(long mill) | 休眠线程 |
Stop() | 停止线程, |
常用线程构造函数 | |
Thread() | 分配一个新的 Thread 对象 |
Thread(String name) | 分配一个新的 Thread对象,具有指定的 name正如其名。 |
Thread(Runable r) | 分配一个新的 Thread对象 |
Thread(Runable r, String name) | 分配一个新的 Thread对象 |
三、守护线程
Java中有两种线程,一种是User Thread用户线程,另一种是Daemon Thread守护线程。
通俗的来说:任何一个守护线程都是整个JVM中所有非守护线程的保姆
用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止(非守护线程,和主线程互不影响)
守护线程当进程不存在或主线程停止,守护线程也会被停止。
使用setDaemon(true)方法设置为守护线程
Daemon 的作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收)
四、运行状态
join作用
是让其他线程变为等待, t1.join();// 让其他线程变为等待,直到当前t1线程执行完毕,才释放。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
优先级
通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。
Thread t1 = new Thread();
t1.start();
// 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配
t1.setPriority(10);
t2.start();
六、多线程之间实现同步
1.为什么有线程安全问题?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题
2.如何解决多线程之间线程安全问题?
使多线程之间同步,即保证数据的原子性
synchronized 自动挡
lock jdk1.5并发包 手动
3.为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
4.问:什么是多线程之间同步?
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
方式:
同步代码块
可能会发生线程安全问题的代码,给包括起来。
private static Object oj = new Object();
public void sale() {
// 前提 多线程进行使用、多个线程只能拿到一把锁。
// 保证只能让一个线程 在执行 缺点效率降低
synchronized (oj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
}
对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
原理:有一个线程已经拿到锁了其他线程已经有了cpu的执行权一直在排队 等待其他线程释放资源,
锁的释放 是在代码执行完毕或者程序抛出异常
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源,效率低
同步函数
在方法上修饰synchronized 称为同步函数
public synchronized void sale() {
}
同步函数使用this锁。
静态同步函数
什么是静态同步函数?
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示。
synchronized (ThreadTrain.class) {
}
注:
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件
什么是多线程死锁?
:同步中嵌套同步,导致锁无法释放
七、多线程的三大特性
1.原子性、
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
独一无二,一致性,保证线程安全问题
2.可见性、(java内存模型)
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
Java提供了volatile关键字来保证可见性。(volatile保证线程之间的可见性,但不保证原子性)
jdk1.5并发包提供了很多原子类 ,可以通过AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。保证原子性
3.有序性
程序执行的顺序按照代码的先后顺序执行。
join,wait,notfi(多线程之间通讯)
:Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序
注:并发编程中两大核心:JMM抽象内存模型以及happens-before规则、三大特性(原子性、有序性、可见性)
八、java内存模型
Java内存模型-----属于 多线程可见性 jmm
java内存结构------属于 java内存分配
1.java内存模型 决定了一个线程与另一个线程 是否可见
2.java内存模型 主内存、(主要存放共享的全局变量)、私有本地内存(本地线程私有变量)
内容:--- 共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤
如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
九、多线程之间实现通讯
多线程之间通讯、其实就是多个线程在操作同一个资源,但操作的动作不同
wait()、notify、notifyAll()方法
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
wait与sleep区别?
wait 用于同步中,可以释放锁资源,sleep不会释放锁资源 ,wait 需要notify唤醒 ,sleep 时间到期,从休眠变到运行状态
相同 都是在做休眠
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态
注意:一定要在线程同步中使用,并且是同一个锁的资源