多线程笔记
多线程
-
程序运行时,即使自己没有创建线程,至少有main线程、gc线程和异常处理线程
-
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
-
每个进程都会有一段专用的内存区域但线程间可以共享相同的内存单元(包括代码与数据)
线程创建
-
不同线程的run()方法中的局部变量互不干扰
-
可以在任何一个线程中启动另一个线程
// 两个线程共同完成1+2+3+...+...10,一个线程计算前一半,另一个线程计算后一半 public class t2t { public static void main(String[] args) { computerSum comSum = new computerSum(); Thread thread1 = new Thread(comSum,"张三"); thread1.start(); } } class computerSum implements Runnable{ int i=1; // 线程共享的数据 int sum=0; // 线程共享的数据 @Override public void run() { Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"开始计算"); while (i<=10){ sum=sum+i; System.out.println(" "+sum); if (i==5){ System.out.println(thread.getName()+"完成任务了! i="+i); i++; // 线程1死亡之前将i变为6 Thread thread2 = new Thread(this,"李四");// 两个线程的目标对象相同 thread2.start(); // 线程2启动,立刻执行run方法,此时由于目标对象相同所以i=6;sum=15; return; // 线程1死亡 } i++; // 线程1死亡之后不会执行这一句,因为在return之后线程1就已经死亡 } } }
继承Thread类(此类已经实现Runnable接口)
-
自定义线程类继承Thread类
-
重写run()方法作为线程执行体
-
创建自定义线程类的对象并调用start()方法启动线程
注意事项:
-
线程开启不一定立即执行,由CPU调度执行(并发执行)
-
不建议使用,因为OOP单继承局限性
-
单继承的意思是,一个子类只能有一个父类,想要得到两个类的特性是不能通过继承实现。接口可以解决这个问题。因为,一个类可以实现多个接口
-
如果你想写一个类C,但这个类C已经继承了一个类A,此时,你又想让C实现多线程。用继承Thread类的方式不行了。此时,只能用Runnable接口。Runnable接口就是为了解决这种情境出现的。
/**
* 多线程下载图片,使用common-io-2.6.jar
*/
public class TestThread{
public static void main(String[] args) {
downThread dt1=new downThread("http://www.ncut.edu.cn/__local/1/21/0C/FB2C0CCB51E16729E81BF44DCB7_A5FCA472_4CA21.jpg","1.jpg");
downThread dt2=new downThread("http://www.ncut.edu.cn/__local/1/21/0C/FB2C0CCB51E16729E81BF44DCB7_A5FCA472_4CA21.jpg","2.jpg");
downThread dt3=new downThread("http://www.ncut.edu.cn/__local/1/21/0C/FB2C0CCB51E16729E81BF44DCB7_A5FCA472_4CA21.jpg","3.jpg");
dt1.start();
dt2.start();
dt3.start();
}
}
class downThread extends Thread{
private String url;
private String name;
public downThread(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
new downLoader().down(url,name);
System.out.println("下载完毕的文件名"+name);
}
}
class downLoader{
public void down(String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载器异常");
}
}
}
实现Runnable接口
- 自定义线程类实现Runnable接口
- 实现Run()方法,编写线程执行体
- 创建自定义线程对象(所创线程的目标对象)作为参数创建Thread对象,Thread对象运行start()方法
public class TestRun{
public static void main(String[] args) {
Test test=new Test();
new Thread(test).start();
for (int i = 0; i < 200; i++) {
System.out.println("==========="+i);
}
}
}
class Test implements Runnable{
@Override
public void run() {
for (int i = 0; i <200; i++) {
System.out.println("++++++++++++"+i);
}
}
}
- 推荐使用,避免Java单继承局限性,灵活方便,方便同一个对象被多个线程使用
// 火车票的例子
// 多个线程同时操作同一个对象,线程不安全,数据紊乱,多个人购买到同一张票,线程同步能解决
class ThreadTest 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 class Thread3{
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
new Thread(threadTest,"小明").start(); // 给线程起名字
new Thread(threadTest,"老师").start();
new Thread(threadTest,"黄牛").start();
}
}
// 龟兔赛跑
class RaceTwo implements Runnable{
private static String winner; //设置静态为了保证只有一个胜利者
@Override
public void run() {
for (int i = 1; i <=100; i++) {
if(Thread.currentThread()
.getName().equals("兔子")&&i%10==0){
System.out.println("小兔子跑了10步,要休息一下啦");
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean b = gameOver(i);
if (b){
break;
}
System.out.println(Thread.currentThread()
.getName()+"跑了"+i+"步");
}
}
public 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 class Race{
public static void main(String[] args) {
RaceTwo race=new RaceTwo();
new Thread(race,"乌龟").start();
new Thread(race,"兔子").start();
}
}
实现Callable接口(了解)
- 实现Callable接口,需要call()方法返回值类型做为接口泛型
- 重写call方法,需要定义返回值类型
- 创建自定义线程对象
- 创建执行服务:
ExecutorService ser=Executors.newFixedThreadPool(3);
- 提交执行:
Future<Boolean> r1=ser.submit(dt1);
- 获取结果:
boolean rs1= r1.get(); //调用get()方法需要抛出异常
- 关闭服务:
ser.shutdownNow();
/**
* Callable接口实现多线程下载图片
* 好处:可以定义返回值;可以抛出异常
*/
public class TestThread{
public static void main(String[] args) throws ExecutionException, InterruptedException {
downThread dt1=new downThread("http://www.ncut.edu.cn/__local/1/21/0C/FB2C0CCB51E16729E81BF44DCB7_A5FCA472_4CA21.jpg","1.jpg");
downThread dt2=new downThread("http://www.ncut.edu.cn/__local/1/21/0C/FB2C0CCB51E16729E81BF44DCB7_A5FCA472_4CA21.jpg","2.jpg");
downThread dt3=new downThread("http://www.ncut.edu.cn/__local/1/21/0C/FB2C0CCB51E16729E81BF44DCB7_A5FCA472_4CA21.jpg","3.jpg");
// 创建执行服务 3 是线程数
ExecutorService ser=Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> r1=ser.submit(dt1);
Future<Boolean> r2=ser.submit(dt2);
Future<Boolean> r3=ser.submit(dt3);
// 获取结果
boolean rs1= r1.get();
boolean rs2= r2.get();
boolean rs3= r3.get();
//关闭服务
ser.shutdownNow();
}
}
class downThread implements Callable<Boolean> {
private String url;
private String name;
public downThread(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() {
new downLoader().down(url,name);
System.out.println("下载完毕的文件名"+name);
return true;
}
}
class downLoader{
public void down(String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载器异常");
}
}
}
静态代理
- 真实对象和代理对象要实现同一个接口
- 代理对象要代理真实对象
// 代理对象可以做很多真实对象做不了的事情
// 真实对象可以专注做自己的事情
public class Testproxy {
public static void main(String[] args) {
You you = new You();
new WeddingCompany(you).HappyMarry();
// new Thread(()-> System.out.println("我爱你")).start(); start()最终会调用run()方法
// new Thread(new Runnable() {
// @Override
// public void run() {
// System.out.println("我爱你,你爱我");
// }
// }).start();
}
}
interface Marry{
void HappyMarry();
}
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("与爱的人结婚");
}
}
class WeddingCompany implements Marry{
private Marry client; // 代理的真实对象
public WeddingCompany(Marry client) {
this.client = client;
}
@Override
public void HappyMarry() {
before();
this.client.HappyMarry();// 调用真实对象中的HappyMarry()方法
after();
}
private void after() {
System.out.println("结婚之后");
}
private void before() {
System.out.println("结婚之前");
}
}
Lamda表达式
- 避免匿名内部类定义过多
- 函数式接口:任何接口,如果只包含唯一一个抽象方法(接口中的方法一定是公开抽象的),那么它就是一个函数式接口
- 对于函数式接口我们可以通过Lambda表达式来创建接口对象
public class Demo01 {
// 3 静态内部类
static class lamdaTestImpl1 implements LamdaTest{
@Override
public void lamda() {
System.out.println("我在学习Lambda1");
}
}
public static void main(String[] args) {
LamdaTest lamdaTest=new lamdaTestImpl();
lamdaTest.lamda();
LamdaTest lamdaTest1=new lamdaTestImpl1();
lamdaTest1.lamda();
// 4 局部内部类
class lamdaTestImpl2 implements LamdaTest{
@Override
public void lamda() {
System.out.println("我在学习Lambda2");
}
}
LamdaTest lamdaTest2=new lamdaTestImpl2();
lamdaTest2.lamda();
// 5 匿名内部类 没有类的名称,必须借助接口或父类
LamdaTest lamdaTest3= new LamdaTest() {
@Override
public void lamda() {
System.out.println("我在学习Lambda3");
}
};
lamdaTest3.lamda();
// 6 用lamda表达式简化
LamdaTest lamdaTest4=()->{
System.out.println("我在学习Lambda4");
};
lamdaTest4.lamda();
// 7 用lamda表达式简化
LamdaTest lamdaTest5=()->
System.out.println("我在学习Lambda5");
lamdaTest5.lamda();
}
}
interface LamdaTest{
void lamda(); // 1 定义一个函数式接口
}
// 2 实现类
class lamdaTestImpl implements LamdaTest{
@Override
public void lamda() {
System.out.println("我在学习Lambda");
}
}
/** * 如果执行代码有多行那么必须要有大括号 * 接口必须是函数式接口 * 多个参数也可以去掉参数类型,要去类型就全部去掉,这些参数必须加括号 */public class Demo02 { public static void main(String[] args) { Ilove ilove; ilove=(int a)->{ System.out.println("我在学习有参Lamda"+a); }; ilove.loveMe(200); // 简化1:去掉参数类型 ilove=(a)->{ System.out.println("我在学习有参Lamda"+a); }; ilove.loveMe(300); // 简化2:去掉小括号 ilove=a->{ System.out.println("我在学习有参Lamda"+a); }; ilove.loveMe(400); // 简化3:去掉大括号 ilove=a-> System.out.println("我在学习有参Lamda"+a); ilove.loveMe(500); }}interface Ilove{ void loveMe(int a);}
停止线程
- 推荐线程自己停止
- 建议使用一个标志位作为终止变量,当flag==false终止线程
class SThread 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 class StopThread{ public static void main(String[] args){ SThread sThread = new SThread(); new Thread(sThread).start(); for (int i = 0; i < 1000; i++) { System.out.println("main"+i); if (i==900){ sThread.stop(); System.out.println("线程该停止了"); } } }}
线程休眠_sleep
- sleep时间到达后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
// 模拟网络延时:放大问题的发生性class ThreadTest 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 class Thread3{ public static void main(String[] args) { ThreadTest threadTest=new ThreadTest(); new Thread(threadTest,"小明").start(); new Thread(threadTest,"老师").start(); new Thread(threadTest,"黄牛").start(); }}
public class TestSleep { public static void main(String[] args) throws InterruptedException { Tdown(); // 模拟倒计时 // 打印系统当前时间 Date NowTime=new Date(System.currentTimeMillis()); while (true){ Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(NowTime)); NowTime=new Date(System.currentTimeMillis()); } } static void Tdown() throws InterruptedException { int num=10; while (true){ Thread.sleep(1000); System.out.println(num--); if (num<=0) break; } }}
线程礼让_yield
- 礼让线程,让当前正在执行的线程暂停,将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU调度情况
// 这个例子不严谨,因为多线程本身就是并发执行的,根本看不出礼让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()+"线程停止执行"); }}
线程强制执行_join
- Join合并线程,阻塞其它线程,待此线程执行完毕后,再执行其他线程
// 想象为插队public class Demo03 { public static void main(String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread th = new Thread(testJoin); th.start(); for (int i = 0; i < 500; i++) { if (i==200) // 200之前是正常的并发执行,200之后main线程等待,直到th子线程执行完毕 { th.join(); } System.out.println("main"+i); } }}class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("线程Vip在此"+i); } }}
观测线程状态_Thread.State
-
在给定时间点上,一个线程只能处于一种状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
-
在调用第一次start方法并且run方法执行完毕之后不能调用第二次start方法,因为调用第一次start方法并且run方法执行完毕之后此线程已经死亡
-
死亡原因:1、正常结束run方法;2、强制run方法结束
-
死亡状态线程释放了分配给线程对象的内存
-
线程可以处于下列状态之一:
NEW
至今尚未启动的线程处于这种状态。RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。TERMINATED
已退出的线程处于这种状态。
public class LookState { public static void main(String[] args) throws InterruptedException{ Thread th = new Thread(()->{ for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程结束"); }); Thread.State state = th.getState(); System.out.println("子线程state = " + state); th.start(); state = th.getState(); System.out.println("子线程state = " + state); // 只要线程不终止就一直输出状态 while (state!= Thread.State.TERMINATED){ Thread.sleep(100); state = th.getState(); // 更新线程状态 System.out.println("子线程state = "+ state); } }}
线程优先级
-
线程优先级用数字表示,范围从1~10.
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
-
使用
getPriority();setPriority(int num)
获得/改变优先级 -
优先级高不一定先执行,主要还是看CPU调度算法
public class TestPrioty { public static void main(String[] args) { // 主线程优先级,是默认的无法改变 System.out.println(Thread.currentThread().getPriority()); Thread t1 = new Thread(()-> System.out.println(Thread.currentThread().getPriority())); Thread t2 = new Thread(()-> System.out.println(Thread.currentThread().getPriority())); t1.start(); t2.setPriority(6); t2.start(); }}
守护线程
- 线程分为守护线程和用户线程
- 虚拟机必须确保用户线程执行完毕而不用等待守护线程执行完毕
- 后台记录操作日志,监控内存,垃圾回收等等
public class TestDaemon { public static void main(String[] args) { Marx marx = new Marx(); Thread th_marx = new Thread(marx); th_marx.setDaemon(true);//线程默认是false即用户线程 th_marx.start(); new Thread(new Me()).start(); }}class Marx implements Runnable{ @Override public void run() { while (true){ System.out.println("马克思永远守护我们"); } }}class Me implements Runnable{ @Override public void run() { for (int i = 1; i <= 36500; i++) { System.out.println("开心活着的第"+i+"天"); } }}
线程同步
- 并发:同一个资源被多个线程同时操作
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
- 锁:每个对象都拥有一把锁
- 队列+锁 形成 线程同步
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
// 线程不安全,买到的票数有负数(0、-1)// 多个线程操作同一个线程对象public class Thread3{ public static void main(String[] args) { BuyTickets buyTickets = new BuyTickets(); new Thread(buyTickets,"小明").start(); new Thread(buyTickets,"小红").start(); new Thread(buyTickets,"小刚").start(); }}class BuyTickets implements Runnable{ private int TicketNums=10; boolean flag=true; @Override public void run() { while (flag) { try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void buy() throws InterruptedException { if (TicketNums <= 0) { this.flag = false; return; } Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "买到了第" + TicketNums-- + "张票"); }}
// 不安全的取钱// 两个人去银行取同一个账户的钱// 两个线程对象操作同一个资源(账户)public class th { public static void main(String[] args) { Account account = new Account(100,"后备能源"); DrawMoney me = new DrawMoney(account,50,"我自己"); DrawMoney myWife = new DrawMoney(account,100,"我的妻子"); me.start(); myWife.start(); }}class Account{ int money; // 余额 String name; // 账户名 public Account(int money, String name) { this.money = money; this.name = name; }}// 银行class DrawMoney extends Thread{Account account; // 账户int drawMoney; //取多少钱int nowMoney; // 现在’手‘里的余额 定义之后默认为0 public DrawMoney(Account account, int drawMoney, String name) { super(name); // 线程名字 this.account = account; this.drawMoney = drawMoney; } // 取钱 @Override public void run() { // 判断有没有钱 if (account.money-drawMoney<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } try { Thread.sleep(1000); // 等待另一个运行到这 } catch (InterruptedException e) { e.printStackTrace(); } account.money = account.money -drawMoney; // 账户余额 = 余额 - 我取的钱 nowMoney =nowMoney +drawMoney; // 你拥有的钱 System.out.println(this.getName()+"取钱之后余额为"+account.money); //this.getName() 等价 Thread.currentThread().getName() 因为继承了Thread类 System.out.println(this.getName()+"手里的钱:"+nowMoney); }}
class ThreadTest implements Runnable { private int ticketNums=10; @Override public void run() { while (ticketNums>=0){ System.out.println(Thread.currentThread() .getName()+"抢到了第"+ticketNums--+"张票"); } }}class ThreadTest1 extends Thread { private int ticketNums=10; @Override public void run() { while (ticketNums>=0){ System.out.println(Thread.currentThread() .getName()+"抢到了第"+ticketNums--+"张票"); } }}public class test2{ public static void main(String[] args) {// ThreadTest threadTest=new ThreadTest();// new Thread(threadTest,"小明").start(); // 给线程起名字// new Thread(threadTest,"老师").start();// 多个线程操作同一个线程对象,那么这个线程对象中的票数就是唯一的公共资源,可以实现多人抢票的概念 ThreadTest1 threadTest1=new ThreadTest1(); ThreadTest1 threadTest2=new ThreadTest1(); threadTest2.start(); threadTest1.start();// 两个线程分别操作各自的线程对象,那么票数就不是公共资源,每个线程都有一个完整票数,自然无法实现多人抢票的概念,实现的是分别卖十张 }}
// 线程不安全的集合// 按照此法测试,Vector确实是线程安全的集合public class ListThread { public static void main(String[] args) throws InterruptedException { List<String> list=new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); // 一万条线程操作了 一万个 内容相同的对象 × // 一万条线程操作了 同一个 对象 √ }).start(); } // 同一瞬间add同一个位置导致数据覆盖,导致数据减少 Thread.sleep(3000); // 休眠主线程,保证这一万个线程全部结束 System.out.println(list.size()); }}
同步方法
-
由于我们可以通过 private 关键字来保证数据对象只能被方法访问(使用get/set方法获得private数据对象),所以我们只需要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法: synchronized 方法和 synchronized 块。
-
同步方法:
public synchronized void method (int a) {}
-
synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
-
缺陷:若将一个大的方法申明为synchronized 将会影响效率,因为方法的代码内容分为只读代码和修改代码,只读代码不需要锁,只锁修改代码,锁的太多浪费资源
同步块
-
同步块: synchronized (Obj )
-
Obj 称之为同步监视器
- Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
- 锁的对象是要变化的,就是需要增删改的对象
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this ,就是这个对象本身,或者是 class [反射中讲解]
-
同步监视器的执行过程
1、第一个线程访问,锁定同步监视器,执行其中代码
2、第二个线程访问,发现同步监视器被锁定,无法访问
3、第一个线程访问完毕,解锁同步监视器
4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问
// 线程安全的买票// 多个线程操作同一个线程对象public class Thread3{ public static void main(String[] args) { BuyTickets buyTickets = new BuyTickets(); new Thread(buyTickets,"小明").start(); new Thread(buyTickets,"小红").start(); new Thread(buyTickets,"小刚").start(); }}class BuyTickets implements Runnable{ private int TicketNums=10; boolean flag=true; @Override public void run() { while (flag) { try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } // synchronized 同步方法 ,锁的是this(默认是this) private synchronized void buy() throws InterruptedException { if (TicketNums <= 0) { this.flag = false; return; } Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "买到了第" + TicketNums-- + "张票"); }}
// 安全的取钱// 两个人去银行取同一个账户的钱// 两个线程对象操作同一个资源(账户)public class th { public static void main(String[] args) { Account account = new Account(100,"后备能源"); DrawMoney me = new DrawMoney(account,50,"我自己"); DrawMoney myWife = new DrawMoney(account,100,"我的妻子"); me.start(); myWife.start(); }}class Account{ int money; // 余额 String name; // 账户名 public Account(int money, String name) { this.money = money; this.name = name; }}// 银行class DrawMoney extends Thread{ Account account; // 账户 int drawMoney; //取多少钱 int nowMoney; // 现在’手‘里的余额 定义之后默认为0 public DrawMoney(Account account, int drawMoney, String name) { super(name); // 线程名字 this.account = account; this.drawMoney = drawMoney; } // 取钱 @Override public void run() { synchronized (account){ // 判断有没有钱 if (account.money-drawMoney<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } try { Thread.sleep(1000); // 等待另一个运行到这 } catch (InterruptedException e) { e.printStackTrace(); } account.money = account.money -drawMoney; // 账户余额 = 余额 - 我取的钱 nowMoney =nowMoney +drawMoney; // 你拥有的钱 System.out.println(this.getName()+"取钱之后余额为"+account.money); //this.getName() 等价 Thread.currentThread().getName() 因为继承了Thread类 System.out.println(this.getName()+"手里的钱:"+nowMoney); } }}
// 线程安全的集合public class ListThread { public static void main(String[] args) throws InterruptedException { List<String> list=new ArrayList<String>(); 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()); }}
JUC_CopyOnWriteArrayList
// 线程安全的集合public class TestJUC { public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(3000); // 休眠主线程,保证这一万个线程全部结束 System.out.println(list.size()); }}
死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行;
- 导致两个或者多个线程都在等待对方释放运行所必需资源而都停止执行(导致僵持的情况)的情形就是死锁
- 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
/*如果两个锁嵌套就会造成死锁,现在这种情况是不会死锁 */public class DeadLock { public static void main(String[] args) { MakeUpThread g1 = new MakeUpThread("小红"); MakeUpThread g2 = new MakeUpThread("小兰"); g1.start(); g2.start(); }}class Mirror{} // 镜子class LipsStick{} // 口红class MakeUpThread extends Thread{ // static保证资源都只有一个 static Mirror mirror=new Mirror(); static LipsStick lipsStick=new LipsStick(); MakeUpThread(String name){ super(name); } @Override public void run() { if (this.getName()=="小红"){ // this.getName().equals("小红") synchronized (mirror){ System.out.println(this.getName()+"获得了镜子的锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lipsStick){ System.out.println(this.getName()+"获得了口红的锁"); } }else{ synchronized (lipsStick){ System.out.println(this.getName()+"获得了口红的锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (mirror){ System.out.println(this.getName()+"获得了镜子的锁"); } } }}// 实现接口的版本public class DeadLock { public static void main(String[] args) { MakeUpThread g = new MakeUpThread(); new Thread(g,"小红").start(); new Thread(g,"小兰").start(); }}class Mirror{} // 镜子class LipsStick{} // 口红class MakeUpThread implements Runnable{ Mirror mirror=new Mirror(); LipsStick lipsStick=new LipsStick(); @Override public void run() { if (Thread.currentThread().getName().equals("小红")){ synchronized (mirror){ System.out.println(Thread.currentThread().getName()+"获得了镜子的锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lipsStick){ System.out.println(Thread.currentThread().getName()+"获得了口红的锁"); } }else{ synchronized (lipsStick){ System.out.println(Thread.currentThread().getName()+"获得了口红的锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (mirror){ System.out.println(Thread.currentThread().getName()+"获得了镜子的锁"); } } }}
-
产生死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
-
只要想办法破坏其中一个或多个就能避免死锁发生
Lock锁
- 从JDK5.0 开始, Java 提供了更强大的线程同步机制 ― 通过显式定义同步锁对象来实现同步,同步锁使用 Lock 对象充当
- java.util.concurrent.Iocks.Lock 接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得 Lock 对象 -
- ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
public class TestLock { public static void main(String[] args) { buyTickets buyTicket = new buyTickets(); new Thread(buyTicket).start(); new Thread(buyTicket).start(); new Thread(buyTicket).start(); } }class buyTickets extends Thread{ private int TicketNums=10; private final ReentrantLock lock=new ReentrantLock(); @Override public void run() { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } try { // 休眠加在这也可以 lock.lock(); // 锁了之后的休眠没有意义,因为休眠不会释放锁 if (TicketNums<=0) { break; } System.out.println(Thread.currentThread().getName() + "买到了第" + TicketNums-- + "张票"); }finally { lock.unlock(); } } }}
-
Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized 是隐式锁,出了作用域自动释放
-
Lock 只有代码块锁, synchronized 有代码块锁和方法锁
-
使用 Lock 锁, JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序: Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
线程通信
- 应用场景:生产者与消费者问题
- 生产者 ——> { 数据缓存区 } ——> 消费者
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待.直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待.直到仓库中再次放入产品为止
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
-
对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
-
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
-
在生产者消费者问题中,仅有 synchronized 是不够的
- synchronized 可阻止并发更新同一个共享资源,实现了同步
- synchronized 不能用来实现不同线程之间的消息传递(通信)
方法(均是Object类的方法,只能在同步方法或同步代码块中使用,否则会抛出异常) | 作用 |
---|---|
wait() | 表示线程一直等待,直到其它线程通知,会释放锁 |
wait(long time) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
解决方式1-管程法
并发协作模型“生产者/消费者模式”---->管程法
-
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
-
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
-
缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区“
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
public class TestPC { public static void main(String[] args) { Container container = new Container(); new Producer(container).start(); new Consumer(container).start(); }}// 产品-鸡class Chicken{ int id; // 鸡的编号 public Chicken(int id) { this.id = id; }}// 生产者class Producer extends Thread{ Container container; public Producer(Container container) { this.container = container; } @Override public void run() { for (int i = 1; i <= 100; i++) { container.push(new Chicken(i)); } }}// 消费者class Consumer extends Thread{ Container container; public Consumer(Container container) { this.container = container; } @Override public void run() { for (int i = 1; i <= 100; i++) { container.pop(); } }}// 缓冲区class Container{ Chicken[] chickens=new Chicken[11]; // 数组下标是 0 ~ 10 ,使用 0 ~ 9作为缓冲区 int count=0; // count此处作为指针,count指向的永远为 null public synchronized void push(Chicken chicken) { while (count==10){ try { this.wait(); // while防止虚假唤醒 } catch (InterruptedException e) { e.printStackTrace(); } } chickens[count++]=chicken; System.out.println("生产者生产了"+chicken.id+"号鸡"); this.notifyAll(); } public synchronized void pop(){ while (count==0){ try { this.wait();// while防止虚假唤醒 } catch (InterruptedException e) { e.printStackTrace(); } } count--; Chicken c=chickens[count]; chickens[count]=null; System.out.println("消费者消费了"+c.id+"号鸡"); this.notifyAll(); }}
解决方式2-信号灯法
并发协作模型“生产者/消费者模式”---->信号灯法
- 通过标志位判断
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 < 20; i++) { if (i%2==0){ tv.perform("哔哩哔哩"); }else{ tv.perform("知乎"); } } }}// 消费者->观众class Watcher extends Thread{ TV tv; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.look(); } }}// 产品->提前录好的电视节目class TV{ boolean flag=true; String content; public synchronized void perform(String c){ if (!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了"+c); this.content=c; flag=!flag; this.notifyAll(); } public synchronized void look(){ if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看了"+content); this.notifyAll(); flag=!flag; }}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)