思维导图看天下:
并行 :指两个或多个事件在同一时刻发生(同时发生)
并发 :指两个或多个事件在同一个时间段内发生。(交替执行)
进程:是指一个内存中运行的程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程
记忆:进程的英文为Process,Process也为过程,所以进程可以大概理解为程序执行的过程。
(进程也是程序的一次执行过程,是系统运行程序的基本单位; 系统运行一个程序即是一个进程从创建、运行到消亡的过程)
线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。【java默认有两个线程:main、GC】
进程与线程的区别:
推荐使用Runnable接口的方式,因为Java是单继承的,所以使用Thread有OPP单继承局限性
线程类
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例
每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码
Java使用线程执行体来代表这段程序流。
自定义线程类:
主函数:
public static void main(String[] args) { MyThread myThread = new MyThread("MyThread"); myThread.start(); for (int i = 0;i<1000;i++){ System.out.println("main"+i); } }
执行结果:
过程:程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建
随着调用Mt类的对象的start方法,另外一个新的线程也启动了 ,这样,整个应用就在多线程下运行。
运行时序图:
内存结构:
可以使用Thread类中的方法getName,
String getName() 返回该线程的名称。
可以先获取当前正在执行的线程,再调用getName方法获取线程名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用
//1.可以使用Thread类中的方法getName String name = getName(); System.out.println(name);//创建时, 指定了名称,获取的就是指定的名称 //如果没有指定名称,获取的就是Thread-0 //2.可以先获取当前正在执行的线程 Thread currentThread = Thread.currentThread(); System.out.println(currentThread);//Thread[Thread-0,5,main] String name2 = currentThread.getName(); System.out.println(name2);//Thread-0
MyThread myThread = new MyThread(); myThread.setName("myThreadName"); myThread.start();
public class MyThread extends Thread{ //定义指定线程名称的构造方法 public MyThread(String name) { super(name); }
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)睡醒了,继续执行
/*程序在执行第二秒时, 会暂停2秒,2秒后,继续执行后面程序*/ for (int i = 1; i <=60; i++) { System.out.println(i); /*让程序睡眠1秒钟 1秒=1000毫秒*/ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }
只针对有参且参是Runnable类的构造方法:public Thread(Runnable target)
由于Thread和target顶层都是Runnable接口,所以Thread是使用了静态代理的方式代理参数target。
优势:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享
1.创建一个RunnableImpl类实现Runnable接口
2.重写Runnable接口中的run方法,设置线程任务
3.创建Runnable接口的实现类RunnableImpl的对象t
4.创建Thread类对象,构造方法中传递Runnable接口的实现类RunnableImpl的对象t
5.调用Thread类中的start方法,开启新的线程,执行run方法
示例:
//实现Runnable接口 public class RunnableImpl implements Runnable{ //2.重写Runnable接口中的run方法,设置线程任务 @Override public void run() { //新线程执行的代码 for (int i = 0; i <20; i++) { System.out.println(Thread.currentThread().getName()+"===>"+i); } } } public static void main(String[] args) { //3.创建Runnable接口的实现类对象 RunnableImpl r = new RunnableImpl(); //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 Thread t = new Thread(r);//打印20次i //5.调用Thread类中的start方法,开启新的线程,执行run方法 t.start(); //【一般16-18行简写为:new Thread(r,"线程名").start();】 //主线程开启新线程之后继续执行的代码 for (int i = 0; i <20; i++) { System.out.println(Thread.currentThread().getName()+"===>"+i); } }
十分重要,但本篇只简单介绍了一下,请去看下一篇JUC
代码:
public class MyCallableImpl implements Callable<Boolean> { @Override public Boolean call() throws Exception { PictureCatch t = new PictureCatch(); t.test(url,name); System.out.println("下载了文件名:"+name); return true; } String url; //网址 String name; //保存的文件名 MyCallableImpl(String url,String name){ this.url=url; this.name=name; } public static void main(String[] args) { MyCallableImpl t1 = new MyCallableImpl("https://img0.baidu.com/it/u=1151663768,725447312&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500","t4"); MyCallableImpl t2 = new MyCallableImpl("https://img0.baidu.com/it/u=1648512719,1593015989&fm=253&fmt=auto&app=120&f=JPEG?w=891&h=500","t5"); MyCallableImpl t3 = new MyCallableImpl("https://img2.baidu.com/it/u=863703859,746061395&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","t6"); ExecutorService ser = Executors.newFixedThreadPool(3); Future<Boolean> result1 = ser.submit(t1); Future<Boolean> result2 = ser.submit(t2); Future<Boolean> result3 = ser.submit(t3); try { boolean r1 = result1.get(); boolean r2 = result2.get(); boolean r3 = result3.get(); System.out.println(r1); System.out.println(r2); System.out.println(r3); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } ser.shutdownNow(); } class PictureCatch{ void test(String url, String name){ try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("获取文件出错!"); } } } }
这里就不讲了,放在了JUC并发编程那篇里详细讲解了
使用 Timer 的方式如下:
public class MyTimer { public static void main(String[] args) { timer(); } /** * 指定时间 time 执行 schedule(TimerTask task, Date time) */ public static void timer() { Timer timer = new Timer(); // 设定指定的时间time,此处为2000毫秒 timer.schedule(new TimerTask() { public void run() { System.out.println("执行定时任务"); } }, 2000); } }
作用
把子类继承父类,重写父类的方法,创建子类对象,合成一步完成
把实现类实现接口,重写接口库的方法,创建实现类对象,合成一步完成
最终得要子类对象或实现类对象
格式
new 父类/接口(){ 重写父类/接口中的方法 };
public static void main(String[] args) { new Thread(){ //new 没有名称的类 继承Thread //重写run方法,设置线程任务 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"==>"+i); } } }.start(); }
new Thread(new Runnable() { //new没有名称的类实现了Runnable接口 //重写run方法,设置线程任务 @Override public void run() { //实现接口当中run方法 for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }).start();
线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
以下举例是使用一个标志位falg来终止变量
public class ThreadStopDemo implements Runnable { private boolean flag = true; @Override public void run() { int i = 1; while (flag) { System.out.println("run..."+(i++)); } } //设置一个专门修改标志位的方法来停止线程 public void stop(){ flag = false; } public static void main(String[] args) { ThreadStopDemo demo = new ThreadStopDemo(); new Thread(demo).start(); for (int i = 1; i <= 500; i++) { System.out.println("main..."+i); if (i == 300) { demo.stop(); System.out.println("线程该停止了"); } } } }
模拟倒计时+打印当前系统时间
public static void main(String[] args) { //模拟倒计时 System.out.println("开始倒计时"); int num = 10; while (true) { System.out.println(num--); Thread.sleep(1000); if (num <= 0) { break; } } System.out.println("开始报时"); //打印当前系统时间 int count = 10; DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); while (true) { System.out.println(LocalDateTime.now().format(dateTimeFormatter)); Thread.sleep(1000); count--; if (count <= 0) { break; } } }
礼让不一定成功,因为cpu重新调度,可能会再次选到之前的线程
代码演示:结果可能有三种:aabb,abab,abba
public class ThreadYieldDemo implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始!"); Thread.yield(); System.out.println(Thread.currentThread().getName()+"结束!"); } public static void main(String[] args) { ThreadYieldDemo demo = new ThreadYieldDemo(); new Thread(demo, "a").start(); new Thread(demo, "b").start(); } }
代码演示:结果是正常排队执行到200后,得等强制执行走完200次后,才会继续执行正常排队201...
//插队线程 public class 线程强制执行 { public static void main(String[] args) { forceThread forceThread = new forceThread(); Thread thread = new Thread(forceThread, "强制线程"); for (int i = 0; i < 500; i++) { System.out.println("正常排队:"+i); if (i==200){ thread.start(); thread.join(); } } } } class forceThread implements Runnable{ @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("强制执行——"+i); } } }
六种线程状态看前面写的
代码演示:
public static void main(String[] args) { Thread thread = new Thread(()->{ for (int i = 0; i < 20; i++) { Thread.sleep(500); } },"线程"); System.out.println("线程start前的状态:"+thread.getState()); thread.start(); System.out.println("线程start后的状态:"+thread.getState()); while (!Thread.State.TERMINATED.equals(thread.getState())){ System.out.println("线程terminated之前的状态:"+thread.getState()); Thread.sleep(500); } System.out.println("线程的状态:"+thread.getState()); }
结果:
线程start前的状态:NEW 线程start后的状态:RUNNABLE 线程terminated之前的状态:RUNNABLE 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:RUNNABLE 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:RUNNABLE 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:TIMED_WAITING 线程terminated之前的状态:RUNNABLE 线程terminated之前的状态:RUNNABLE 线程的状态:TERMINATED
源码中所有线程的优先级默认为5
优先级的设置建议在start()调度前
优先级低只是意味着获取调度的概率低,并不是优先级低就不会被调用了,这都是看cup的调度
代码演示:
public class 线程优先级 { public static void main(String[] args) { //打印主线程的优先级(也是所有线程默认的优先级) System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority,"线程1"); Thread t2 = new Thread(myPriority,"线程2"); Thread t3 = new Thread(myPriority,"线程3"); Thread t4 = new Thread(myPriority,"线程4"); Thread t5 = new Thread(myPriority,"线程5"); Thread t6 = new Thread(myPriority,"线程6"); t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority(7); t3.setPriority(Thread.MAX_PRIORITY); t4.setPriority(4); t5.setPriority(9); t6.setPriority(2); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); } }
线程分为用户线程和守护线程(daemon)
虚拟机需要确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕(如,后台记录操作日志,监控内存,垃圾回收等)
也就是说可以做到主线程结束了,但守护线程还没结束
代码演示:
public class 守护线程 { public static void main(String[] args) { God god = new God(); You you = new You(); Thread godThread = new Thread(god, "守护线程"); Thread youThread = new Thread(you, "普通线程"); godThread.setDaemon(true); //设置为守护线程 godThread.start(); youThread.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 < 30; i++) { System.out.println("我是普通人,只有三万多天的日子"); } System.out.println("======一个月完美的结束了======"); } }
用于存储一个线程专有的值【对象方法】
ThreadLocal类,来创建工作内存中的变量,它将我们的变量值存储在内部(只能存储一个变量),不同的变量访问到ThreadLocal对象时,都只能获取到自己线程所属的变量。【每个线程的工作内存空间不同,所以线程之间相互独立,互不相关】
public static void main(String[] args) throws InterruptedException { ThreadLocal<String> local = new ThreadLocal<>(); //注意这是一个泛型类,存储类型为我们要存放的变量类型 Thread t1 = new Thread(() -> { local.set("lbwnb"); //将变量的值给予ThreadLocal System.out.println("线程1变量值已设定!"); try { Thread.sleep(2000); //间隔2秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1读取变量值:"); System.out.println(local.get()); //尝试获取ThreadLocal中存放的变量 }); Thread t2 = new Thread(() -> { local.set("yyds"); //将变量的值给予ThreadLocal System.out.println("线程2变量值已设定!"); }); t1.start(); Thread.sleep(1000); //间隔1秒 t2.start(); } //结果:lbwnb。就算t2也设置了值,但不影响t1的值
拓展:子类线程也获得不了父类线程设置的值,但可以通过用InheritableThreadLocal
方法来解决这个问题。(在InheritableThreadLocal存放的内容,会自动向子线程传递)
public static void main(String[] args) { ThreadLocal<String> local = new InheritableThreadLocal<>(); Thread t = new Thread(() -> { local.set("lbwnb"); new Thread(() -> { System.out.println(local.get()); }).start(); }); t.start(); }
- 等待wait和唤醒notify、notifyall都需要在同步代码内(锁方法 or 锁代码块)
- 等待和唤醒只能由锁对象调用。(锁代码块的锁对象容易看出,锁方法的锁对象一般是this或方法所在的类)
public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.会继续执行wait()方法之后的代码
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
示例:
顾客与老板线程:
创建一个顾客线程(消息者):告诉老板要吃什么 调用wait方法,放弃cpu的执行,进入wating状态(无限等待)
创建一个老板线程(生产者):花5秒做好 做好后 调用notify方法 唤醒顾客 开吃
注意
顾客线程
老板线程
Object obj = new Object(); new Thread(){ @Override public void run() { synchronized (obj){ System.out.println("告诉老板要吃饺子"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("做好===开始吃饺子"); } } }.start(); new Thread(){ @Override public void run() { synchronized (obj){ try { Thread.sleep(3000); System.out.println("老板饺子已经做好"); obj.notify();//唤醒当前锁对象上的等待线程 } catch (InterruptedException e) { e.printStackTrace(); } } } }.start();
进入计时等待状态的两种方式
使用sleep(long m)方法,在毫秒值结束后,线程睡醒,进入Runnable/Blocked状态(抱着锁睡觉,不放锁)
使用wait(long m)方法wait方法如果在毫秒值结束之后,还没有被唤醒,就会自动醒来,进入Runnable/Blocked状态(等待的时候会释放锁)
两种唤醒的方法
public void notify()
随机唤醒1个
public void notifyall()
唤醒锁对象上所有等待的线程.
多个线程操作同一个资源
并发:同一个对象被多个线程同时操作
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一个等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程时候完毕,下一个线程再使用。
线程同步
多线程访问了共享的数据,就会产生线程的安全
举例:
解决措施:可锁代码块可锁方法,后面的解决方案是以买票问题为例
代码演示:
//买票问题 public class UnsafeTicket implements Runnable{ private static Boolean falg = true; private int ticket =10;//票数 public static void main(String[] args) { UnsafeTicket demo1 = new UnsafeTicket(); new Thread(demo1,"小红").start(); new Thread(demo1,"小明").start(); new Thread(demo1,"黄牛").start(); } @Override public void run() { while (falg){ buy(); } } //买票 public void buy(){ //没票了就停止线程 if (ticket<=0) { falg = false; return; } //还有票就继续买 System.out.println(Thread.currentThread().getName()+"买到了第"+ticket+"张票"); ticket--; try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } }
结果:会出现三个人同时去了第5张票、也可能会出现有人取0 -1张票
解决措施:使用代码块锁account
代码演示:
//银行取钱问题 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100, "结婚基金"); Bank drawMoney1 = new Bank(account, 50, "新一"); Bank drawMoney2 = new Bank(account, 100, "小兰"); drawMoney2.start(); drawMoney1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("卡内余额:" + account.money); } } //账户 class Account{ int money;//账户内的钱 String name;//卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行 class Bank extends Thread{ Account account;//操纵的账户 int drawingMoney;//取了多少钱 public Bank(Account account, int drawingMoney,String who) { super(who); this.account = account; this.drawingMoney = drawingMoney; } @Override public void run() { drawing(); } //取钱 private void drawing() { if (this.account.money - drawingMoney < 0) { System.out.println("余额不足," + Thread.currentThread().getName() + "取钱失败"); return; } //sleep可以提高问题的发生的概率! try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } this.account.money = this.account.money - drawingMoney; System.out.println(Thread.currentThread().getName() + "取了" + drawingMoney); } }
结果:
解决措施:锁代码块
代码演示:
public class UnsafeArrayList { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ int x = 10; list.add(x++); },"list线程不安全").start(); } System.out.println(list.size()); } }
结果:9997,少了三个,是因为前面插入数据的时候有三个下标被重复赋值,导致有三次赋值被覆盖了。
锁类模板 和 锁用该类模板创建出来的对象 两者之间互不影响!
同步代码块synchronized的格式:
synchronized(锁对象obj){ 出现安全问题的代码(访问了共享数据的代码) }
注意
1.锁对象可以是任意对象 new Person new Student ...(一般是锁变化的对象,需要增删改的对象)
2.必须保证多个线程使用的是同一个锁对象
3.锁对象的作用:把{}中代码锁住,只让一个线程进去执行
适用于使用同一个Runnable对象创建多个线程的情况,不适用于多个Runnable对象分别创建多个线程的情况
作用范围是对象实例,不可跨对象,所以多个线程不同对象实例访问此方法,互不影响,无法产生互斥。由于本题抢票中是多个线程使用同一个Runnable对象,所以得到的锁是同一个对象产生的obj,可以实现线程隔离。但银行例子中是多个线程分别使用不同的Runnable对象,所以使用锁实例对象是没用的。
示例
public class TicketRunnableImpl implements Runnable { //定义共享的票源 private int ticket = 100; private Object obj = new Object(); //锁对象 //线程任务:卖票 @Override public void run() { synchronized (obj){ while (ticket > 0) { /*为了提高线程安全问题出现的几率 让线程睡眠10毫秒,放弃cpu的执行权*/ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //卖票操作,ticket-- System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); ticket--; } } } public static void main(String[] args) { UnsafeTicket demo1 = new UnsafeTicket(); new Thread(demo1,"小红").start(); new Thread(demo1,"小明").start(); new Thread(demo1,"黄牛").start(); } }
总结:
同步监视器的执行过程(同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class)
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问,处于阻塞状态,一直等待
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
适用于使用同一个Runnable对象创建多个线程的情况,也适用于多个Runnable对象分别创建多个线程的情况
虽然是通过对象访问的此方法,但是加锁的代码块是类级别的跨对象的,所以锁的范围是针对类,多个线程访问互斥。
public class SynchronizedDemo { // 代码块锁(类):锁的应用对象是User类,可以称之为类锁 public void method2() { synchronized (User.class) { // TODO 业务逻辑 } } public static void main(String[] args) { SynchronizedDemo obj1 = new SynchronizedDemo(); SynchronizedDemo obj2 = new SynchronizedDemo(); new Thread(() ->{ obj1.method2(); //代码块锁,后面是类,多线程访问互斥 }).start(); new Thread(() ->{ obj2.method2(); }).start(); } }
锁的是this,也就是主方法里调用该方法的对象
同步方法解决线程安全的格式:
修饰符 synchronized 返回值类型 方法名(参数列表){ 出现安全问题的代码(访问了共享数据的代码) }
使用步骤
1.创建一个方法,方法的修饰符添加上synchronized
2.把访问了共享数据的代码放入到方法中
3.调用同步方法
适用于使用同一个Runnable对象创建多个线程的情况,不适用于多个Runnable对象分别创建多个线程的情况
普通方法作用范围是对象实例,不可跨对象,所以多个线程不同对象实例访问此方法,互不影响,无法产生互斥。由于本题抢票中是多个线程使用同一个Runnable对象,所以得到的锁是同一个类对象this,可以实现线程隔离。但银行例子中是多个线程分别使用不同的Runnable对象最后锁的this也是不同类对象的this,所以使用锁普通方法是没用的。
示例
@Override public void run() { ticketMethods(); } public synchronized void ticketMethods(){ while (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //卖票操作,ticket-- System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); ticket--; } }
锁对象是谁???
锁对象为this
public void ticketMethods(){ synchronized(this){ while (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //卖票操作,ticket-- System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); ticket--; } } }
适用于使用同一个Runnable对象创建多个线程的情况,也适用于多个Runnable对象分别创建多个线程的情况
静态方法是通过类访问,是类级别的跨对象的,所以锁的范围是针对类,多个线程访问互斥。
示例:变化的量记得也要static
public class TicketRunnableImpl implements Runnable { //定义共享的票源 private static int ticket = 100; private Object obj = new Object(); //锁对象 //线程任务:卖票 @Override public void run() { ticketMethods(); } public static synchronized void ticketMethods(){ while (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //卖票操作,ticket-- System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); ticket--; } } }
锁对象是谁???
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)
概述
Lock接口中的方法
void lock() 获取锁。 void unlock() 释放锁。//如果有try/catch的话,一般unlock是放在finally里
使用步骤
1.在成员位置创建一个Lock接口的实现类对象ReentrantLock
2.在可能会出现安全问题的代码前,调用lock方法获取锁对象
3.在可能会出现安全问题的代码后,调用unlock方法释放锁对象
示例
public class TicketRunnableImpl implements Runnable { //定义共享的票源 private int ticket = 100; //1.在成员位置创建一个Lock接口的实现类对象ReentrantLock Lock l = new ReentrantLock(); //线程任务:卖票 @Override public void run() { while (true) { l.lock(); if (ticket > 0){ try { Thread.sleep(10); //卖票操作,ticket-- System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); }finally { //3.在可能会出现安全问题的代码后,调用unlock方法释放锁对象 l.unlock(); //无论程序是否异常,都会把锁对象释放,节约内存提高程序的效率 } } } } }
8锁现象:
1)标准情况下,一个对象 两个同步方法 第一个线程先拿到锁 谁先执行
2)一个对象 两个同步方法 第一个线程先拿到锁 第一个方法延迟4S 谁先执行
3)一个对象 一个同步方法一个普通方法 第一个线程先拿到锁 谁先执行
4)两个对象 两个同步方法 第一个线程先拿到锁 第一个方法延迟4S 谁先执行
5)一个对象 两个静态同步方法 第一个线程先拿到锁 第一个方法延迟4S 谁先执行
6)两个对象 两个静态同步方法 第一个线程先拿到锁 第一个方法延迟4S 谁先执行
7)一个对象 一个静态同步方法一个普通同步方法 第一个线程先拿到锁 第一个方法延迟4S 谁先执行
8)两个个对象 一个静态同步方法一个普通同步方法 第一个线程先拿到锁 第一个方法延迟4S 谁先执行
package com.ambition; import java.util.concurrent.TimeUnit; /** * 同一个对象 两个线程 两个同步方法 谁先执行? **/ public class Question1 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> phone.sendMsg()).start(); // 延迟的目的是控制哪个线程先拿到锁 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> phone.call()).start(); } } class Phone { // synchronized 锁的对象是方法的调用者 // 两个方法用的是同一个锁 谁先拿到谁先执行 public synchronized void sendMsg() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call() { System.out.println("打电话"); } }
小结:
- 对于普通同步方法,锁是当前new实例对象。
- 对于static 静态同步方法,锁是当前类模板的Class对象。
1.线程通信
2.线程通讯-分析
生产者——缓存区——消费者
并发协作模型”生产者/消费者模式“-->管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
代码:
//餐厅模式:生产者————厨师、消费者————顾客 public class 管程法 { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Cousumer(container).start(); } } /** * 生产者 */ class Productor extends Thread { /** * 缓冲区 */ private SynContainer container; public Productor(SynContainer container) { this.container = container; } @Override public void run() { for (int i = 1; i <= 100; i++) { container.push(new Chicken(i)); // System.out.println("生产了" + i + "只鸡"); } } } /** * 消费者 */ class Cousumer extends Thread { /** * 缓冲区 */ private SynContainer container; public Cousumer(SynContainer container) { this.container = container; } @Override public void run() { for (int i = 1; i <= 100; i++) { Chicken pop = container.pop(); // System.out.println("消费了" + pop.getId() + "只鸡"); } } } /** * 鸡(食物) */ class Chicken { /** * 鸡的编号 */ private int id; public Chicken(int id) { this.id = id; } public int getId() { return id; } } /** * 缓冲区 */ class SynContainer { /** * 缓冲区的容器大小(十只鸡) */ private Chicken[] chickens = new Chicken[10]; /** * 计数器 */ private int count = 0; /** * 生产者往容器中放入产品 */ public synchronized void push(Chicken chicken) { //如果容器满了,生产者就需要等待消费者消费 if (count == 10) { //生产者开始等待消费者消费 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,生产者往容器中继续放入产品 chickens[count] = chicken; count++; System.out.println("生产了" + chicken.getId() + "只鸡"); //生产者通知消费者消费 this.notifyAll(); //模拟生产者要休息一下下 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 消费者消费容器中的产品 */ public synchronized Chicken pop() { //消费者判断容器中是否有产品 if (count == 0) { //消费者等待生产者生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //消费者开始消费容器中的产品 count--; Chicken chicken = chickens[count]; System.out.println("消费了" + chicken.getId() + "只鸡"); //消费者通知生产者继续生产 this.notifyAll(); return chicken; } }
flag标志位来告诉消费者继续/停止消费,告诉生产者继续/停止生产
并发协作模型”生产者/消费者模式“-->信号灯法
public class 信号灯法 { public static void main(String[] args) { TV tv = new TV(); new Actor(tv).start(); new Audience(tv).start(); } } //演员 class Actor extends Thread{ TV tv = new TV(); public Actor(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 1; i <= 20; i++) { if (i % 2 == 0) { tv.push("快乐大本营"); } else { tv.push("抖音"); } } } } //听众 class Audience extends Thread{ TV tv = new TV(); public Audience(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 1; i <= 20; i++) { tv.pop(); } } } //电视节目 class TV { String name;//节目名称 boolean flag=true; //标志位 T生产 F观看 //生产节目 public synchronized void push(String name){ //判断要不要生产 //不生产就等待 if (!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //生产 this.name=name; flag=!flag; System.out.println("我生产了"+name); //生产完就唤醒观众 this.notifyAll(); } //消费节目 public synchronized void pop(){ //判断有没有节目看 //没有节目看就等待 if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //有节目就消费 flag=!flag; System.out.println("我看完了"+name); //看完就让演员再演 this.notifyAll(); } }