多线程
1、线程与进程
1.1进程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息
是进程存在的唯一标志。
进程具有的特征:
-
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
-
并发性:任何进程都可以同其他进行一起并发执行;
-
独立性:进程是系统进行资源分配和调度的一个独立单位;
-
结构性:进程由程序,数据和进程控制块三部分组成
1.2线程
在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明
了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
1.3进程与线程的区别
-
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
-
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
-
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
-
调度和切换:线程上下文切换比进程上下文切换要快得多
2.线程创建(Thread、Runnable、Callable)
-
Thread class(继承Thread类)重点
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程 //总结:线程开启不一定立即执行,由CPU调度执行 public class Test01 extends Thread{ //run方法线程体 @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run方法----------------"+i); } } //main线程,主线程 public static void main(String[] args) { //创建一个线程对象 Test01 test01 = new Test01(); //调用start方法开启线程 test01.start(); for (int i = 0; i < 2000; i++) { System.out.println("main--"+i); } } }
-
练习
//练习Thread,实现多线程同步下载图片 //结果:下载完成的顺序并不一致 public class Test02 extends Thread{ private String url;//网络图片地址 private String name;//保存的文件名 public Test02(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) { Test02 t1 = new Test02("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1843699576,2090351546&fm=26&gp=0.jpg", "1.jpg"); Test02 t2 = new Test02("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3072908190,487120691&fm=26&gp=0.jpg", "2.jpg"); Test02 t3 = new Test02("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1362759233,1316299362&fm=26&gp=0.jpg", "3.jpg"); t1.start(); t2.start(); t3.start(); } } //下载器 class WebDownloader{ //下载方法 public void downloader(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现错误!"); } } }
-
Runnable接口(实现Runnable接口)重点、推荐
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便、方便同一个对象被多个线程使用
package com.wjw.demo01; //创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法 public class Test03 implements Runnable{ public void run() { for (int i = 0; i < 200; i++) { System.out.println("run------------------"+i); } } public static void main(String[] args) { //创建runnbale接口实现类对象 Test03 test03 = new Test03(); //创建线程对象,通过线程对象来开启我们的线程。--代理 Thread thread = new Thread(test03); thread.start(); for (int i = 0; i < 2000; i++) { System.out.println("main--"+i); } } }
-
造成的并发问题
package com.wjw.demo01; //多个线程同时操作一个对象 问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 public class Test04 implements Runnable { private int number = 10; public void run() { while (true){ if (number<=0){ break; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"--对应的为--"+number--); } } public static void main(String[] args) { Test04 test04 = new Test04(); new Thread(test04,"张三").start(); new Thread(test04,"李四").start(); new Thread(test04,"王五").start(); } } /*结果:张三--对应的为--8 王五--对应的为--10 李四--对应的为--9 李四--对应的为--7 王五--对应的为--6 张三--对应的为--5 李四--对应的为--4 王五--对应的为--3 张三--对应的为--2 张三--对应的为--1 王五--对应的为--0 李四--对应的为---1 */
package com.wjw.demo01; //龟兔赛跑 public class Race implements Runnable { private static String ACE; public void run() { for (int i = 1; i <= 100; i++) { if (bushu(i)){ break; } if (Thread.currentThread().getName().equals("兔子")&&i%10==0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"--到达了第"+i+"米"); } } //判断冠军是谁 private boolean bushu(int bushu){ if (ACE!=null){ return true; } if (bushu == 100) { ACE = Thread.currentThread().getName(); System.out.println("冠军为:" + ACE); return true; } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); } }
-
Callable接口(实现Callable接口)了解
-
实现Callable接口,需要返回值类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务:ExecutorSrvice ser = Executors.newFixedThreadPool(1);
-
提交执行:Fture result1 = ser.subm(t1)
-
获取结果:boolean r1 = result1.get()
-
关闭服务:ser.shutdownNow()
package com.wjw.demo02; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; public class Test01 implements Callable<Boolean>{ private String url;//网络图片地址 private String name;//保存的文件名 public Test01(String url,String name){ this.url = url; this.name = name; } 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 { Test01 t1 = new Test01("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1843699576,2090351546&fm=26&gp=0.jpg", "1.jpg"); Test01 t2 = new Test01("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3072908190,487120691&fm=26&gp=0.jpg", "2.jpg"); Test01 t3 = new Test01("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1362759233,1316299362&fm=26&gp=0.jpg", "3.jpg"); //创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行: Future<Boolean> result1 = ser.submit(t1); Future<Boolean> result2 = ser.submit(t2); Future<Boolean> result3 = ser.submit(t3); //获取结果: boolean r1 = result1.get(); boolean r2 = result2.get(); boolean r3 = result3.get(); //关闭服务: ser.shutdownNow(); } } //下载器 class WebDownloader{ //下载方法 public void downloader(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现错误!"); } } }
-
3、静态代理模式
package com.wjw.proxystatic;
//静态代理模式总结:
/*
* 真实对象和代理对象都要实现同一个接口
*代理对象要代理真实角色
*是线程的底部原理
* 好处:代理对象酷做真实对象做不了的事情
* 真实对象专注做自己的事情
* */
public class StacticProxy {
public static void main(String[] args) {
you you = new you();//真实对象
wedingCompany wedingCompany = new wedingCompany(you);//将真实对象给代理
wedingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//代表你
class you implements Marry{
public void HappyMarry() {
System.out.println("你结婚了!");
}
}
//婚庆公司,代理角色,帮助你完成事宜
class wedingCompany implements Marry{
//指向代理的谁
private Marry target;
public wedingCompany(Marry target) {
this.target = target;
}
public void HappyMarry(){
before();
this.target.HappyMarry();//这就是真实对象
after();
}
private void before(){
System.out.println("开始--布置场景");
}
private void after(){
System.out.println("结束--收尾款");
}
}
- 代理分为静态代理和动态代理两种。
- 静态代理,代理类需要自己编写代码写成。
- 动态代理,代理类通过 Proxy.newInstance() 方法生成。
- 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
- 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
- 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
- 代理模式本质上的目的是为了增强现有代码的功能。
4、Lamda表达式
-
λ希腊字母表中排序第十一位的字母,英文名称为Lambda
-
避免匿名内部类定义过多
-
其实质属于函数式编程的概念
-
为什么使用Lanmda表达式
- 避免匿名内部类定义过多
- 可以让代码看起来更加简洁
- 去掉了一堆没有意义的代码,只留下核心的逻辑
package com.wjw.Lambad;
//推导Lambda表达式
public class TestLambad {
//2、静态内部类
static class Like2 implements Ilike {
public void lambad() {
System.out.println("静态内部类--lambad02");
}
}
public static void main(String[] args) {
//实现类
Ilike like = new Like();//输出lambad01
like.lambad();
//静态内部类
like = new Like2();//输出lambad02
like.lambad();
//3、局部内部类
class Like3 implements Ilike {
public void lambad() {
System.out.println("局部内部类--lambad03");
}
}
like = new Like3();
like.lambad();
//4、匿名内部类,没有类的名称,必须借助接口或者父类
like = new Ilike() {
public void lambad() {
System.out.println("匿名内部类--lambad04");
}
};
like.lambad();
//5、用Lambad简化
like = ()-> {
System.out.println("用Lambad简化--lambad05");
};
like.lambad();
}
}
//定义一个函数式接口
interface Ilike{
void lambad();
}
//1、实现类
class Like implements Ilike {
public void lambad() {
System.out.println("实现类--lambad01");
}
}
package com.wjw.Lambad;
/*
*总结:
* 1、lambad表达式只能有一行代码的情况下才能简化为一行,如果多行有,那么就用代码块包裹
* 2、前提是接口为函数式接口
* 3、多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
* */
public class Test03Lambad {
public static void main(String[] args) {
Tes tes = null;
//1、lambad表示简化
tes = (int i)->{
System.out.println("01---"+i);
};
tes.sou(1);
//2、简化参数类型
tes = (i)->{
System.out.println("02---"+i);
};
tes.sou(2);
//3、简化括号
tes = i -> {
System.out.println("03---"+i);
};
tes.sou(3);
//4、简化花括号
tes = i -> System.out.println("04---"+i);
tes.sou(4);
}
}
interface Tes{
void sou(int i);
}
5、线程状态
-
创建状态
-
就绪状态
-
阻塞状态
-
运行状态
-
死亡状态
-
线程方法
-
停止线程
package com.wjw.state;
/*
* 测试stop
* 1、建议线程正常停止--->利用次数,不建议死循环
* 2、建议使用标志位--->设置一个标志位
* 3、不使用stop或者destory等过时或者JDK不建议使用的方法
* */
public class TestStop implements Runnable {
//1、线程中定义线程体使用标识
private boolean flag = true;
@Override
public void run() {
int i=0;
//2、线程体使用该标识
while (flag){
System.out.println("run.............Thread"+i++);
}
}
//3、对外提供方法改变标识
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 < 500; i++) {
if (i==100){
//调用stop()方法切换标志位
testStop.stop();
System.out.println("线程已停止");
}
System.out.println("main----"+i);
}
}
}
6、线程休眠 sleep()
- sleep(毫秒数)指定当前线程停止的实践
- sleep()存在异常InteruptedException
- sleep()实践到达后线程进入就绪状态
- sleep()可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
package com.wjw.state;
//模拟网络延时:放大问题的发生性
public class TestSleep implements Runnable {
private int number = 10;
public void run() {
while (true){
if (number<=0){
break;
}
try {
//延时
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--对应的为--"+number--);
}
}
public static void main(String[] args) {
com.wjw.demo01.Test04 test04 = new com.wjw.demo01.Test04();
new Thread(test04,"张三").start();
new Thread(test04,"李四").start();
new Thread(test04,"王五").start();
}
}
package com.wjw.state;
import java.text.SimpleDateFormat;
import java.util.*;
//倒计时
public class TestSleep02 {
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<0){
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
//倒计时
//tenDown();
//打印当前时间
Date date = new Date(System.currentTimeMillis());
while (true){
Thread.sleep(1000);//SimpleDateFormat
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
}
}
}
7、线程礼让 yield()
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU心情
package com.wjw.state;
//测试线程礼让
//不一定成功
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行!");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行!");
}
}
8、线程强制执行 join()
- Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
- 可以想象成插队
package com.wjw.state;
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Join----------"+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==50){
thread.join();
}
System.out.println("main--"+i);
}
}
}
9、线程优先级
- java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程;线程调度器按照优先级决定该调度哪个线程来执行
- 线程的优先级用数字来表示,范围从1~10
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY= 10
- Thread.NORM_PRIORITY = 5
- 使用getPriority()和setPriority()来获取或改变优先级
package com.wjw.state;
//线程优先级
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-----"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority,"t1");
Thread t2 = new Thread(myPriority,"t2");
Thread t3 = new Thread(myPriority,"t3");
Thread t4 = new Thread(myPriority,"t4");
Thread t5 = new Thread(myPriority,"t5");
//先设置优先级,在启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
t5.setPriority(7);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-----"+Thread.currentThread().getPriority());
}
}
10、守护线程setDeamon()
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志,监控内存,垃圾回收
package com.wjw.state;
public class TestDaemon {
public static void main(String[] args) {
gud gud = new gud();
you you = new you();
Thread thread = new Thread(gud);
thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程
thread.start();//gud线程启动
new Thread(you).start();//you线程启动
}
}
class gud implements Runnable{
@Override
public void run() {
int i = 0;
while (true){
System.out.println("守护你的第----------"+ i++ +"天");
}
}
}
class you implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你的第--"+i+"天");
}
}
}
11、线程同步
-
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程在使用
-
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制syncronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一下问题:
- 一个线程持有锁会导致其他所有需要此锁的进程挂起
- 在多线程竞争的情况下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
package com.wjw.syn;
import java.util.ArrayList;
import java.util.List;
public class UnasList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
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());
}
}
package com.wjw.syn;
//不安全的取钱
//结果:余额会变成负数
//解决方法:加入synchronized(Obj){},锁的对象就是变化的量,需要增删改的对象,此处就为accounty
public class UnsafeBank {
public static void main(String[] args) {
Accounty accounty = new Accounty(100, "基金");
new Drawing(accounty, 50, "张三").start();
new Drawing(accounty, 80, "李四").start();
}
}
//账户
class Accounty{
int money;//余额
String name;//卡号
public Accounty(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Accounty accounty;//账户
int drawingMoney;//取了多少
int nowMonmey;//现在金额
public Drawing(Accounty accounty, int drawingMoney,String name ) {
super(name);
this.accounty = accounty;
this.drawingMoney = drawingMoney;//你取多少
}
//取钱
@Override
public void run() {
synchronized (accounty){
//判断有没有钱
if (accounty.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+",账户钱不够!");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
accounty.money=accounty.money-drawingMoney;
//你手里的钱
nowMonmey = nowMonmey+drawingMoney;
System.out.println(accounty.name+"卡内余额为:"+accounty.money);
//Thread.currentThread().getName() = this.getName();
System.out.println(this.getName()+"你拿到了"+nowMonmey);
}
}
}
package com.wjw.syn;
//买票的错误例子/
//结果:错误,会有重复 并且会有负数
//解决方法:加synchronized,排队一个一个来,会影响效率但是安全
public class UnsafeBuyTicket {
public static void main(String[] args) {
piao piao = new piao();
new Thread(piao,"张三").start();
new Thread(piao,"李四").start();
new Thread(piao,"王五").start();
}
}
class piao implements Runnable{
private int num = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
try {
falge();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法,锁是this
public synchronized void falge() throws InterruptedException {//判断有没有票
if (num<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到了第"+num--+"张票");
}
}
12、同步方法和同步块(解决上面问题)
-
同步块: synchronized(obj){}
-
obj称之为同步监视器
-
obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个对象本身,或者是class -
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
-
同步方法在方法上添加synchronized关键子,锁的是对象本身
13、死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源释放才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有"两个以上的对象锁"时,就可能发生死锁现象
- 死锁产生的条件
- 互斥条件: 一个资源每次只能被一个进程使用
- 请求保持条件: 一个进程因请求资源而阻塞时.对以获得的资源保持不放
- 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
14、Lock锁
-
从JDK1.5开始,java提供了更为强大的线程同步机制——通过显示定义同步锁对象来实现同步,同步锁使用lock对象来充当
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中.比较常用的是ReentrantLock,可以显示加锁,释放锁
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 ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { while (true) { try { reentrantLock.lock(); //加锁 if(ticketNums > 0 ){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "购买了第" + ticketNums-- + "张票"); } else { break; } } finally { //解锁 reentrantLock.unlock(); } } } }
-
和synchronized对比
- Lock是显示锁(手动开启和关闭,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序
- Lock > 同步代码块 > 同步方法
15、线程协作
-
生产者和消费者问题
-
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库产品取走消费
-
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
-
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
-
这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件
-
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又要马上通知消费者消费
-
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
-
在生产者消费者问题上,仅有synchronized是不够的
-
synchronized可阻止并发更新同一个共享资源,实现了同步
-
synchronized不能用来实现不同线程之间消息传递(通信)
-
-
解决方式
- 并发协作模式"生产者/消费者模式" ===>管程法
- 生产者: 负责生产数据的模块(可能是方法,对象,线程,数组)
- 消费者: 负责处理数据的模块(可能是方法,对象,线程,数组)
- 缓冲区: 消费者不能直接使用生产者的数据,他们之间有个缓冲区
- 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//测试生产者消费者模型 --->管程法 //产品 public class Product { private int id; public Product(int id) { this.id = id; } public int getId() { return id; } public void setId(int id) { this.id = id; } } //消费者 public class ConsumerThread extends Thread { private SyncContainer syncContainer; public ConsumerThread(SyncContainer syncContainer) { this.syncContainer = syncContainer; } @Override public void run() { for (int i = 0; i < 100; i++) { Product pop = syncContainer.pop(); System.out.println("消费了第"+ pop.getId() + "号产品"); } } } //生产者 public class ProviderThread extends Thread { //创建好的缓冲区 private SyncContainer syncContainer; public ProviderThread(SyncContainer syncContainer) { this.syncContainer = syncContainer; } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生产了" + i + "只鸡!"); syncContainer.push(new Product(i)); } } } //容器 public class SyncContainer { //容器大小 Product[] products = new Product[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Product product){ //如果容器满了,就要等待消费者 //通知消费者消费,生产者等待 if(count == products.length) { try { this.wait(); } catch (InterruptedException e){ e.printStackTrace(); } } //如果容器没有满,就要生产产品 products[count] = product; count++; //可以通知消费者消费 this.notifyAll(); } //消费者消费产品 public synchronized Product pop(){ //判断容器是否为空 if(count == 0){ //通知生产者生产.消费者等待 try { this.wait(); } catch (InterruptedException e){ e.printStackTrace(); } } //如果可以消费 count--; Product product = products[count]; //通知生产者生产 this.notifyAll(); return product; } } public class Main { public static void main(String[] args) { SyncContainer container = new SyncContainer(); new ProviderThread(container).start(); new ConsumerThread(container).start(); } }
- 并发协作模式"生产者/消费者模式" ===>管程法
-
并发协作模式"生产者/消费者模式" ===>信号灯法
//产品-->节目 public class TV { //演员表演,观众等待 //观众观看,演员等待 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.voice = voice; this.notifyAll(); this.flag = !flag; } //观看 public synchronized void watch(){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看了" + voice); //通知演员表演 this.notifyAll(); this.flag = !flag; } } public class Player extends Thread { private TV tv = null; public Player(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 100; i++) { tv.play("表演了" + i + "号节目"); } } } public class Watcher extends Thread { private TV tv = null; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 100; i++) { tv.watch(); } } } //测试生产者消费者问题:信号灯法,标志位解决 public class Main { public static void main(String[] args) { TV tv = new TV(); new Watcher(tv).start(); new Player(tv).start(); } }
16、使用线程池
-
背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
-
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完毕放回池中,可以避免频繁的创建销毁,实现重复利用,类似生活中的工共交通工具
-
好处
- 提高了响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(…)
- corePoolSize: 核心池的大小
- maximumPoolSize: 最大线程数
- keepAliveTime: 线程没有任务时最多保持多长时间后会终止
package com.wjw.gaoji; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestPool { public static void main(String[] args) { //1、创建服务,创建线程池 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭连接 service.shutdown(); } } class MyThread implements Runnable{ @Override public synchronized void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+",,,"+i); } } }
17、总结
package com.wjw.state;
/*
* 测试stop
* 1、建议线程正常停止--->利用次数,不建议死循环
* 2、建议使用标志位--->设置一个标志位
* 3、不使用stop或者destory等过时或者JDK不建议使用的方法
* */
public class TestStop implements Runnable {
//1、线程中定义线程体使用标识
private boolean flag = true;
@Override
public void run() {
int i=0;
//2、线程体使用该标识
while (flag){
System.out.println("run.............Thread"+i++);
}
}
//3、对外提供方法改变标识
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 < 500; i++) {
if (i==100){
//调用stop()方法切换标志位
testStop.stop();
System.out.println("线程已停止");
}
System.out.println("main----"+i);
}
}
}