一、线程的创建
线程的创建方式1:通过继承Thread的方式
(1)创建一个继承于Thread的子类
(2)重写Thread的run()方法
(3)在main方法中创建子类的对象
(4)通过此对象调用start()方法 ①启动当前线程 ②调用当前线程的run()方法
注意:
不能通过对象直接调用run方法,需要调用start方法
再启动一个线程不能只创建一个对象,需要再创建一个线程的对象,再调用start方法
Thread类中的一些方法:
1、start()方法:启动当前线程的run()
2、run()方法:通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3、currentThread():静态方法,返回执行当前代码的线程
4、getName():获取当前线程的名字
5、setName():设置当前线程的名字
6、yield():调用此方法的线程释放当前CPU的执行权
7、join():在线程a中调用此方法,线程a就会进入阻塞状态,直到其他线程执行完之后,才会执行线程a
8、stop():已经过时了,直接结束当前线程
9、sleep():让当前方法“睡眠”指定的millitime:毫秒数 。在指定的毫秒时间内,当前线程是阻塞状态,此方法会出现异常处理,只能用try-catch去处理,不能抛出异常
10、isAlive():判断当前线程是否存活
线程的优先级:
说明:高优先级的线程抢占低优先级CPU的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的执行完,低优先级的才能执行
1、
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
2、如何获取和设置当前线程的优先级
getPriority()
setPriority()
线程的创建方式2:实现Runnable接口的方式
1、创建一个实现Runnable接口的类
2、实现类去实现Runnable接口中的方法run()
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象调用start方法
线程的创建方式3:实现Callable接口
和实现Runnable()方式相比,Callable功能更强大
1、创建Callable接口的实现类,并重写call()方法,call方法中声明的是需要执行的操作
①此方法有返回值
②方法可以抛出异常
③支持泛型返回值
④需要借助FutureTask类,比如获取返回结果
2、创建一个重写call()方法类的实例
例:NewThread newThread = new NewThread();
3、创建一个FutureTask类的对象,并将2的实例放进去
FutureTask task = new FutureTask(newThread);
4、创建Thread类的实例,并将task放进去
Thread thread = new Thread(task);
5、使用thread调用start方法,启动线程
thread.start();
线程的创建方式4(开发中最常用的):使用线程池
1、使用工具类Executors,提供指定线程数量的线程池
ExecutorSercive servive = Executors.newFixedThreadPool(10);
好处:
①提高响应速度
②降低资源消耗
③便于线程管理
2、可调用的两个方法
service.execute();//适合适用于Runnable,括号里面传入一个实现了Runnable接口的实现类
service.submit();//适合使用Callable,括号里面传入一个实现了Callable接口的实现类
二、第一种方式和第二种方式的对比
开发中:优先选择:实现Runnable接口的方式
原因:1、实现的方式没有类的单继承的局限性
2、实现的方式更适合来处理多个线程有共享数据的情况
联系:Thread类也实现了Runnable接口
相同点:两种方式都需要重写run方法
三、线程的生命周期
新建:
新建——>就绪:调用start()进入就绪状态
就绪:
就绪——>运行:得到CPU的执行权会进入运行状态
运行:
运行——>就绪:失去CPU的执行权或调用yield()方法会进入就绪状态,
运行——>死亡:执行完run()方法会进入死亡状态或者调用stop()方法;出现异常Error/Exception并且没处理
运行——>阻塞:执行sleep(long time)方法,join()方法,等待同步锁,wait()方法,suspend()方法(已经过时)
阻塞:
阻塞——>就绪:sleep()时间到,join()结束,获取同步锁,notify()/notifyAll(),resume()
死亡:结束
四、线程的安全
1、同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:从操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程共同操作的变量。
同步监视器:俗称:锁。任何一个类的对象,都可以充当锁,
要求:多个线程必须要用同一把锁。
同步的方式,解决了线程的安全问题。-----好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
2、同步方法
用synchronized关键字修饰的方法
总结:1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
2、非静态的同步,同步监视器:this
3、静态的同步方法,同步监视器是:当前类本身
3、使用同步锁
①创建同步锁实例,实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
②、调用里面方法,锁住线程
lock.lock();
③调用里面方法,释放锁
lock.unlock();放在finally中,保证一定能执行
五、线程通信
三个方法:
①wait():一旦执行了此方法,当前线程就会进入阻塞状态
②notify():一旦执行了此方法,就会唤醒wait的一个线程。如果有多个线程,先唤醒优先级高的
③notifyAll():一旦执行了此方法,会唤醒所有wait的线程
说明:
三个方法必须在同步代码块或者同步方法中
三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现异常IllegalMonitorStateException()
三个方法时定义在Object类中的,而不是Thread类中
六、重要面试题
1、sleep()和wait()方法的异同?
相同点:一旦执行了这两个方法,都会使线程进图阻塞状态
不同点:
①两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
②调用的要求不同:sleep()可以在任何需要的场景下调用。wait()只能在同步代码块或者同步方法中
③关于释放锁的问题:调用wait()会释放锁,而sleep()不会释放锁
2、生产者消费者问题(线程通信的应用 )