-
概念
* 并发:这俩个或多个时间在同一时间段内发生,交替执行
* 并行:这俩个或多个同时执行,同时执行
* 进程:每个进程都是一个独立的单位,可以理解为,进入内存的程序就是进程
* 线程:线程是进程的执行单位,负责当前进程中程序的执行,一个进程可以有多个线程
* cpu:中央处理器,对数据进行计算 -
线程的调度
分时调度:所有线程轮流使用CPU的使用权
抢占式调度:优先让优先级高的线程使用CPU -
多线程的运行原理
主线程开始,new的时候,开辟新的栈空间,执行run方法,之间互不影响,抢占Cpu的使用权。Java程序时抢占式调度 -
多线程的实现
1.继承Thread
实现步骤:1.创建一个Thread类的子类;2.重写run()方法,设置线程任务;3.调用start方法,开启线程,执行run方法***Thread构造方法 Thread() 分配一个新的 Thread对象 Thread(String name) 分配一个新的 Thread对象 Thread(Runnable target) 分配一个新的 Thread对象 Thread(Runnable target, String name) 分配一个新的 Thread对象 ***Thread类中的常用方法 void start() static void sleep(long millis)以指定的毫秒值进行暂停 void setName(String name) String getName() static Thread currentThread()返回当前正在执行的线程对象 boolean isAlive(): 判断一个线程是否存活。 static int activeCount(): 程序中活跃的线程数。 static int enumerate(): 枚举程序中的线程。 boolean isDaemon(): 一个线程是否为守护线程。 void setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) void setName(): 为线程设置一个名称。 void setPriority(int newPriority) 更改此线程的优先级。 int getPriority() 返回此线程的优先级。
2.实现Runnable接口,重写run方法,(只有一个run方法)
步骤:- 自定类MyRunnable 实现Runnable接口
- 重写run方法
- 创建MyRunnable 对象
- 创建Thread类对象,把MyRunnable 对象 作为参数传递给Thread类
实现Runnable接口的好处:
- 避免了单继承的局限性;
- 增强了程序的可扩展性,降低了程序的耦合性
3.实现Callable接口,依赖于线程池
步骤:- 自定义类实现Callable接口
- 重写call方法
- 创建线程池对象
- 调用方法执行Callable表示的线程任务
实现Callable接口
好处: 有返回值,可以抛异常
弊端: 代码相对比较复杂,用的比较少 -
线程的安全问题
当多个线程同时共享同一个全局变量或静态变量,做写的操作(修改变量值)时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作时不会发生数据冲突问题。- 同步代码块
synchronized(锁对象){ 可能出现线程安全问题的代码 }
- 同步方法
public synchronized void method(){ 可能产生线程安全问题的代码 }
注意:需要保证多个线程使用的是相同的锁,同步方法的锁对象时this,静态同步方法的锁对象是类的字节码文件
- 锁机制Lock
void lock()获取锁 void unlock()释放锁 ******************************* 4. void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁. 5. boolean tryLock():如果锁可用, 则获取锁, 并立即返回true, 否则返回false. 该方法和ock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用,当前线程仍然继续往下执行代码. 而lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行. 6. void unlock():执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程并不持有锁, 却执行该方法, 可能导致异常的发生. 7. Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将缩放锁。 8. getHoldCount() :查询当前线程保持此锁的次数,也就是执行此线程执行lock 方法的次数。 9. getQueueLength():返回正等待获取此锁的线程估计数,比如启动10 个线程,1 个线程获得锁,此时返回的是9 10. getWaitQueueLength:(Condition condition)返回等待与此锁相关的给定条件的线程估计数。比如10 个线程,用同一个condition 对象,并且此时这10 个线程都执行了condition 对象的await 方法,那么此时执行此方法返回10 11. hasWaiters(Condition condition) : 查询是否有线程等待与此锁有关的给定条件(condition),对于指定contidion 对象,有多少线程执行了condition.await 方法 12. hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁 10.hasQueuedThreads():是否有线程等待此锁 11.isFair():该锁是否公平锁 12.isHeldByCurrentThread(): 当前线程是否保持锁锁定,线程的执行lock 方法的前后分别是false 和true 13.isLock():此锁是否有任意线程占用 14.lockInterruptibly():如果当前线程未被中断,获取锁 15.tryLock():尝试获得锁,仅在调用时锁未被线程占用,获得锁 16.tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持,则获取该锁。
使用步骤:
1.Lock是一个接口,需要用他的实现类ReentrantLock,在成员位置创建一个ReentrantLock对象
2.在可能出现安全问题的代码前调用lock获取锁
3.在可能出现安全问题的代码后调用unlock释放锁- volatile关键字
Java 语言提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。volatile 变量具备两种特性,volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile 类型的变量时总会返回最新写入的值。不保证原子性。
变量可见性
其一是保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的
值对于其他线程是可以立即获取的。
禁止重排序
volatile 禁止了指令重排。
比sychronized 更轻量级的同步锁,在访问volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile 变量是一种比sychronized 关键字更轻量级的同步机制。volatile 适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
-
线程状态
- 线程状态(5种):
- NEW(新建状态):
- Runnable(就绪状态):
- Running(运行状态):
- Blocked(阻塞状态):没有抢到执行权,进入等待状态。
- 等待阻塞(wait,TimedWaiting(计时等待))
- 同步阻塞(lock)
- 其他阻塞(sleep,join等)
- Dead(死亡状态):
- 正常结束(run或者call执行完毕)
- 异常结束
- 调用stop(该方法通常容易导致死锁,不推荐使用)
- TimedWaiting(计时等待)状态,定时器
***Timer定时 -构造方法 Timer() Timer(String name) -成员方法 void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。 void schedule(TimerTask task, long delay, long period) 在指定 的延迟之后开始,重新 执行固定延迟执行的指定任务。 void schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。 new Timer().schedule(new MyTask(),...)执行完不结束 new Timer().schedule(new MyTask(new Timer),...)执行完结束 ***TimerTask定时器任务 protected TimerTask() 创建一个新的计时器任务。 boolean cancel() 取消此计时器任务。 abstract void run() 该定时器任务要执行的操作。
- 终止线程的四种方式
- 正常结束
- 使用退出标志推出(volatile关键字可以使变量同步)
- interrupt方法结束线程
- stop终止(不安全)
- 线程通信
等待唤醒机制(线程之间的通信)
注意:只有锁对象才可以调用wait和notify方法,必须是同一把锁,要在同步代码块中void wait() 导致当前线程等待 void wait(long timeout) 经过timeout时间没有被唤醒,则自动醒来 void notify() 唤醒正在等待对象监视器的单个线程。 void notifyAll() 唤醒正在等待对象监视器的所有线程。
- wait和sleep的区别
- 对于sleep()方法,我们首先要知道该方法是属于Thread 类中的。而wait()方法,则是属于
Object 类中的。 - sleep()方法导致了程序暂停执行指定的时间,让出cpu 该其他线程,但是他的监控状态依然保持,当指定的时间到了又会自动恢复运行状态。
- 在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
- 对于sleep()方法,我们首先要知道该方法是属于Thread 类中的。而wait()方法,则是属于
- Java后台线程
- 定义:守护线程–也称“服务线程”,他是后台线程,它有一个特性,即为用户线程提供公共服务,在没有用户线程可服务时会自动离开。
- 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
- 设置:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程
的方式是在 线程对象创建之前用线程对象的setDaemon 方法。 - 在Daemon 线程中产生的新线程也是Daemon 的。
- 线程则是JVM 级别的,以Tomcat 为例,如果你在Web 应用中启动一个线程,这个线程的生命周期并不会和Web 应用程序保持同步。也就是说,即使你停止了Web 应用,这个线程
依旧是活跃的。 - example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM 上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
- 生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当JVM 中所有的线程都是守护线程的时候,JVM 就可以退
出了;如果还有一个或以上的非守护线程则JVM 不会退出。
- 线程状态(5种):
-
线程组
线程组:
-默认情况下,所有的线程都属于主线程组
ThreadGroup getThreadGroup() 返回此线程所属的线程组
-我们也可以给线程设置组
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name) -
线程池
1,程序新建和启动一个线程花费的成本实际很高,因为涉及到要和系统进行交互。使用线程池可以很好的提高性能。线程池里面的每一个线程使用完成之后,不会死亡。而是会再次回到池中。等待下一次被使用
2,在jdk5之前,我们需要自己实现线程池,但是从5之后,java就内置了线程池。
新增了一个Executors 工厂类,通过这个类可以来生产线程池。
3,Executors 工厂类,ExecutorService是一个继承了Executor的接口
注意:一般用ExecutorService的实现类ThreadPoolExecutor
static ExecutorService newCachedThreadPool() 创建一个可缓存线程池
static ExecutorService newFixedThreadPool(int nThreads) 创建一个定长线程池
static ExecutorService newSingleThreadExecutor() 创建一个单线程化的线程池
-这些方法返回的都是ExecutorService 对象,这个对象就表示一个线程池,可以执行Runnable 对象或者 Callable对象表示的线程任务。
Future<?> submit?(Runnable task) 提交一个可运行的任务执行,
并返回一个表示该任务的未来。
<T> Future<T> submit?(Runnable task, T result) 提交一个可运行的任务执行,
并返回一个表示该任务的未来。
<T> Future<T> submit?(Callable<T> task) 提交值返回任务以执行,
并返回代表任务待处理结果的Future。
void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
如何实现?
步骤:
1.创建一个线程池对象
2.通过线程池对象的方法直接执行线程任务
3.如果需要把线程池对象销毁也也可调用方法
定时器
1.是一个应用比较广泛的线程工具。可以让线程任务以后台线程的方式执行。
-在java中 可以使用 Timer 和 TimerTask 类来实现功能。需要定义一个类来继承TimerTask
2,Timer操作任务
-构造方法
Timer()
Timer(String name)
-成员方法
void schedule(TimerTask task, long delay)
void schedule(TimerTask task, long delay, long period)
3,TimerTask创建任务
TimerTask() 创建一个新的计时器任务
boolean cancel() 取消此计时器任务
abstract void run() 该定时器任务要执行的操作
public class MyTask extends TimerTask{
private Timer t;
public MyTask() {
}
public MyTask(Timer t) {
this.t = t;
}
public void run() {
System.out.println("爆炸了。。。。");
t.cancel();
}
}
public class TimerDemo {
public static void main(String[] args) {
//创建定时器
Timer t = new Timer();
//3秒后执行爆破任务
//t.schedule(new MyTask(), 3000);
t.schedule(new MyTask(t), 3000);
}
}