一、线程简介
-
程序:指令和数据的有序集合(静态概念)
-
进程:执行程序的一次执行过程(动态概念)
-
线程:CPU调度和执行的单位,一个进程中有多个线程。程序运行的时候,即使自己没有创建线程,后台也会多个线程,如main()主线程和gc线程。
-
操作系系中运行的程序就是进程,一个进程可以有多个线程。
-
普通方法调用:只有主线程一条执行路径。
-
多线程:多条执行路径,主线程和子线程并行交替执行。
-
对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。
二、线程创建
1.继承Thread类
步骤:
- 自定义线程类继承Thread类。
- 重写run()方法,编写线程执行体。
- 创建线程对象,调用start()方法启动线程。
- 线程开启不一定立即执行,由cpu调度执行。
//创建多线程方式1:继承Threa类,重写run()方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,由cpu调度执行
public class TestThread1 extends Thread {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 100; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start()方法开启线程
testThread1.start();//调用start方法,而不是run方法
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程"+i);
}
}
}
小结:
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用(避免OOP单继承局限性)
2.实现Runnable接口
步骤:
- 定义MyRunnable类实现Runnable接口。
- 实现run()方法,编写线程执行体。
- 创建线程对象,调用start()方法启动线程。
//创建多线程方式2:实现runnable接口,重写run()方法,执行线程需要丢入runnable接口的实现类,调用start方法
public class TestThread3 implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 100; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个runnable接口的实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
Thread thread = new Thread(testThread3);
thread.start();
//上面三行代码等于下面一行
new Thread(testThread3).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程"+i);
}
}
}
小结:
- 实现接口Runnable具备多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用(避免单继承局限性,灵活方便,方便同一个对象被多个线程使用)
模拟龟兔赛跑(Runnable)
//模拟龟兔赛跑
public class Race implements Runnable {
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子")&& i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
//如果比赛结束就停止程序
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
//判断是否结束比赛
private boolean gameOver(int steps){
//判断是否有胜利者
if(winner!=null){//已经存在胜利者
return true;
}else{
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
下载图片(Runnable)
//练习Runnable,实现多线程同步下载照片
public class TestThread2 implements Runnable{
private String url;//网络图片地址
private String name;//保存的文件名
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("http://bpic.588ku.com/element_origin_min_pic/16/10/29/2ac8e99273bc079e40a8dc079ca11b1f.jpg","中秋1.jpg");
TestThread2 t2 = new TestThread2("http://bpic.588ku.com/element_origin_min_pic/16/07/06/17577cd55945262.jpg%21r650","中秋2.jpg");
TestThread2 t3 = new TestThread2("http://bpic.588ku.com/element_origin_min_pic/17/06/13/5c5a1442f0ec72e59829ee10d891f224.jpg%21r650","中秋3.jpg");
new Thread(t1).start();//正常下载顺序t1 t2 t3,由于多线程的缘故,同时下载
new Thread(t2).start();
new Thread(t3).start();
}
}
//下载器
class WebDownloader{
//下载方法 导入commonio2.6
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
System.out.println("IO异常,downloader方法出现问题!");
}
}
}
3.初识并发问题
- 多个线程同时操作同一个资源的情况下,线程不安全,数据紊乱(并发问题)
//多个线程同时操作一个对象
//买火车票的例子
//发现问题:多个线程同时操作同一个资源的情况下,线程不安全,数据紊乱(并发问题)
public class TestThread4 implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛党").start();
}
}
小明拿到了第10票
老师拿到了第10票
老师拿到了第9票
结果会出现这样的问题
4.实现Callable接口(了解)
步骤:
- 实现Callable接口,需要返回值类型 。
- 重写call方法,需要抛出异常。
- 创建目标对象。
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
//创建线程方式3:实现callable接口
/*
callable的好处
1.可以定义返回值
2.可以抛出异常
*/
public class TestCallable implements Callable {
private String url;//网络图片地址
private String name;//保存的文件名
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("http://bpic.588ku.com/element_origin_min_pic/16/10/29/2ac8e99273bc079e40a8dc079ca11b1f.jpg","中秋11.jpg");
TestCallable t2 = new TestCallable("http://bpic.588ku.com/element_origin_min_pic/16/07/06/17577cd55945262.jpg%21r650","中秋22.jpg");
TestCallable t3 = new TestCallable("http://bpic.588ku.com/element_origin_min_pic/17/06/13/5c5a1442f0ec72e59829ee10d891f224.jpg%21r650","中秋33.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
//关闭服务
ser.shutdownNow();
}
}
//下载器
class WebDownloader{
//下载方法 导入commonio2.6
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
System.out.println("IO异常,downloader方法出现问题!");
}
}
}
5.静态代理
静态代理总结
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事情
public class StaticProxy {
public static void main(String[] args) {
You you = new You();//你要结婚
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.happyMarry();
//上三行等同于 new WeddingCompany(new You()).happyMarry();
}
}
interface Marry{
void happyMarry();
}
//真实角色,你去结婚
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("玛卡巴卡结婚了。");
}
}
//代理角色,帮助你去结婚
class WeddingCompany implements Marry{
//代理谁-->真是目标角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
befor();
this.target.happyMarry();//这就是真实对象
after();
}
void befor(){
System.out.println("结婚之前,布置现场");
}
void after(){
System.out.println("结婚之后,收尾款");
}
}
结婚之前,布置现场
玛卡巴卡结婚了。
结婚之后,收尾款
6.Lambda表达式
new Thread(()->System.out.println(“Lambda表达式”)).start();
为什么使用Lambda表达式?
- 避免匿名内部类定义过多。
- 使代码简洁明了。
- 去掉一堆没有意义的代码,只留下核心的逻辑。
/*
推导lambda表达式
*/
public class TestLambda1 {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();
//5.匿名内部类,没有类的接口,必须借助接口或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
//6.用lambda简化
like = ()-> {
System.out.println("i like lambda5");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike {
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
Lambda简化
- lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,必须用代码块包裹。
- 前提是接口为函数式接口(只包含唯一一个抽象方法的接口是函数式接口)
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号(a,b)
public class TestLambda2 {
public static void main(String[] args) {
ILove love = null;
//1.lambda表示简化
love = (int a) -> {
System.out.println("i love you-->" + a);
};
//简化1:去掉参数类型
love = (a) -> {
System.out.println("i love you-->" + a);
};
//简化2:去掉括号
love = a -> {
System.out.println("i love you-->" + a);
};
//简化2:去掉花括号
love = a -> System.out.println("i love you-->" + a);
love.love(521);
}
}
//定义一个函数式接口
interface ILove{
void love(int a);
}
三、线程状态
- 创建状态–就绪状态–运行状态–死亡状态 阻塞状态
1.线程停止
- 建议线程正常停止–>利用次数,不建议死循环
- 建议使用标志位–>设立一个标志位
- 不要使用stop或者destroy等过时或者JDK不推荐使用的方法
public class TestStop implements Runnable {
//1.设置一个标识符
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run......Thread"+i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位。
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("maian"+i);
if (i==900){
//调用stop方法切换标志位,让线程停止。
testStop.stop();
System.out.println("该线程停止了");
}
}
}
}
2.线程方法
- 线程休眠 sleep
- sleep存在异常InterruptedException。
- sleep指当前线程阻塞的毫秒数,时间到达后线程进入就绪状态。
- 每一个对象都会有一个锁,sleep不会释放锁。
- sleep可以模拟网络延时和倒计时。
//模拟网络延时:放大问题的发生性。
public class TestSleep implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestSleep ticket = new TestSleep();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛党").start();
}
}
//模拟倒计时 和 打印当前时间
public class TestSleep2 {
public static void main(String[] args) {
//打印当前时间
Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间
while(true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());//更新当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//tendown();
}
//模拟倒计时
public static void tendown(){
int num = 10;
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
- 线程礼让 yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞。
- 线程从运行状态转换为就绪状态
- 让CPU重新调度,礼让不一定成功!看CPU心情。
//礼让不一定成功,看CPU心情
public class TestYield {
public static void main(String[] args) {
MyYiled myYiled = new MyYiled();
new Thread(myYiled,"a").start();
new Thread(myYiled,"b").start();
}
}
class MyYiled implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程终止执行");
}
}
- 线程强制执行 join
- Join合并线程,待此线程执行完毕后,再执行其他线程,其他线程阻塞。
//线程强制执行 Join (插队执行)
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程VIP来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动我们的线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if (i==200){
thread.join();//插队
}
System.out.println("main"+i);
}
}
}
3.线程状态观测
- Thread.State
- NEW,尚未启动的线程所处的状态。
- RUNNABLE,在Java虚拟机中执行的线程所处的状态。
- BLOCKED,被阻塞等监视器锁定的线程所处的状态。
- WAITING,等待另一个线程执行特定动作所处的状态。
- TERMINATED,线程的结束状态。
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{ //lambda表达式
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("//");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);//NEW
//观察启动后
thread.start();//启动后
state = thread.getState();
System.out.println(state);//RUN
while (state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState();//更新线程状态
System.out.println(state);//输出状态
}
thread.start();//报错,线程进入死亡状态就不能再次启动
}
}
4.线程优先级 priority
-
线程优先级用数字表示,范围1~10。
Thread.MIN_PRIORITY = 1;最小是1。
Thread.MAX_PRIORITY = 10;最大是10。
Thread.NORM_PRIORITY = 5;默认是5。
-
getPriority()获取优先级,setPriority(int xxx)设置优先级。
-
优先级指的是获取调度的概率,并不是高优先级就一定在第一个执行,看CPU调度。
public class TestPriority {
public static void main(String[] args) {
//主程序默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
Thread t4 = new Thread(myThread);
Thread t5 = new Thread(myThread);
Thread t6 = new Thread(myThread);
//先设置优先级,再启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(5);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
t4.start();
/* t5.setPriority(-1);
t5.start();
t6.setPriority(11);
t6.start();*/ 报错
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
5.守护线程 daemon
- 线程分为用户线程和守护线程。
- 虚拟机不用等待守护线程执行完毕。
- 虚拟机必须确保用户线程执行完毕。
- setDaemon(true) 切换为守护线程。
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认是false表示用户线程,正常的线程都是用户线程。
thread.start();//上帝守护线程启动,虚拟机不用等待守护线程执行完毕。
new Thread(you).start();//你,用户线程启动,虚拟机必须确保用户线程执行完毕。
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你!");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("你幸福的活着"+i);
}
System.out.println("=======GoodBye,World!=======");
}
}
四、线程同步
- 并发:同一个对象被多个线程同时操作。
- 线程同步:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。
- 线程同步形成条件:队列+锁
1.三大不安全案例
- 不安全的买票(解决办法:同步方法)
//不安全的买票
//同步方法,默认锁的是this
public class UnsafeBuyTicket {
public static void main(String[] args) {
Buyticket buyticket = new Buyticket();
new Thread(buyticket,"我").start();
new Thread(buyticket,"你").start();
new Thread(buyticket,"黄牛党").start();
}
}
class Buyticket implements Runnable{
private int ticketNum = 10;//票
private boolean flag = true;//外部停止方式
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//sychronized 同步方法,锁定的是this
private synchronized void buy() throws InterruptedException {//解决办法:同步方法
//判断是否有票
if(ticketNum<=0){
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum--+"张票");
}
}
- 不安全银行取钱(解决办法:同步块)
//不安全银行取钱
//俩个人去银行取钱
//同步块,锁的对象是变化的量,需要增删改的对象。
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlfriend = new Drawing(account, 100, "她");
you.start();
girlfriend.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在手里多少钱
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
//synchronized 默认锁的是this
@Override
public void run() {
//锁的对象是变化的量,需要增删改的对象。
synchronized (account) {//解决办法:同步块
//判断有没有钱
if(account.money<drawingMoney){
System.out.println(Thread.currentThread().getName()+"取不了钱,账户余额不足");
return;
}
//模拟延时 放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 账户的钱 - 取了的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为"+account.money);
//this.getName() = Thread.currentThread().getName()
System.out.println(this.getName()+"手里的钱为"+nowMoney);
}
}
}
- 不安全集合(解决办法:同步块)
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {//解决办法:同步块
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
2.同步方法和同步块(synchronized)
锁机制synchronized:隐式锁。在多线程竞争下,加锁和释锁会导致比较多的上下文切换和调度延时,造成性能问题。一个优先级高的线程等待一个优先级低的线程释放锁,会到导致优先级倒置,也会造成性能问题。
-
同步方法
- synchronized方法 :public synchronized void method(int a){}
- synchronized 默认锁的是this,控制对“对象”的访问,每个对象都有一把锁。
- 若将一个大的方法申明为synchronized 将会影响效率。
-
同步块
- 同步块:synchronized (Obj ) { }
- Obj是同步监视器,可以是任何对象,推荐使用共享资源作为Obj。一个线程访问的时候锁定Obj,执行其中的代码,然后解锁Obj,接着第二个线程锁定、执行、解锁,然后第三个······
3.死锁
-
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源 ,都停止执行的情形 。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
-
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。
只要攻破上述四个条件中的一个或者多个条件,就可以避免死锁的发生。
//死锁,多个线程互相抱着对方需要的资源,然后形成僵持。
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{}
//镜子
class Mirror{}
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有一份。
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
public Makeup(int choice,String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆,互相持有对方的锁,就是需要拿到对方的资源。
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得了口红的锁");
Thread.sleep(1000);
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName+"获得了镜子的锁");
}
}
/*synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName+"获得了镜子的锁");
}解决办法:把获得镜子的锁的同步块移出来*/
}else{
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName+"获得了镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得了口红的锁");
}
}
/*synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得了口红的锁");
}解决办法:把获得口红的锁的同步块移出来*/
}
}
}
4.Lock(锁)
- Lock:显示锁(手动开启关闭锁),只有代码块锁。使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。
//lock锁
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();//加锁
try {
if (ticketNums > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
} finally {
lock.unlock();//解锁,如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
}
五、线程协作
1.线程通信(生产者消费者模式)
- 这是一个线程同步问题(不同线程之间的消息传递),生产者和消费者共享同一个资源,二者互相依赖,互为条件。
- 对于生产者,没有产品, 生产产品时,通知消费者等待,生产了产品,通知消费者消费。
- 对与消费者,没消费产品之后,通知生产者生产。
- wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁。
- wait(long timeout):指定等待的毫秒数。
- notify():唤醒一个处于等待状态的线程。
- notifyAll():唤醒同一个对象上所有调用wait()方法的线程。
解决方式一:管程法
- 通过数据缓冲区处理,生产者将产品放入缓冲区,消费者从缓冲区取出产品。
//测试:生产者消费者问题-->利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPc {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread {
SynContainer container;//容器
public Productor(SynContainer container){
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"只鸡");
container.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+container.pup().id+"只鸡");
}
}
}
//产品
class Chicken {
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,生产者等待消费者消费
if (count==chickens.length){
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没满,生产者丢入产品
chickens[count] =chicken;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pup(){
//判断是否能消费
if (count==0){
//消费者等待生产者生产。
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者
this.notifyAll();
return chicken;
}
}
解决方式二:信号灯法
- 通过标志为解决。
//测试生产者消费者问题2:信号灯法,标志位解决
public class TsetPc2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Wacher(tv).start();
}
}
//生产者-->演员
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.play("快乐大本营播放中");
}else{
this.tv.play("抖音:纪律美好生活");
}
}
}
}
//消费者-->观众
class Wacher extends Thread{
TV tv;
public Wacher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
//产品-->节目
class TV{
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice;//表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();//演员等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了节目-->"+voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice = voice;
this.flag = !this.flag;//切换标志位
}
//观看
public synchronized void watch(){
if (flag){
try {
this.wait();//观众等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看节目-->"+voice);
//通知演员表演
this.notifyAll();//通知唤醒
this.flag = !this.flag;//切换标志位
}
}
2.线程池
- 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。 可以避免频繁创建销毁、实现重复利用。
- 提高响应速度,降低资源消耗,便于线程管理。
- ExecutorSerivce:真正的线程池接口。Executors:工具类,用于创建并返回不同类型的线程池。
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执 行Runnable。
- Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable。
- void shutdown() 关闭连接池。
//线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}