多线程基础篇
多线程
多线程概念
进程:正在进行中的程序
线程:是进程中一个负责程序执行的控制单元(执行路径)一个进行中可以有多个执行线程,称之为多线程。
一个进程中至少要有一个线程
开启多个线程是为了同时运行多部份代码
每一个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务
好处:解决了多部分同时运行的问题
弊端:线程太多会导致效率降低
其实应用程序的执行都是cpu
在做着快速的切换完成的,这个切换是随机的。
Jvm启动时就启动了多个线程,至少有两个小城可以分析出来。
- 执行main函数的线程,该小城的任务代码都定义在main函数中
- 负责垃圾回收的线程
如何创建一个线程
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
继承Thread类创建
步骤:
- 定义一个类继承
Thread
类 - 覆写
Thread
类中的run
方法。 - 直接创建
Thread
的子类对象创建线程 - 调用
start
方法开启线程并调用小城的任务run
方法执行
public class Demo1 extends Thread{
@Override
public void run() {
// 覆写run方法用于执行线程任务,将要执行的任务定义在run方法中
for (int i=0; i<10; i++) {
System.out.println("副线程中执行的代码-->" + i);
}
}
public static void main(String[] args) {
//创建Thread的线程对象
Demo1 demo = new Demo1();
// run方法由Jvm去调用执行,我们只需要调用父类Thread的start方法开启线程即可
demo.start();
System.out.println("主线程结束");
}
}
还可以调用父类Thread
的getName()
方法获取线程名称,获取当前正在执行的线程名称则是Thread.currentThread.getName()
https://www.cnblogs.com/3s540/p/7172146.html
实现Runnable接口
通过实现Runnable接口创建并启动线程一般步骤如下:
- 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
- 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
- 第三部依然是通过调用线程对象的start()方法来启动线程
public class Demo2 implements Runnable{
@Override
public void run() {
// 覆写run方法用于执行线程任务,将要执行的任务定义在run方法中
for (int i=0; i<10; i++) {
System.out.println("副线程中执行的代码-->" + i);
}
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
Thread t1 = new Thread(demo2);
t1.start();
System.out.println("主线程结束......");
}
}
相比继承Thread
类的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成对象。
- 避免了
java
单继承的局限性
实现Callable接口
和Runnable
接口不一样,Callable
接口提供了一个call()
方法作为线程执行体,call()
方法比run()
方法功能要强大:
call()方法可以有返回值
call()方法可以声明抛出异常
Java5
提供了Future
接口来代表Callable
接口里call()
方法的返回值,并且为Future
接口提供了一个实现类FutureTask
,这个实现类既实现了Future
接口,还实现了Runnable
接口,因此可以作为Thread
类的任务。在Future
接口里定义了几个公共方法来控制它关联的Callable
任务。
boolean cancel(boolean mayInterruptIfRunning)
:视图取消该Future里面关联的Callable任务
V get()
:返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
V get(long timeout,TimeUnit unit)
:返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
boolean isDone()
:若Callable任务完成,返回True
boolean isCancelled()
:如果在Callable任务正常完成前被取消,返回True
介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:
- 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
- 使用
FutureTask
类来包装Callable对象,该FutureTask
对象封装了Callable对象的call()方法的返回值 - 使用
FutureTask
对象作为Thread对象的target创建并启动线程(因为FutureTask
实现了Runnable接口) - 调用
FutureTask
对象的get()方法来获得子线程执行结束后的返回值
代码实例
public class Demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 也可以传一个Callable接口的实现类
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i=0; i<5; i++) {
System.out.println("副线程执行计算任务" + i);
sum = sum + i;
}
return sum;
}
});
Thread thread = new Thread(task);
thread.start();
System.out.println("主线程结束......");
// 获取线程的返回结果
System.out.println("副线程计算任务执行结果" + task.get());
}
}
三种创建线程方法对比
实现Runnable
和实现Callable
接口的方式基本相同,不过是后者执行call()
方法有返回值,后者线程执行体run()
方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:
1、线程只是实现Runnable
或实现Callable
接口,还可以继承其他类。
2、这种方式下,多个线程可以共享一个target
对象,非常适合多线程处理同一份资源的情形。
3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()
方法。
4、继承Thread
类的线程类不能再继承其他父类(Java单继承决定)。
注:一般推荐采用实现接口的方式来创建多线程
线程的生命周期
thread-life-circle.png线程安全问题
产生原因:
- 多个线程在操作共享的数据
- 操作共享数据的线程代码有多条
当一个线程在操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题
解决线程安全问题的思路:
就是将多条操作共享数据的代码封装起来,当线程 在执行这些代码的时候,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算
在Java中,用同步代码块就可以解决这个问题
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处:
解决了线程安全问题
弊端:
相对降低了效率,因为同步外的线程都会判断同步锁,消耗资源
同步的前提:
同步中必须有多个线程并使用同一个锁
案例分析:
package xyz.guqing.thread;
/**
* 两个储户,每个都到银行存钱每次存100块,共存三次
*/
class Bank {
// 银行总账户余额
private int account;
// 存钱方法
public void add(int money) {
account = account + money;
System.out.println("account=" + account);
}
}
class Customer implements Runnable {
// 共享数据
private Bank bank = new Bank();
@Override
public void run() {
for(int i=0; i<3; i++) {
bank.add(100);
}
}
}
class CustomerTest {
public static void main(String[] args) {
Customer customer = new Customer();
// 两个线程表示两个储户
Thread t1 = new Thread(customer);
Thread t2 = new Thread(customer);
t1.start();
t2.start();
}
}
分析上面代码是否存在线程安全问题,从产生线程安全问题的原因分析:
- 首先需要判断是否存在共享数据
- 操作共享数据的线程代码是否有多条
对于上面的代码Bank
被线程任务执行
class Bank {
// 共享数据
private int account;
public void add(int money) {
// account共享数据被两条语句操作
account = account + money;
System.out.println("account=" + account);
}
}
所以会产生线程安全问题,解决办法:加锁
- 同步代码块
//锁需要一个对象
private Object object = new Object();
public void add(int money) {
synchronized(object) {
account = account + money;
System.out.println("account=" + account);
}
}
- 同步函数(同步函数用的锁时
this
对象)
public synchronized void add(int money) {
account = account + money;
System.out.println("account=" + account);
}
同步函数和同步代码块的区别:
- 同步函数的锁是固定的
this
,也就是当前的对象 - 同步代码块使用的锁是任意对象
建议使用同步代码块
静态同步函数的锁:
public static synchronized void add(int money) {
account = account + money;
System.out.println("account=" + account);
}
还是以上的实例,这样也是可以的,这说明静态同步函数的锁绝对不是this
,因为静态函数根本就没有this
,那锁是什么呢?
同步函数有所属的对象this
,静态之后就没有所属对象了。锁是有对象的,那么静态函数被加载进内存时有对象吗?,函数随着类的加载而被加载进内存但是该对象还没有通过new
创建对象,而java
的特点是字节码文件进内存先封装对象,所有的对象建立都有自己所属的字节码文件对象,通过getClass()
方法获取,,所以函数被加载进内存时有对象,这个对象就是当前class
文件所属的对象。而这个对象也就是同步静态函数所使用的锁
总结:静态的同步函数使用的锁是该函数所属的字节码文件对象,可以用getClass()
获取也可以用当前类名.class
形式表示
多线程下的单类
饿汉式:
/**
* 饿汉式单类
*/
public class Single {
private static final Single single = new Single();
private Single() {
}
public static Single getInstance() {
return single;
}
}
懒汉式(延迟加载):
//懒汉式,延迟加载
public class Single {
private static Single single = null;
private Single(){}
public static Single getInstance() {
if(single == null) {
single = new Single();
}
return single;
}
}
分析懒汉式单类在多线程下是否存在线程安全问题
// 共享数据
private static Single single = null;
//多条语句操作共享数据
// 1.线程0判断为空进入
// 3.线程1判断还是为空
if(single == null) {
// 2.线程0执行到此没有创建对象,cpu执行权被切换走了,等待
// 4.线程1执行到此,cpu执行权被切换走了,等待
// 5.等待的线程0获得执行权,创建对象
// 6.线程1获得执行权创建对象,造成了对象不唯一
single = new Single();
}
通过上述分析存在线程安全问题,加同步
- 同步函数,每次判断锁进入后都需判断
if
,但是不管存不存在对象都需要判断锁
public static synchronized Single getInstance() {
if(single == null) {
single = new Single();
}
return single;
}
- 同步代码块(静态函数同步代码块,没有
this
),但是和同步代码函数没什么区别
public static Single getInstance() {
// 不能用getClass()方法,因为该方法非静态
synchronized(Single.class) {
if(single == null) {
single = new Single();
}
}
return single;
}
改进:
public static Single getInstance() {
// 多加一个判断解决效率问题当single!=null时不在需要判断锁
if(single == null) {
// 解决同步问题
synchronized(Single.class) {
if(single == null) {
single = new Single();
}
}
}
return single;
}
死锁
常见情景之一:同步的嵌套,锁不一致
public class DeadLock
{
public static void main(String[] args)
{
byte[] lock1 = new byte[0];
byte[] lock2 = new byte[0];
Thread th1=new Thread(new Processer1(lock1,lock2));
Thread th2=new Thread(new Processer2(lock1,lock2));
th1.setName("th1");
th2.setName("th2");
th1.start();
th2.start();
}
}
class Processer1 implements Runnable
{
private byte[] lock1;
private byte[] lock2;
Processer1(byte[] lock1,byte[] lock2){
this.lock1=lock1;
this.lock2=lock2;
}
public void run(){
synchronized(lock1){
System.out.println(Thread.currentThread().getName()+" get lock1,and is waiting for lock2.");
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(lock2){
System.out.println(Thread.currentThread().getName()+" has get lock2.");
}
}
}
}
class Processer2 implements Runnable
{
private byte[] lock1;
private byte[] lock2;
Processer2(byte[] lock1,byte[] lock2){
this.lock1=lock1;
this.lock2=lock2;
}
public void run(){
synchronized(lock2){
System.out.println(Thread.currentThread().getName()+" get lock2,and is waiting for lock1.");
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(lock1){
System.out.println(Thread.currentThread().getName()+" has get lock1.");
}
}
}
}
线程间通信
多个线程在处理同一资源,但是任务却不同
package xyz.guqing.thread2;
//资源
class Resource {
String name;
String sex;
}
// 输入
class Input implements Runnable{
Resource resource;
public Input(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
int x = 0;
while(true) {
// 加同步
synchronized (resource) {
if(x == 0){
resource.name = "张三";
resource.sex = "男";
}else {
resource.name = "lucy lucy lucy";
resource.sex = "女";
}
}
x = (x+1)%2;
}
}
}
//输出
class Output implements Runnable{
Resource resource;
public Output(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while(true) {
// 输出也需要加同步
synchronized (resource) {
System.out.println(resource.name + "...." + resource.sex);
}
}
}
}
class ResourceDemo{
public static void main(String[] args) {
// 创建资源传入Input和Output确保两个类操作的是同一个资源
Resource resource = new Resource();
//对资源进行输入和输入的两个类
Input in = new Input(resource);
Output out = new Output(resource);
//开启线程任务
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
/**
* 出现线程安全问题:
* lucy lucy lucy....男
* 张三....女
* lucy lucy lucy....男
*
* 共享数据:
* resource
* 有多条语句操作共享数据:
* resource.name = "张三";
* resource.sex = "男";
*
* 加同步也需要注意:
* 锁该用什么需要保证两个线程锁是同一个
* 1. this不行是两个不同的类
* 2. 创建一个Object不行不唯一
* 可以两个锁都用Resource.class或者resource
*/
}
}
虽然改造完了加了锁,但是输出是一片一片的,我们希望赋值一个输出一个,交替出现。
在资源中加一个flag
标记,默认没有值,如果有值flag=true
输出,没有flag=true
存值,但是如果线程赋值完还持有执行权此时flag=false
,那么输出线程此时就需要等待,等着让输出线程输出后在赋值,这就是线程之前的等待唤醒机制
等待唤醒机制涉及的方法:
-
wait()
:让线程处于冻结状态,被wait
的线程会被存储到线程池中,失去cpu
的执行权 -
notify()
:唤醒线程池中的一个线程(任意),让线程具备执行资格 -
notifyAll()
:唤醒线程池中的所有线程
这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是那个锁上的线程,所以wait()
和notify()
在同步里面还需要用锁调用该方法标识出锁,例如resource.wait()
代表resource
这个锁在调用wait()
方法,线程进入到resource
锁后resource
锁中的线程被wait()
方法改变状态,也就是等待和唤醒必须要有所属,不能乱唤醒。
参考文档:
public final void wait() throws InterruptedException
导致当前线程等待,直到另一个线程调用该对象的
notify()
方法或notifyAll()
方法。换句话说,这个方法的行为就好像简单地执行呼叫wait(0)
。当前的线程必须拥有该对象的监视器。 该线程释放此监视器的所有权,并等待另一个线程通知等待该对象监视器的线程通过调用
notify
方法或notifyAll
方法notifyAll
。 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
该方法只能由作为该对象的监视器的所有者的线程调用。
为什么操作线程的方法wait notify notifyAll定义在Object类中:
因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方法一定定义在Object
类中
线程通信wait、notify例子:
//资源
class Resource {
String name;
String sex;
boolean flag = false;
}
// 输入
class Input implements Runnable{
Resource resource;
public Input(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
int x = 0;
while(true) {
// 加同步
synchronized (resource) {
if(resource.flag) {
//如果flag为true说明要赋值但是已经有了线程等待
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 赋完值后设置flag和notify
if(x == 0){
resource.name = "张三";
resource.sex = "男";
}else {
resource.name = "lucy lucy lucy";
resource.sex = "女";
}
// 赋值完置为true
resource.flag = true;
//唤醒线程,取值
resource.notify();
}
x = (x+1)%2;
}
}
}
//输出
class Output implements Runnable{
Resource resource;
public Output(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while(true) {
// 输出也需要加同步
synchronized (resource) {
if(!resource.flag) {
//如果flag为false说明要取值但是没有线程就需要等待
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 取完值,flag=true,notify唤醒线程
System.out.println(resource.name + "...." + resource.sex);
resource.flag = false;
resource.notify();
}
}
}
}
class ResourceDemo{
public static void main(String[] args) {
// 创建资源传入Input和Output确保两个类操作的是同一个资源
Resource resource = new Resource();
//对资源进行输入和输入的两个类
Input in = new Input(resource);
Output out = new Output(resource);
//开启线程任务
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
代码优化就是将资源中的属性设置为私有private
提供get、set
方法提供访问,如此才安全。
多生产者多消费者问题
class Resource {
private int count = 1;
private String name;
private boolean flag = false;
public synchronized void set(String name) {
if(flag) {
try {
// 如果烤鸭没被消费,等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"生产了一只烤鸭:"+this.name);
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag) {
try {
// 如果烤鸭没被消费,等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"..消费了..."+this.name);
flag = false;
this.notify();
}
}
class Producer implements Runnable {
private Resource resource;
public Producer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.set("烤鸭");
}
}
}
class Consumer implements Runnable {
private Resource resource;
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.out();
}
}
}
class ProducerConsumer {
public static void main(String[] args) {
Resource resource = new Resource();
Producer producer = new Producer(resource);
Consumer consumer = new Consumer(resource);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
/**
* 多生产者多消费者问题:
* Thread-1生产了一只烤鸭:烤鸭13807
* Thread-3..消费了...烤鸭13807
* Thread-2..消费了...烤鸭13807
* Thread-3..消费了...烤鸭13807
* Thread-2..消费了...烤鸭13807
* Thread-3..消费了...烤鸭13807
* Thread-2..消费了...烤鸭13807
* 经过分析是因为if标记判断时线程等待再次被唤醒后不在判断标记直接执行导致的
*/
}
}
修改判断为while:
while(!flag) {
try {
// 如果烤鸭没被消费,等待
this.wait();//线程再此等待被唤醒后会回到while判断
} catch (InterruptedException e) {
e.printStackTrace();
}
}
但是这么已修改运行立马发现线程死锁
发生了。while
标记判断导致运行过程中所有线程都被wait
,没有线程能继续执行,所以死锁了,理想的情况是唤醒生产者线程后下次应该唤醒消费者线程,但是线程唤醒是随机的,所以解决办法可以是使用notifyAll()
,把所有线程唤醒
class Resource {
private int count = 1;
private String name;
private boolean flag = false;
public synchronized void set(String name) {
while(flag) {
try {
// 如果烤鸭没被消费,等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"生产了一只烤鸭:"+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out() {
while(!flag) {
try {
// 如果烤鸭没被消费,等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"..消费了..."+this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable {
private Resource resource;
public Producer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.set("烤鸭");
}
}
}
class Consumer implements Runnable {
private Resource resource;
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.out();
}
}
}
class ProducerConsumer {
public static void main(String[] args) {
Resource resource = new Resource();
Producer producer = new Producer(resource);
Consumer consumer = new Consumer(resource);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
使用Lock解决多生产者多消费者问题
Lock
实现提供了比使用synchronized
方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
。
锁用于通过多个线程控制对共享资源的访问的工具,通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。但是,一些锁可能允许并发访问共享资源,如ReadWriteLock
的读锁。
使用synchronized
方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,他们必须以相反的顺序被释放,并且所有的锁都必须被释放在与他们相同的词汇范围内。
虽然synchronized
方法和语句的范围机制是的使用监视器锁更容易编程,并且有助于避免设计锁的许多常见变成错误,但是有时你需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用手动
或者链锁定
:你获取节点A
的锁定,然后获取节点B
,然后释放A
并获取C
,然后释放B
并获得D
等。所属的实施方式中lock
接口通过允许获得并在不同范围释放的锁,并允许获得并以任何顺序释放多个锁是的能够使用这样的技术。
随着这种增加的灵活性,额外的责任,没有块结构化锁会删除使用synchronized方法和语句发生的锁自动释放,在大多数情况下应该使用Lock
使用方式:
Lock lock = new ReentrantLock();
try {
lock.lock(); // 获取锁
// code......
} finally {//确保异常时释放锁
lock.unlock(); //释放锁
}
ReentrantLock
是接口Lock
的实现,是一个可重入互斥Lock
具有与使用synchronized
方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
jdk1.5以后将同步锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,隐式动作变成了显式动作。
Condition
public interface Condition
Condition
因素出Object
监视器方法( wait
, notify
和notifyAll
)成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock
个实现。Lock
替换synchronized
方法和语句的使用, Condition
取代了对象监视器方法的使用。
条件(也称为条件队列或条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait
。
一个Condition
实例本质上绑定到一个锁。 要获得特定Condition
实例的Condition实例,请使用其newCondition()
方法。
使用Condition
后替代方法
class Resource {
private int count = 1;
private String name;
private boolean flag = false;
// 使用lock锁
Lock lock = new ReentrantLock();
// 通过已有的锁获取该锁上的监视器对象
Condition condition = lock.newCondition();
public void set(String name) {
lock.lock();
try {
while (flag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "生产了一只烤鸭:" + this.name);
flag = true;
condition.signalAll();
} finally {
// 释放锁
lock.unlock();
}
}
public void out() {
lock.lock();
try {
while (!flag) {
try {
// 如果烤鸭没被消费,等待
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "..消费了..." + this.name);
flag = false;
condition.signalAll();
}finally {
lock.unlock();
}
}
}
这是基本用法和之前synchronized
没什么区别,但是Lock
一个锁可以有多组Condition
以后,就不用在全唤醒了。
其他代码全不变,只需要更改Resource
class Resource {
private int count = 1;
private String name;
private boolean flag = false;
// 使用lock锁
Lock lock = new ReentrantLock();
// 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
Condition producerCondition = lock.newCondition();
Condition consumerCondition = lock.newCondition();
public void set(String name) { // 生产者
lock.lock();
try {
while (flag) {
try {
// 已经有烤鸭了,生产者等待
producerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "生产了一只烤鸭:" + this.name);
flag = true;
// 生产完唤醒消费者
consumerCondition.signal();
} finally {
// 释放锁
lock.unlock();
}
}
public void out() { // 消费者
lock.lock();
try {
while (!flag) {
try {
// 消费者等待
consumerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "..消费了..." + this.name);
flag = false;
// 消费完毕,唤醒生产者生产
producerCondition.signal();
}finally {
lock.unlock();
}
}
}
总结:
Lock
接口:它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成显式锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock()
:获取锁的方法
unlock()
:释放锁的方法,通常需要定义在finally
代码块中,以确保即使发生异常也能释放锁
Condition
接口: 出现代替了Object
中的wait
,notify
,notifyAll
方法。这些监视器方法单独进行了封装,变成Condition
监视器对象。可以与任意锁进行组合,其中:
-
await()
:相当于synchronized
中的wait()
-
signal()
: 相当于synchronized
中的notify()
-
signalAll()
: 相当于synchronized
中的notifyAll()
Wati和Sleep的区别
-
wait
可以指定时间也可以不指定。sleep
必须指定时间 - 在同步中时,对于
cpu
的执行权和锁的处理不同,wait
释放执行权且释放锁,sleep
释放cput
执行权,不释放锁。
停止线程
-
stop
方法(已过时) -
run
方法结束,线程任务结束就会停止,
如何控制线程任务结束?
任务中都会有循环结构,只要控制住循环就可以结束任务,控制循环通常就用自定义标记来完成
但是如果线程处于了冻结状态,无法读取标记该如何结束?
可以使用public void interrupt()
方法将线程从冻结状态强制恢复到运行状态,让线程具备cpu的执行资格。
但是强制动作会发生InterruptException
,需要处理
public void interrupt() 中断这个线程。
除非当前线程中断自身,这是始终允许的,所以调用此线程的
checkAccess
方法,这可能会导致抛出SecurityException
。如果该线程阻塞的调用
wait()
,wait(long)
,或wait(long, int)
的方法Object
类,或者在join()
,join(long)
,join(long, int)
,sleep(long)
,或sleep(long, int)
,这个类的方法,那么它的中断状态将被清除,并且将收到一个InterruptedException
。如果该线程在可阻止在I / O操作
InterruptibleChannel
则信道将被关闭,该线程的中断状态将被设置,并且螺纹将收到一个ClosedByInterruptException
。如果该线程在
Selector
中被阻塞,则线程的中断状态将被设置,并且它将从选择操作立即返回,可能具有非零值,就像调用了选择器的wakeup
方法一样。如果以前的条件都不成立,则该线程的中断状态将被设置。
中断不存在的线程不需要任何效果。
线程类的其他方法
setPriority(int num)
public final int getPriority()
返回此线程的优先级
setDaemon(boolean b)
public final void setDaemon(boolean on)
将此线程标记为
daemon
线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。线程启动前必须调用此方法。
- 参数
on
- 如果true
,将此线程标记为守护线程
join()
public final void join() throws InterruptedException
等待这个线程死亡。
调用此方法的行为方式与调用完全相同
join (0)
toString()
public String toString()
返回此线程的字符串表示,包括线程的名称,优先级和线程组。
- 重写:
toString
在Object
- 结果
这个线程的字符串表示形式。
线程优先级常量:
static int MAX_PRIORITY
线程可以拥有的最大优先级。
static int MIN_PRIORITY
线程可以拥有的最小优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。