多线程的概述
什么是线程
线程是程序执行的一条路径, 一个进程中可以包含多条线程 多线程并发执行可以提高程序的效率, 可以同时完成多项工作 多线程并行和并发的区别
并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU) 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。 Java程序运行原理和JVM的启动是多线程的吗
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。 JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
多线程的实现
继承Thread
定义类继承Thread 重写run方法 把新线程要做的事写在run方法中 创建线程对象 开启新线程, 内部会自动执行run方法
class MyThread extends Thread {
public void run ( ) {
for ( int i = 0 ; i < 3000 ; i++ ) {
}
}
}
实现Runnable
定义类实现Runnable接口 实现run方法 把新线程要做的事写在run方法中 创建自定义的Runnable的子类对象 创建Thread对象, 传入Runnable 调用start()开启新线程, 内部会自动调用Runnable的run()方法
public class Demo3_Runnable {
public static void main ( String[ ] args) {
MyRunnable mr = new MyRunnable ( ) ;
Thread t = new Thread ( mr) ;
t. start ( ) ;
for ( int i = 0 ; i < 3000 ; i++ ) {
System. out. println ( "bb" ) ;
}
}
}
class MyRunnable implements Runnable {
@Override
public void run ( ) {
for ( int i = 0 ; i < 3000 ; i++ ) {
System. out. println ( "aaaa" ) ;
}
}
}
实现Runnable的原理
看Thread类的构造函数,传递了Runnable接口的引用 通过init()方法找到传递的target给成员变量的target赋值 查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法 两种方式的区别
继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
好处是:可以直接使用Thread类中的方法,代码简单 弊端是:如果已经有了父类,就不能用这种方法 实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂 匿名内部类实现
new Thread ( ) {
public void run ( ) {
for ( int i = 0 ; i < 3000 ; i++ ) {
System. out. println ( "aaaaaaa" ) ;
}
}
} . start ( ) ;
new Thread ( new Runnable ( ) {
public void run ( ) {
for ( int i = 0 ; i < 3000 ; i++ ) {
System. out. println ( "bb" ) ;
}
}
} ) . start ( ) ;
Thread类
构造方法:
public Thread() :分配一个新的线程对象。 public Thread(String name) :分配一个指定名字的新的线程对象。 public Thread(Runnable target) :分配一个带有指定目标新的线程对象。 public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
Thread类的常用方法
获取名字
new Thread ( "lwb" ) {
public void run ( ) {
for ( int i = 0 ; i < 1000 ; i++ ) {
System. out. println ( this . getName ( ) ) ;
}
}
} . start ( ) ;
new Thread ( "zjf" ) {
public void run ( ) {
for ( int i = 0 ; i < 1000 ; i++ ) {
System. out. println ( this . getName ( ) ) ;
}
}
} . start ( ) ;
设置名字
Thread t1 = new Thread ( ) {
public void run ( ) {
for ( int i = 0 ; i < 1000 ; i++ ) {
System. out. println ( this . getName ( ) ) ;
}
}
} ;
Thread t2 = new Thread ( ) {
public void run ( ) {
for ( int i = 0 ; i < 1000 ; i++ ) {
System. out. println ( this . getName ( ) ) ;
}
}
} ;
t1. setName ( "lwb" ) ;
t2. setName ( "zjf" ) ;
t1. start ( ) ;
t2. start ( ) ;
获得当前线程的对象
Thread.currentThread(), 主线程也可以获取
new Thread ( new Runnable ( ) {
public void run ( ) {
for ( int i = 0 ; i < 1000 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) ) ;
}
}
} ) . start ( ) ;
new Thread ( new Runnable ( ) {
public void run ( ) {
for ( int i = 0 ; i < 1000 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) ) ;
}
}
} ) . start ( ) ;
Thread. currentThread ( ) . setName ( "我是主线程" ) ;
System. out. println ( Thread. currentThread ( ) . getName ( ) ) ;
休眠线程
Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
new Thread ( "java" ) {
public void run ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
System. out. println ( getName ( ) ) ;
try {
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
} . start ( ) ;
new Thread ( "c++" ) {
public void run ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
System. out. println ( getName ( ) ) ;
try {
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
} . start ( ) ;
守护线程
setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
Thread t1 = new Thread ( "守护线程" ) {
public void run ( ) {
for ( int i = 0 ; i < 50 ; i++ ) {
System. out. println ( getName ( ) ) ;
try {
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
} ;
Thread t2 = new Thread ( "非守护线程" ) {
public void run ( ) {
for ( int i = 0 ; i < 5 ; i++ ) {
System. out. println ( getName ( ) ) ;
try {
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
} ;
t1. setDaemon ( true ) ;
t1. start ( ) ;
t2. start ( ) ;
加入线程
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续 join(int), 可以等待指定的毫秒之后继续
final Thread t1 = new Thread ( "最初线程" ) {
public void run ( ) {
for ( int i = 0 ; i < 50 ; i++ ) {
System. out. println ( getName ( ) ) ;
try {
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
} ;
Thread t2 = new Thread ( "加入线程" ) {
public void run ( ) {
for ( int i = 0 ; i < 50 ; i++ ) {
if ( i == 2 ) {
try {
t1. join ( 30 ) ;
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
System. out. println ( getName ( ) ) ;
}
}
} ;
t1. start ( ) ;
t2. start ( ) ;
礼让线程
设置线程优先级
同步代码块和同步方法
同步代码块
何时需要使用同步代码块:
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步. 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码. 同步代码块:
使用synchronized关键字加上一个锁对象(可以是任意的)来定义一段代码, 这就叫同步代码块 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
class Printer {
Object o = new Object ( ) ;
public static void print1 ( ) {
synchronized ( o) {
System. out. print ( "a" ) ;
System. out. print ( "a" ) ;
System. out. print ( "\r\n" ) ;
}
}
public static void print2 ( ) {
synchronized ( o) {
System. out. print ( "b" ) ;
System. out. print ( "b" ) ;
System. out. print ( "\r\n" ) ;
}
}
}
同步的方法:
使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
class Printer {
public static void print1 ( ) {
synchronized ( Printer. class ) {
System. out. print ( "a" ) ;
System. out. print ( "a" ) ;
System. out. print ( "\r\n" ) ;
}
}
public static synchronized void print2 ( ) {
System. out. print ( "b" ) ;
System. out. print ( "b" ) ;
System. out. print ( "\r\n" ) ;
}
}
线程安全问题
多线程并发操作同一数据时, 就有可能出现线程安全问题 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
public class Demo2_Synchronized {
public static void main ( String[ ] args) {
TicketsSeller t1 = new TicketsSeller ( ) ;
TicketsSeller t2 = new TicketsSeller ( ) ;
TicketsSeller t3 = new TicketsSeller ( ) ;
TicketsSeller t4 = new TicketsSeller ( ) ;
t1. setName ( "窗口1" ) ;
t2. setName ( "窗口2" ) ;
t3. setName ( "窗口3" ) ;
t4. setName ( "窗口4" ) ;
t1. start ( ) ;
t2. start ( ) ;
t3. start ( ) ;
t4. start ( ) ;
}
}
class TicketsSeller extends Thread {
private static int tickets = 100 ;
static Object obj = new Object ( ) ;
public TicketsSeller ( ) {
super ( ) ;
}
public TicketsSeller ( String name) {
super ( name) ;
}
public void run ( ) {
while ( true ) {
synchronized ( obj) {
if ( tickets <= 0 )
break ;
try {
Thread. sleep ( 10 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
System. out. println ( getName ( ) + "...这是第" + tickets-- + "号票" ) ;
}
}
}
}
Runtime类
Runtime类是一个单例类
Runtime r = Runtime. getRuntime ( ) ;
r. exec ( "shutdown -a" ) ;
Timer类
Timer类:计时器
public class Demo5_Timer {
public static void main ( String[ ] args) throws InterruptedException {
Timer t = new Timer ( ) ;
t. schedule ( new MyTimerTask ( ) , new Date ( 114 , 9 , 15 , 10 , 54 , 20 ) , 3000 ) ;
while ( true ) {
System. out. println ( new Date ( ) ) ;
Thread. sleep ( 1000 ) ;
}
}
}
class MyTimerTask extends TimerTask {
@Override
public void run ( ) {
System. out. println ( "起床敲代码" ) ;
}
}
线程状态
线程的六种状态 状态的相互转换
线程通信
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。 为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。 如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制
等待唤醒机制
什么是等待唤醒机制
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。 就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。 wait/notify 就是线程间的一种协作机制。 等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象 上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中 notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。 notifyAll:则释放所通知对象的 wait set 上的全部线程。 注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。 总结如下:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成BLOCKED 状态 调用wait和notify方法需要注意的细节
wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。 wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。 wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。 生产者和消费者案例(吃包子和包子铺)
思路分析 创建一个包子的类
public class Baozi {
public String pi;
public String xian;
public boolean flag = false ;
}
public class Baozipu extends Thread {
private Baozi bz;
public Baozipu ( String name, Baozi bz) {
super ( name) ;
this . bz= bz;
}
@Override
public void run ( ) {
int count = 0 ;
while ( true ) {
synchronized ( bz) {
if ( bz. flag == true ) {
try {
bz. wait ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
System. out. println ( "包子铺正在准备包子" ) ;
bz. pi = "薄皮" ;
bz. xian = "牛肉馅" ;
count++ ;
bz. flag = true ;
System. out. println ( "包子做好了:" + bz. pi+ bz. xian+ "的包子" ) ;
System. out. println ( "快来买新鲜的包子!!" ) ;
bz. notify ( ) ;
}
}
}
}
public class Luren extends Thread {
private Baozi bz;
public Luren ( String name, Baozi bz) {
super ( name) ;
this . bz = bz;
}
@Override
public void run ( ) {
while ( true ) {
synchronized ( bz) {
if ( bz. flag == false ) {
try {
bz. wait ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
System. out. println ( getName ( ) + "正在吃" + bz. pi+ bz. xian+ "的包子" ) ;
try {
sleep ( 1000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
System. out. println ( "---------------------------------" ) ;
bz. flag= false ;
bz. notify ( ) ;
}
}
}
}
public class test {
public static void main ( String[ ] args) {
Baozi bz = new Baozi ( ) ;
Baozipu bzp = new Baozipu ( "包子铺" , bz) ;
Luren lr = new Luren ( "lwb" , bz) ;
bzp. start ( ) ;
lr. start ( ) ;
}
}
结果
线程池
线程池创建的原因
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。 线程池概念
其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。 合理利用线程池能够带来三个好处:
降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 线程池使用
Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService 。 要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。 Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量) 获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。 使用线程池中线程对象的步骤:
使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 创建一个类,实现Runnable接口,重写run方法,设置线程任务 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
public class ThreadPool {
public static void main ( String[ ] args) {
ExecutorService es = Executors. newFixedThreadPool ( 2 ) ;
es. submit ( new RunnableImpl ( ) ) ;
es. submit ( new RunnableImpl ( ) ) ;
es. submit ( new RunnableImpl ( ) ) ;
es. shutdown ( ) ;
es. submit ( new RunnableImpl ( ) ) ;
}
}
public class RunnableImpl implements Runnable {
@Override
public void run ( ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "创建了一个新的线程执行" ) ;
}
}