活动地址:CSDN21天学习挑战赛
一、什么是多线程
1.什么是进程
进程:在操作系统中运行的某个软件/某个程序(主要是指在内存中)。
任何软件/程序要运行都要被加载到内存中,而内存负责运行这个软件/程序所需要的那些内存空间,就被称为当前软件在内存中的一个进程。
进程需要依赖于操作系统
进程就是操作系统中动态运行的静态代码。
2.什么是线程
线程就是在操作系统中动态运行的静态代码中的某一项具体功能的执行过程【执行轨迹/执行线索】
例如:
我们在window操作系统上打开“暴风影音”播放电影,此时“暴风影音”就会在window操作系统中产生一个进程;打开“暴风影音”播放电影的时候有画面,声音,中文字幕等等,这些画面,声音,中文字幕就是这个“暴风影音”进程中的多个线程。
进程 | 线程 |
---|---|
依赖操作系统 | 依赖进程 |
进程与进程之间的数据交互很困难 | 线程与线程之间的数据交互很容易 |
3.什么是多线程
多线程:某一个程序在运行时可能会产生多个不同的执行线索【执行轨迹】,这些多个不同的执行线索【执行轨迹】共同运行的情况就是多线程。
往往我们会感觉到这些多个不同的执行线索【执行轨迹】同时执行,实际上这时一种错觉假象,实际上当这些多个不同的执行线索【执行轨迹】在运行的时候,某一个时刻只用一个执行线索【执行轨迹】在运行,只是这多个不同的执行线索【执行轨迹】快速的切换而已。
4.为什么使用多线程
(1)使用多线程的目的就是为了提高程序的执行效率。
(2)解决并发问题。
并行和并发有什么区别
并行:多个处理器或多核处理器同时处理多个任务。
并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
如下图:【并发=两个队列和一台咖啡机】【并行=两个队列和两台咖啡机】
二、多线程的创建方式以及区别
Java中的线程
当一个java程序启动运行以后,至少有2个线程在运行。
1.主线程,就是java程序的主方法执行线索
2.垃圾回收线程
2.1通过继承Thread类创建线程类
1.创建一个类,继承Thread类
2.重写run方法
3.将需要由线程执行的具体动作写入run方法
例如:
package com.wangxing.test1;
/**
* 1.创建一个类,继承Thread类
2.重写run方法
3.将需要由线程执行的具体动作写入run方法
* @author Administrator
*
*/
public class TestThread extends Thread{
@Override
public void run() {
//得到当前线程的名称
String name=Thread.currentThread().getName();
for(int i=1;i<=10;i++) {
System.out.println(name+"--i=="+i);
}
}
}
package com.wangxing.test1;
public class TestMain {
public static void main(String[] args) {
/**
* 启动线程的步骤
* 1.创建线程对象【继承Thread类的对象】
* 2.通过线程对象调用start方法启动线程
*/
TestThread tth1=new TestThread();
tth1.start();
TestThread tth2=new TestThread();
tth2.start();
}
}
运行结果:
2.2通过实现Runnable接口创建线程类
1. 创建一个类,实现Runnable接口
2. 重写run方法
3. 将需要由线程执行的具体动作写入run方法
例如:
package com.wangxing.test1;
/**
* 1.创建一个类,实现Runnable接口
2.重写run方法
3.将需要由线程执行的具体动作写入run方法
* @author Administrator
*
*/
public class MyThread implements Runnable{
@Override
public void run() {
//得到当前线程的名称
String name=Thread.currentThread().getName();
for(int i=1;i<=10;i++) {
System.out.println(name+"--i=="+i);
}
}
}
package com.wangxing.test1;
public class TestMain {
public static void main(String[] args) {
/*
* 1.创建有线程类执行的目标对象【实现Runnable接口的java类对象】
* 2.创建线程对象【java.lang.Thread类的对象】
* public Thread(Runnable target)
* public Thread(Runnable target, String name)
* 3.调用start方法启动线程运行
*/
MyThread mth=new MyThread();
Thread th1=new Thread(mth);
th1.start();
Thread th2=new Thread(mth);
th2.start();
}
}
运行结果:
2.3第三种,通过Callable和Future接口创建线程
通过这两个接口创建线程,就需要了解这两个接口并知道其作用:
通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程的执行体,那么,是否可以把任意方法都包装成线程的执行体呢?
从Java5开始,Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法的功能抢答题现在:
(1)call()方法可以有返回值
(2)call()方法可以声明抛出异常
从这里可以看出,完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是call()方法。
问题是:Callable接口是Java新增的接口,而且他不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。还有一个原因:call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的,所以这里涉及获取call()方法返回值的问题。
于是,Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。
在Future接口里定义了如下几个公共方法来控制与它关联的Callable任务:
1、boolean cancel(boolean mayInterruptIfRunning):试图取消Future里关联的Callable任务;
2、V get():返回Callable任务里call()方法的返回值,调用该方法将导致程序阻塞,必须等到子线程结束以后才会得到返回值;
3、V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后,Callable任务依然没有返回值,将会抛出TimeoutException异常;
4、boolean isCancelled():如果Callable任务正常完成前被取消,则返回true;
5、boolean isDone():如果Callable任务已经完成, 则返回true;
这种方式创建并启动多线程的步骤如下:
1、创建Callable接口实现类,并实现call()方法,该方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例;
2、使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
3、使用FutureTask对象作为Thread对象的target创建并启动新线程;
4、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
例如:
package com.wangxing.test1;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int i=1;
//得到当前线程的名称
String name=Thread.currentThread().getName();
for(;i<=10;i++) {
System.out.println(name+"--i=="+i);
}
return i;
}
}
package com.wangxing.test1;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestMain {
public static void main(String[] args) {
//目标对象
MyCallable mycall=new MyCallable();
//目标对象的封装
FutureTask<Integer> task1=new FutureTask(mycall);
//线程对象
Thread th1=new Thread(task1);
//启动线程
th1.start();
try {
//是否取消正在执行的线程任务
task1.cancel(true);
//判断是否是线程任务没有运行结束之前取消线程认为
if(task1.isCancelled()) {
System.out.println("关闭窗口!");
}
//判断线程任务是否正常执行完毕
if(task1.isDone()) {
//得到线程任务的执行结果
int task_Thread_1=task1.get();
System.out.println("线程1的运行结果--"+task_Thread_1);
}else {
}
} catch (Exception e) {
e.printStackTrace();
}
FutureTask<Integer> task2=new FutureTask(mycall);
Thread th2=new Thread(task2);
th2.start();
try {
int task_Thread_2=task2.get();
System.out.println("线程2的运行结果--"+task_Thread_2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
4.区别
Java中多线程的创建方式
第一种,通过继承Thread类创建线程类
第二种,通过实现Runnable接口创建线程类
第三种,通过Callable和Future接口创建线程
【第4种,通过线程池技术创建线程】
上面3种多线程的创建方式区别
继承Thread类 | 实现Runnable接口 | Callable接口 |
---|---|---|
extends Thread | implements Runnable | implements Callable<???> |
重写run方法 | 重写run方法 | 重写call方法 |
没有返回值 | 没有返回值 | 有返回值 |
不能声明抛出异常 | 不能声明抛出异常 | 能声明抛出异常 |
直接创建线程对象继承Thread类的子类对象 | 先创建目标对象【实现Runnable接口子类对象】通过Thread类的构造方法创建线程对象 | 线程创建Callable接口的子类对象,后创建FutrueTask类对象通过Thread类的构造方法创建线程对象 |
不能资源共享 | 能资源共享 | 能资源共享 |
通常情况我们都是使用实现Runnable接口方式创建线程。
三、线程中的常用操作方法
3.1 Java.lang.Thread类【线程类】三
void | start() 启动线程 |
---|---|
static Thread | currentThread()得到当前正在运行的线程对象 |
String | getName()返回该线程的名称 |
void | setName(String name)设置线程名称1. 当没有设置线程名称的时候,系统会赋予线程一个默认的名称“Thread-0,Thread-1…”2. 主线程【主方法的执行线程】的名称默认是“main” |
int | getPriority() 返回线程的优先级 |
void | setPriority(int newPriority) 更改线程的优先级 |
3.2 线程的优先级--就是线程的执行先后
1. 线程的优先级有10个级别,分别使用整数1~10来表示。
为了方便操作,java将10个级别有规定成3个级别,分别是最低的优先级,中等优先级,最高的优先级,并且将这3个级别封装成了静态常量
static int | MAX_PRIORITY 线程可以具有的最高优先级。10 |
---|---|
static int | MIN_PRIORITY线程可以具有的最低优先级。1 |
static int | NORM_PRIORITY分配给线程的默认优先级。5 |
2. 设置线程的优先级的时候,数字越大优先级越高,数字越小优先级越高。优先级越高并代表就一定会优先执行,只是被优先执行的几率增大,因此不要试图通过控制线程的优先级,来保证某一个线程,总是第一个执行。
3. 所有线程默认的优先级都是5【中等级别】
例如:
package com.wangxing.test1;
public class MyTestThread implements Runnable{
@Override
public void run() {
//static Thread currentThread()得到当前正在运行的线程对象
Thread threadObj=Thread.currentThread();
//String getName()返回该线程的名称。
String threadName=threadObj.getName();
for(int i=1;i<=10;i++) {
System.out.println(threadName+"--i=="+i);
}
}
}
package com.wangxing.test1;
public class TestMain {
public static void main(String[] args) {
/*
//得到主线程的线程名称
Thread mainThread=Thread.currentThread();
String mainName=mainThread.getName();
// int getPriority() 返回线程的优先级。
int mainpro=mainThread.getPriority();
System.out.println("主线程的名称=="+mainName);
System.out.println("主线程的优先级=="+mainpro);
*/
//创建目标对象
MyTestThread mtth=new MyTestThread();
//创建线程对象
Thread th1=new Thread(mtth);
//void setName(String name)设置线程名称
th1.setName("线程A");
//void setPriority(int newPriority) 更改线程的优先级。
th1.setPriority(1);
// int getPriority() 返回线程的优先级。
int th_a_pro=th1.getPriority();
System.out.println("线程A的优先级=="+th_a_pro);
// void start() 启动线程
th1.start();
//创建线程对象
Thread th2=new Thread(mtth);
//void setName(String name)设置线程名称
th2.setName("线程B");
//void setPriority(int newPriority) 更改线程的优先级。
th2.setPriority(Thread.MAX_PRIORITY);
// int getPriority() 返回线程的优先级。
int th_b_pro=th2.getPriority();
System.out.println("线程B的优先级=="+th_b_pro);
// void start() 启动线程
th2.start();
}
}
运行结果:
3.3 守护线程的相关操作方法
通常情况之下我们所创建的线程都是普通线程,非守护线程,也叫用户线程。
守护线程,也叫精灵线程【当所有用户线程都执行完毕以后,自动结束运行的线程就是守护线程】
特征:当所有用户线程都执行完毕以后,无论守护线程能否可以继续运行,都要立刻停止运行。
例如:不是同年同月同日生,但是一定会同年同月同日死【共死/陪葬】
boolean | isDaemon() 测试该线程是否为守护线程 |
---|---|
void | setDaemon(boolean on) 将该线程标记为守护线程用户线程 |
例如:
package com.wangxing.test2;
public class TestThread implements Runnable{
@Override
public void run() {
//static Thread currentThread()得到当前正在运行的线程对象
Thread threadObj=Thread.currentThread();
//String getName()返回该线程的名称。
String threadName=threadObj.getName();
for(int i=1;i<=5;i++) {
try {
Thread.sleep(400);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(threadName+"--i=="+i);
}
}
}
package com.wangxing.test2;
public class DoxThread implements Runnable{
@Override
public void run() {
//static Thread currentThread()得到当前正在运行的线程对象
Thread threadObj=Thread.currentThread();
//String getName()返回该线程的名称。
String threadName=threadObj.getName();
while(true) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName+"是一个守护线程");
}
}
}
package com.wangxing.test2;
public class TestMain {
public static void main(String[] args) {
//创建目标对象
TestThread tth=new TestThread();
DoxThread dth=new DoxThread();
//创建线程对象
Thread th1=new Thread(tth);
Thread th2=new Thread(tth);
Thread th3=new Thread(dth);
//void setDaemon(boolean on) 将该线程标记为守护线程用户线程。
th3.setDaemon(true);
//boolean isDaemon() 测试该线程是否为守护线程。
System.out.println("th1是否是守护线程?"+th1.isDaemon());
System.out.println("th2是否是守护线程?"+th2.isDaemon());
System.out.println("th3是否是守护线程?"+th3.isDaemon());
//设置线程的名称
th1.setName("线程A");
th2.setName("线程B");
th3.setName("C线程");
//启动线程
th1.start();
th2.start();
th3.start();
}
}
运行结果:
3.4 线程休眠
static void | sleep(long millis) 设置线程休眠【暂停】指定的时间【毫秒】 |
---|---|
void | interrupt() 中断线程休眠【暂停】 |
package com.wangxing.test3;
public class MyTestThread implements Runnable{
@Override
public void run() {
String name=Thread.currentThread().getName();
System.out.println(name+"开始听讲");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"进入梦乡。。。。。。");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(name+"被老师叫醒");
System.out.println(name+"又开始听讲");
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("下课啦!");
}
}
}
package com.wangxing.test3;
public class TestMain {
public static void main(String[] args) {
System.out.println("上课铃响了");
Thread th=new Thread(new MyTestThread());
th.setName("小明");
th.start();
System.out.println("老师开始上课了");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老师发现同学睡着啦");
System.out.println("老师走过去叫醒了他");
th.interrupt();
}
}
运行结果:
package com.wangxing.test3;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class NaoZhong {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
System.out.println("请设置一个闹钟时间:");
String setTime=input.nextLine();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
boolean flag=true;
while(flag) {
String newtime=sdf.format(new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(newtime);
if(newtime.equals(setTime)) {
System.out.println("闹钟响铃!!!!!!");
flag=false;
}
}
}
}
运行结果:
3.5 强制线程执行
void | join(long millis)等待该线程终止的时间最长为 millis 毫秒。【强制线程执行】 |
---|
例如:
package com.wangxing.test4;
public class MyTestThread implements Runnable{
@Override
public void run() {
String name=Thread.currentThread().getName();
for(int i=1;i<=10;i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+"--i=="+i);
}
}
}
package com.wangxing.test4;
public class TestMain {
public static void main(String[] args) {
Thread th=new Thread(new MyTestThread());
th.setName("Test线程");
th.start();
String mainname=Thread.currentThread().getName();
for(int j=1;j<=10;j++) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(mainname+"--j=="+j);
if(j==5) {
try {
th.join(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
运行结果:
四、线程的生命周期
1、线程的生命周期就是线程从一开始创建,到run方法执行完毕以后的状态变化。[状态之间的切换]
2、线程的生命周期几种状态【1、新建状态 2、就绪状态 3、运行状态 4.阻塞状态 5.死亡状态】
创建状态:通过new的方式创建出线程对象,此时线程就进入到创建状态【新建状态】。
新建状态的线程是不能运行。
就绪状态:新建状态的线程调用strat方法之后就会进入就绪状态。
就绪状态的线程具备执行能力,但是没有cpu资源。【万事具备只差cpu】.
运行状态:就绪状态的线程得到cpu资源开始执行run方法,此时这个线程就是运行状态。
运行状态的线程当cpu切换到其他线程时候,本线程就再一次进入就绪状态。
阻塞状态:运行状态的线程调用sleep/wait方法…此时线程进入阻塞状态。
处于阻塞状态的线程的休眠时间到/调用notify方法/notifyAll方法在此时线程进入就绪状态,
从就绪状态中得到cpu资源从而进入运行状态。
死亡状态:运行状态的线程run方法运行结束/线程执行过程中遇到异常/调用stop方法此时 线程就进入到死亡状态。
死亡状态的线程是不能运行,除非再一次使用strat方法重新启动运行。
五、通过继承Thread类所创建的线程不能实现资源共享功能,
例如:
public class MyThread extends Thread{
//定义车票【共享资源】
private int piao=5;
@Override
public void run() {
while(piao>0) {
//我们通过线程的暂停来模拟
//收钱-->打票-->找钱
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"卖出1张票,还剩"+(--piao)+"张");
}
}
}
package com.wangxing.test1;
public class TestMain1 {
public static void main(String[] args) {
MyThread th1=new MyThread();
MyThread th2=new MyThread();
MyThread th3=new MyThread();
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
运行结果:
六、通过实现Runnable接口所创建的线程可以实现资源共享功能
package com.wangxing.test2;
public class MyThread implements Runnable{
//定义车票【共享资源】
private int piao=5;
@Override
public void run() {
while(piao>0) {
//我们通过线程的暂停来模拟
//收钱-->打票-->找钱
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"--卖出1张票,还剩"+(--piao)+"张");
}
}
}
package com.wangxing.test2;
public class TestMain2 {
public static void main(String[] args) {
//创建目标对象
MyThread mth=new MyThread();
//创建线程对象
Thread th1=new Thread(mth);
Thread th2=new Thread(mth);
Thread th3=new Thread(mth);
//设置线程名称
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
//开启线程
th1.start();
th2.start();
th3.start();
}
}
运行结果:
通过上面实现Runnab接口的买票程序可以实现资源共享,但是卖出票会出现负数情况。
分析:当窗口3开始卖最后一张票的时候,窗口3判断还有一张票,这时窗口3开始收钱打票,当窗口3开始收钱打票的时候,线程就切换给了窗口1,由于窗口3还有来得及对票数减1,因此窗口1判断还有一张票,这时窗口1开始收钱打票,当窗口1开始收钱打票的时候,线程就切换给了窗口2,由于窗口1还有来得及对票数减1,因此窗口2判断还有一张票,这时窗口2开始收钱打票,线程切换给了窗口3,所以窗口3输出“窗口3卖出1张票,还剩0张”,输出结束以后线程就切换给窗口1,由于窗口3已经对票数减1,所以窗口1输出剩余票数的时候在窗口3减1以后的基础上再一次减1,就得到剩余-1张票,所以窗口1输出“窗口1卖出1张票,还剩-1张”,输出结束以后线程就切换给窗口2,由于窗口1已经对票数减1,所以窗口2输出剩余票数的时候在窗口1减1以后的基础上再一次减1,就得到剩余-2张票,所以窗口2输出“窗口2卖出1张票,还剩-2张”
经过上面运行程序的分析,我得到的结果是:
当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。
为了解决这种数据不一致的错误情况,我们才学习线程同步。
6.1、为什么需要线程同步/线程安全
因为当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。为了解决这种数据不一致的错误情况,我们才学习线程同步。
6.2、什么是线程同步/线程安全
线程同步:当多条线程同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步也叫线程安全。
6.3、线程同步/线程安全的实现方式有几种,分别是什么,有什么区别
6.3.1 同步代码块
格式:
synchronized(同步对象){
}
例如:
package com.wangxing.test3;
public class MyThread implements Runnable{
//定义车票【共享资源】
private int piao=5;
@Override
public void run() {
//同步代码块
synchronized (this) {
while(piao>0) {
//我们通过线程的暂停来模拟
//收钱-->打票-->找钱
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"卖出1张票,还剩"+(--piao)+"张");
}
}
}
}
package com.wangxing.test3;
public class TestMain {
public static void main(String[] args) {
MyThread mth=new MyThread();
Thread th1=new Thread(mth);
Thread th2=new Thread(mth);
Thread th3=new Thread(mth);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
运行结果:
同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。
6.3.2 同步方法
同步方法也是方法,所以它一定是符合方法的定义格式的。
方法的定义格式:
访问限制修饰符 方法返回值类型 方法名称(){}
同步方法的定义格式:
访问限制修饰符 synchronized 方法返回值类型 方法名称(){}
例如:
package com.wangxing.test4;
public class MyThread implements Runnable{
//定义车票【共享资源】
private int piao=5;
private boolean flag=true;
@Override
public void run() {
while(flag) {
sellPiao();
}
}
/**
* 买票的同步方法
*/
public synchronized void sellPiao() {
if(piao>0) {
//我们通过线程的暂停来模拟
//收钱-->打票-->找钱
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"--卖出1张票,还剩"+(--piao)+"张");
}else {
flag=false;
}
}
}
package com.wangxing.test4;
public class TestMain {
public static void main(String[] args) {
MyThread mth=new MyThread();
Thread th1=new Thread(mth);
Thread th2=new Thread(mth);
Thread th3=new Thread(mth);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
运行结果:
6.3.3 通过Lock接口
public interface Lock
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
常用的接口方法
void | lock() 获得锁 |
---|---|
void | unlock() 释放锁 |
由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类来创建对象。
Lock接口得子类ReentrantLock
ReentrantLock() 创建一个 ReentrantLock的实例
例如:
package com.wangxing.test5;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable{
//定义车票【共享资源】
private int piao=5;
//创建Lock对象
private Lock lock=new ReentrantLock();
@Override
public void run() {
lock.lock();
while(piao>0) {
//我们通过线程的暂停来模拟
//收钱-->打票-->找钱
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"--卖出1张票,还剩"+(--piao)+"张");
}
lock.unlock();
}
}
package com.wangxing.test5;
public class TestMain {
public static void main(String[] args) {
MyThread mth=new MyThread();
Thread th1=new Thread(mth);
Thread th2=new Thread(mth);
Thread th3=new Thread(mth);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
运行结果:
区别
synchronized | Lock |
---|---|
关键字 | 接口 |
自动锁定资源 | 手动锁定资源 |
不灵活 | 灵活 |
异常时会自动释放锁 | 不会自动释放锁,所以需要在finally中实现释放锁 |
不能中断锁,必须等待线程执行完成释放锁 | 可以中断锁 |
总结