多线程详解
概述
核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程创建
三种创建方式
-
继承Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程 //总结:注意,线程开启不一定立即执行,由cpu调度执行 public class TestThread01 extends Thread{ @Override public void run() { //run方法线程体 for (int i = 0; i < 200; i++) { System.out.println("我在看代码---"+i); } } public static void main(String[] args) { //main线程,主线程 //创建一个线程对象 TestThread01 testThread01 = new TestThread01(); //调用start方法开启线程 testThread01.start(); for (int i = 0; i < 1000; i++) { System.out.println("我在学习多线程----"+i); } } }
网图下载
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; //练习Thread,实现多线程同步下载图片 public class TestThread02 extends Thread{ private String url; private String name; public TestThread02(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) { TestThread02 testThread01 = new TestThread02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F121420113514%2F201214113514-1-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648128667&t=22e223725381f757ce22e2c9464be38d","1.jpg"); TestThread02 testThread02 = new TestThread02("https://img1.baidu.com/it/u=1407750889,3441968730&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=799","2.jpg"); TestThread02 testThread03 = new TestThread02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp08%2F01042323313046.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648128718&t=8cbd7d628ec7bf5b1953c8ac9edaf2ff","3.jpg"); //开启线程 testThread01.start(); testThread02.start(); testThread03.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方法出错!"); } } }
不建议使用:避免oop单继承局限性
-
实现Runnable接口
//使用Runable实现多线程 public class MyRunnable01 implements Runnable{ @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run()----"+i); } } public static void main(String[] args) { new Thread(new MyRunnable01()).start(); for (int i = 0; i < 1000; i++) { System.out.println("main()-----"+i); } } }
网图下载
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class MyRunnable02 implements Runnable{ private String url; private String name; public MyRunnable02(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) { MyRunnable02 mr01 = new MyRunnable02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F121420113514%2F201214113514-1-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648128667&t=22e223725381f757ce22e2c9464be38d","1.jpg"); MyRunnable02 mr02 = new MyRunnable02("https://img1.baidu.com/it/u=1407750889,3441968730&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=799","2.jpg"); MyRunnable02 mr03 = new MyRunnable02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp08%2F01042323313046.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648128718&t=8cbd7d628ec7bf5b1953c8ac9edaf2ff","3.jpg"); //开启线程 new Thread(mr01).start(); new Thread(mr02).start(); new Thread(mr03).start(); } }
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
//一份资源 StartThread4 station = new StartThread4(); //多个代理 new Thread(station,"小明").start(); new Thread(station,"小红").start(); new Thread(station,"老师").start();
//多个线程同时操作同一个对象 //模拟抢票 //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 public class MyRunnable03 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) { MyRunnable03 myRunnable03 = new MyRunnable03(); new Thread(myRunnable03,"小明").start(); new Thread(myRunnable03,"老师").start(); new Thread(myRunnable03,"黄牛党").start(); } }
-
实现Callable接口
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行:Future result1 = ser.submit(t1);
6.获取结果:boolean r1 = result1.get()
7.关闭服务:ser.shutdownNow();
import com.sun.org.apache.xpath.internal.operations.Bool; import java.util.concurrent.*; //线程创建方式三:实现Callable接口 /* callable的好处 1.可以有返回值 2.抛出异常 */ public class MyCallableDemo01 implements Callable<Boolean> { private String url; private String name; public MyCallableDemo01(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 { MyCallableDemo01 md01 = new MyCallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F121420113514%2F201214113514-1-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648128667&t=22e223725381f757ce22e2c9464be38d","1.jpg"); MyCallableDemo01 md02 = new MyCallableDemo01("https://img1.baidu.com/it/u=1407750889,3441968730&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=799","2.jpg"); MyCallableDemo01 md03 = new MyCallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp08%2F01042323313046.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648128718&t=8cbd7d628ec7bf5b1953c8ac9edaf2ff","3.jpg"); //创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> result1 = ser.submit(md01); Future<Boolean> result2 = ser.submit(md02); Future<Boolean> result3 = ser.submit(md03); //获取结果 boolean r1 = result1.get(); boolean r2 = result2.get(); boolean r3 = result3.get(); //关闭服务 ser.shutdown(); } }
案例:龟兔赛跑
//龟兔赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag){//比赛结束,停止游戏
break;
}
//兔子睡觉
// if (Thread.currentThread().getName().equals("兔子")&& i%10==0){
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
}
}
//判断游戏是否结束
private boolean gameOver(int steps){
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is" + winner);
return true;
}
if (winner!=null){//已经存在胜利者
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
静态代理
package proxystatic;
//静态代理
public class StaticProxy {
public static void main(String[] args) {
new Wedding(new You()).HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("我结婚了,很开心!");
}
}
//代理角色,帮助你结婚
class Wedding implements Marry{
private Marry target;
public Wedding(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚之后,需要收尾款");
}
private void before(){
System.out.println("结婚之前,需要布置现场");
}
}
Lamda表达式
-
避免匿名内部类定义过多
-
其实质属于函数式编程的概念
(params) -> expression[表达式] (params) -> statement[语句] (params) -> {statements}
a-> System.out.println("i like lambda-->"+a)
``new Thread(()->System.out.println(“多线程学习。。。”)).start();`
-
函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。函数式接口就可以通过lambda表达式来创建该接口的对象。
package lambdaShow;
public class TestLambda01{
//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();
}
}
//定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
package lambdaShow;
//总结:lambda表达式只有一行代码的情况下才能简化成一行,如有多行,那么就用代码块包裹。
//前提是接口为函数式接口
//多个参数也可以去掉参数类型,要去掉都去掉,必须加上括号
public class TestLambda02 implements ILove{
@Override
public void Love(int hours) {
System.out.println("I love you--" + hours);
}
public static void main(String[] args) {
ILove love = null;
love = (int hours) -> {
System.out.println("I love you,too ---" + hours);
};
//1.第一种简化 省略参数
love = (hours)->{
System.out.println("I love you,too ---" + hours);
};
//2.第二种简化 省略括号
love = hours->{
System.out.println("I love you,too ---" + hours);
};
//3.第三种简化 省略花括号
love = hours-> System.out.println("I love you,too ---" + hours);
love.Love(5201314);
}
}
interface ILove{
void Love(int hours);
}
线程状态
- 线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
-
线程停止
- 不推荐使用JDK提供的stop()\destroy()方法。(已废弃)
- 推荐线程自己停下来
- 建议使用一个标志位进行终止变量flag=false,则终止线程运行。
public class TestStop01 implements Runnable{ //设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while (flag){ System.out.println("run。。。。Runnable" + i++); } } //设置一个公开的方法停止线程,转换标志位 public void stop(){ this.flag = false; } public static void main(String[] args) { TestStop01 testStop01 = new TestStop01(); new Thread(testStop01).start(); for (int i = 0; i < 1000; i++) { if (i==900){ //调用stop方法停止线程 testStop01.stop(); System.out.println("线程停止了"); } System.out.println("main--" + i); } } }
-
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间到达后线程进入就绪状态;
- sleep可以模拟网络延时,倒计时等。
- 每一个对象都有一个锁,sleep不会释放锁。
import java.text.SimpleDateFormat; import java.util.Date; public class TestSleep01 { public static void main(String[] args) throws InterruptedException { timeDown(); Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间 while (true){ Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH-mm-ss").format(startTime)); startTime = new Date(System.currentTimeMillis());//获取当前系统时间 } } public static void timeDown() throws InterruptedException { int time = 10; while (true){ Thread.sleep(1000); System.out.println(time--); if (time<=0){ break; } } } }
-
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看cpu心情
public class TestYield01 { 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() + "线程停止执行"); }
-
Join
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可以想象成插队
public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 500; 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 < 300; i++) { if (i == 20){ thread.join();//插队 } System.out.println("main" + i); } } }
-
线程状态观测
-
Thread.State
NEW:尚未启动的线程处于此状态。
RUNNABLE:在java虚拟机中执行的线程处于此状态
BLOCKED:被阻塞等待监视器锁定的线程处于此状态
WAITING:正在等待另一个线程执行特定动作的线程处于此状态
TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED:已退出的线程处于此状态
//观测线程的状态 public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ for (int i = 0; i < 5; 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(); System.out.println(thread.getState()); while (state != Thread.State.TERMINATED){//只要线程不终止,就一直输出状态 Thread.sleep(1000); state = thread.getState();//更新状态 System.out.println(state);//输出状态 } } }
-
-
线程优先级
-
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
线程的优先级用数字来表示,范围从1~10
Thread.MIN_PRIORITY=1;
Thread.MAX_PRIORITY=10;
Thread.NORM_PRIORITY=5;
-
使用以下方式改变或获取优先级
getPriority() setPriority(int xxx)
//测试线程的优先级 //线程优化级较高的线程不一定先执行。 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); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); t1.setPriority(6); t1.start(); t2.setPriority(2); t2.start(); t3.setPriority(Thread.MAX_PRIORITY); t3.start(); t4.setPriority(1); t4.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->" + Thread.currentThread().getPriority()); } }
-
-
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待…
//守护线程 public class TestDaemon { public static void main(String[] args) { God god = new God(); Person person = new Person(); Thread thread = new Thread(god); thread.setDaemon(true);//设置守护线程 thread.start();//开启守护线程 thread = new Thread(person); thread.start();//人线程开始 } } //上帝 class God implements Runnable{ @Override public void run() { while (true){ System.out.println("上帝正在守护着你!"); } } } //人 class Person implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("人每天都快乐的活着"); } System.out.println("跟世界说再见---------------------------------"); } }
线程同步机制
- 并发:同一个对象被多个线程同时操作
- 线程同步:处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步,线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
- 队列和锁:为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
三大不安全线程例子
//不安全的买票
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 tickets = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
Buy();
}
}
private void Buy(){
if (tickets<=0){
flag = false;
}
System.out.println(Thread.currentThread().getName() + "买到了第" + tickets-- +"张票");
}
}
//不安全的取钱
//两个人去银行取钱,账户
public class UnsaftBank {
public static void main(String[] args) {
Account account = new Account(100,"中国银行储蓄卡");
new Drawing(account,50,"你").start();
new Drawing(account,100,"媳妇儿").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;
}
@Override
public void run() {
if (drawingMoney>account.money){
System.out.println(Thread.currentThread().getName()+"对不起余额不足!");
return;
}
//线程休眠,放大不安全性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money-drawingMoney;
//你手里的钱
nowMoney += drawingMoney;
System.out.println(this.getName() + "取了" + drawingMoney + "万元");
System.out.println(this.getName()+"手里有" + nowMoney + "万元");
System.out.println("余额还有" + account.money + "万元");
}
}
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
//有可能几个线程同时操作了同一个位置
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
同步方法及同步块
-
同步方法
-
synchronized关键字:包括两种用法:synchronized方法和synchronized块
同步方法:public synchronized void method(int args){}
-
synchronized方法控制对”对象“的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则会线程阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
-
缺陷:若将一个大的方法申明为synchronized将会影响效率
-
同步方法弊端
方法里面需要修改的内容才需要锁,锁的太多,浪费资源
-
同步块
- synchronized(Obj){}
- Obj称为同步监视器
- Obj可以时任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
三大不安全线程修改
//不安全的买票
//使用synchronized让线程变安全
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 tickets = 10;
boolean flag = true;//外部停止方式
@Override
public synchronized void run() {
//买票
while (flag){
try {
Buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void Buy() throws InterruptedException {
if (tickets<=0){
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "买到了第" + tickets-- +"张票");
}
}
//不安全的取钱
//两个人去银行取钱,账户
public class UnsaftBank {
public static void main(String[] args) {
Account account = new Account(1000,"中国银行储蓄卡");
new Drawing(account,50,"你").start();
new Drawing(account,100,"媳妇儿").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;
}
@Override
public void run() {
//锁的对象就是变化的量
synchronized (account){
if (drawingMoney>account.money){
System.out.println(Thread.currentThread().getName()+"对不起余额不足!");
return;
}
//线程休眠,放大不安全性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money-drawingMoney;
//你手里的钱
nowMoney += drawingMoney;
System.out.println(this.getName() + "取了" + drawingMoney + "万元");
System.out.println(this.getName()+"手里有" + nowMoney + "万元");
System.out.println("余额还有" + account.money + "万元");
}
}
}
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
//有可能几个线程同时操作了同一个位置
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(3000);//此处不加线程休眠任然出现数据不正常,原因是主线程跑完了线程还没跑完
System.out.println(list.size());
}
}
CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
死锁
多个线程都在等待对方释放资源,都停止执行的情形
某一个同步块同时拥有”两个以上对象的锁“时,就可能会发生”死锁“的问题。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁情况
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 LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice = 0;//选择顺序
String girlName;
MakeUp(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
makeup();
}
private void makeup(){
if (choice == 0){
synchronized (lipStick){
System.out.println(this.girlName + "获得了口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror){
System.out.println(this.girlName + "获得了镜子的锁");
}
}
}else {
synchronized (mirror){
System.out.println(this.girlName + "获得了镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipStick){
System.out.println(this.girlName + "获得了口红的锁");
}
}
}
}
}
解决死锁
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 LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice = 0;//选择顺序
String girlName;
MakeUp(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
makeup();
}
private void makeup(){
if (choice == 0){
synchronized (lipStick){
System.out.println(this.girlName + "获得了口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror){
System.out.println(this.girlName + "获得了镜子的锁");
}
}else {
synchronized (mirror){
System.out.println(this.girlName + "获得了镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipStick){
System.out.println(this.girlName + "获得了口红的锁");
}
}
}
}
Lock锁
-
JDK5.0开始提供了更加强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象来充当
-
java.util.concurrent.locks.LOCK接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //测试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){ try{ lock.lock(); if (ticketNums>0){ System.out.println(ticketNums--); }else { break; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }finally { lock.unlock(); } } } }
生产消费者模式
应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
-
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
-
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
-
在生产消费者问题中,仅有synchronized是不够的
- synchronized可以阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信)
-
Java提供了几个方法解决线程之间的通信问题
方法名 作用 wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 wait(long timeout) 指定等待的毫秒数 notify() 唤醒一个处于等待状态的线程 notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException
并发协作模型”生产者/消费者模式“ -->管程法
-
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
-
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
-
缓冲区:消费者不能直接使用生产者的数据,他们之间有个”缓冲区“
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//测试:生产者消费者模型-->利用缓冲区解决:管程法 //生产者,消费者,产品,缓冲区 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 synContainer; public Productor(SynContainer synContainer){ this.synContainer = synContainer; } @Override public void run() { for (int i = 1; i <= 100; i++) { Chicken chicken = new Chicken(i); synContainer.push(chicken);//放入缓冲区 System.out.println("生产了第" + i + "只鸡"); } } } //消费者 class Consumer extends Thread{ SynContainer synContainer; public Consumer(SynContainer synContainer){ this.synContainer = synContainer; } @Override public void run() { for (int i = 0; i < 100; i++) { Chicken pop = synContainer.pop(); System.out.println("消费了第" + pop.foodId + "只鸡"); try { Thread.sleep(300);//控制时间,防止出现还没生产就消费了的情况 } catch (InterruptedException e) { e.printStackTrace(); } } } } //产品 class Chicken{ int foodId; public Chicken(int foodId) { this.foodId = foodId; } } //缓冲区 class SynContainer{ Chicken[] chickens = new Chicken[10];//缓冲区大小,类型是存取产品 int count = 0;//缓冲区计数器 //生产者放入产品 public synchronized void push(Chicken chicken){ //如果容器满了就要通知消费者消费 if (count == chickens.length-1){ //通知消费者消费.生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,我们就需要丢入产品 chickens[count] = chicken; count++; //可以通知消费者消费了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop(){ //如果容器空了就要停止消费 if (count ==0){ //消费者等待,生产者生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; //吃完了,通知生产者生产 this.notifyAll(); return chicken; } }
并发协作模型”生产者/消费者模式“ -->信号灯法
//生产者,消费者模式 --> 信号灯法
//利用标志位控制
//表演者,观看者,节目
public class TestPC2 {
public static void main(String[] args) {
Tv tv = new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//表演者
class Player extends Thread{
Tv tv;
public Player(Tv tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i%2 == 0){
this.tv.Programe("动作大片");
}else {
this.tv.Programe("爱情动作大片");
}
}
}
}
//观看者
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv){
this.tv = tv;
}
@Override
public void run() {
for(int i=0;i<10;i++){
this.tv.Watch();
}
}
}
//产品--节目
class Tv{
//表演者表演,观看者等待
//观众观看,演员等待
String programme;//节目
boolean flag = true;
//表演节目
public synchronized void Programe(String programme){
if (!flag){
//演员等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + programme);
//通知观众观看
this.flag = !this.flag;
this.notifyAll();//通知唤醒
this.programme = programme;
}
//观看节目
public synchronized void Watch(){
if (flag){
try {
this.wait();//等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了:" + programme);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
线程池
使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
好处:提高响应速度
降低资源消耗
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
JDK 5.0提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
futuresubmit(Callabletask):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池
//newFixedThreadPool
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭链接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}