线程简介
多任务:边吃饭边玩手机,同时做多件事(但是实际上是分时进行的,大脑分时处理,时间交替很快)
多线程:多车道,多条线路同时执行任务
普通方法调用和多线程
直接调用run和调用start函数的不同,直接调用run函数无法实现多线程
在操作系统中运行的程序就是进程(qq,播放器,游戏,IDE),播放视频时有声音,图像,字幕,这些就是由不同的线程控制
进程与线程
说起进程,就不得不说下程序,程序是数据和指令的有序集合,其本身没有任何运行的含义,是一个静态的概念
而进程是执行程序的一次执行过程,它是一个动态的概念,进程是系统分配资源的单位(程序跑起来才叫一个进程)
通常一个进程可以包含若干个线程,一个进程至少包含一个线程,线程是CPU调度和执行的单位
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换得很快,所以就有同时执行的错局
核心概念
线程就是独立的执行路径
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc(垃圾回收)线程
main()称之为主线程,为系统的入口,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如CPU调度时间,并发控制开销(让线程排队执行)
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程实现
线程创建
Thread Runnable Callable
main函数是自己写的叫用户线程
gc线程是JVM给的,叫守护线程
线程创建的3种方式
1 继承Thread类(重点)
不建议使用:为了避免OOP单继承局限性
Thread类本身实现了Runnable接口
①自定义线程类继承Thread类
②重写run方法,编写程序执行体
③创建线程对象,调用start()方法启动线程
总结:线程开启不一定立即执行,由CPU调度执行,直接调用run方法相当于调用普通方法,不会创建新的线程
/** *@className: TestThread *@description: 创建线程方式1:继承Thread类,重写run()方法,调用start开启线程 * 总结: 线程开启不一定立即执行,由cpu调度执行 *@author: jj.liu *@create: 2021-10-12 09:53 */ public class TestThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run()方法线程体正在执行-----------------"+i); } } public static void main(String[] args) { TestThread1 thread = new TestThread1(); //调用start()方法开启线程:多条执行路径,主线程和子线程并行交替执行 thread.start(); //调用run()方法开启线程:主线程先执行run方法再执行main() //thread.run(); for (int i = 0; i < 1000; i++) { System.out.println("main()方法主线程正在执行。。。。。。。。。。。。。"+i); } } }
案例:多线程下载图片
需要用到commons-io2.6这个jar包,可以自己去百度下载
然后把包拷贝到我们的工程下面,右键 Add as library
因为我们需要用到commons-io包里面的FileUtils函数
/** *@className: TestThread2 *@description: 利用多线程实现多个网络图片同时下载 *@author: jj.liu *@create: 2021-10-12 10:40 */ public class TestThread2 extends Thread{ String url; String name; public TestThread2(String url,String name){ this.url = url; this.name = name; } @Override public void run() { WebDownLoader downloader = new WebDownLoader(); downloader.donwloader(url,name); } public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://tenfei02.cfp.cn/creative/vcg/800/new/VCG211164262326.jpg","1.jpg"); TestThread2 t2 = new TestThread2("https://t7.baidu.com/it/u=1392863136,806273060&fm=193&f=GIF","2.jpg"); TestThread2 t3 = new TestThread2("https://t7.baidu.com/it/u=2301872827,3431176467&fm=193&f=GIF","3.jpg"); t1.start(); t2.start(); t3.start(); } }
package com.agentliu.demo01; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; /** *@className: WebDownLoader *@description: 图片下载器 *@author: jj.liu *@create: 2021-10-12 17:55 */ public class WebDownLoader { public void donwloader(String url,String name) { try { FileUtils.copyURLToFile(new URL(url),new File(name)); System.out.println("下载图片成功,下载图片为:"+name); } catch (IOException e) { e.printStackTrace(); System.out.println("下载器下载图片失败"); } } }
结果 如图。
2、实现Runnable接口
java是单继承,推荐使用Runnable接口,方便同一个独享被多个线程使用
避免了单继承的局限性:即在Java中一个类只能使用extends继承一个父类.,如果继承多个父类,而父类有同名方法时就不知道调用哪一个方法了,另外会是两个类的耦合性增加,如果父类有改动时会直接影响子类
① 定义MyRunnable类实现Runnabke接口
②实现Run()方法,编写程序执行体
③创建线程对象,调用start()方法启动线程
/** *@className: TestThread3 *@description: 实现Runable接口 *@author: jj.liu *@create: 2021-10-12 11:41 */ public class TestThread3 implements Runnable{ @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run()方法线程体正在执行-----------------"+i); } } public static void main(String[] args) { TestThread3 thread3 = new TestThread3(); Thread thread = new Thread(thread3); thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("main()方法主线程正在执行。。。。。。。。。。。。。"+i); } } }
初识并发问题
多个线程操作同一个资源的情况下出现不同的线程抢到同一张票,线程不安全,数据紊乱
/** *@className: TestThread4 *@description: 多线程并发抢票问题 *@author: jj.liu *@create: 2021-10-12 14:29 */ public class TestThread4 implements Runnable{ private int ticketNum = 10 ; @Override public void run() { while (true){ if(ticketNum <= 1){ break; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum-- +"张票"); } } public static void main(String[] args) { TestThread4 thread1 = new TestThread4(); new Thread(thread1,"小明").start(); new Thread(thread1,"老王").start(); new Thread(thread1,"黄牛").start(); } }
结果如果:
案例:龟兔赛跑:
/** *@className: Race *@description: 龟兔赛跑,模拟兔子乌龟赛跑,兔子中途休息,乌龟赢得比赛 *@author: jj.liu *@create: 2021-10-12 16:58 */ public class Race implements Runnable{ public String winer; @Override public void run() { for (int i = 1; i <= 100; i++) { //模拟兔子中途休息 if(Thread.currentThread().getName().equals("兔子")&& i%10==0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } boolean flag = stopGame(i); //如果比赛结束,停止循环 if(flag){ break; } System.out.println(Thread.currentThread().getName()+"跑了"+i+"米"); } } //判断是否完成比赛 public boolean stopGame(int i){ //判断是否有胜利者 if(winer != null){ return true; }{ if ( i == 100) { winer = Thread.currentThread().getName(); System.out.println("winer is:" + winer); return true; } } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); }
}
结果如图:兔子在第10秒休息,乌龟赢得比赛。
3、 实现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();
/** * @className: TestCallableThread * @description: 实现callable接口 * @author: jj.liu * @create: 2021-10-12 17:45 */ public class TestCallableThread implements Callable { String url; String name; public TestCallableThread(String url, String name) { this.url = url; this.name = name; } @Override public Boolean call() { WebDownLoader downloader = new WebDownLoader(); downloader.donwloader(url, name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallableThread t1 = new TestCallableThread("https://tenfei02.cfp.cn/creative/vcg/800/new/VCG211164262326.jpg", "1.jpg"); TestCallableThread t2 = new TestCallableThread("https://t7.baidu.com/it/u=1392863136,806273060&fm=193&f=GIF", "2.jpg"); TestCallableThread t3 = new TestCallableThread("https://t7.baidu.com/it/u=2301872827,3431176467&fm=193&f=GIF", "3.jpg"); //创建线程池,放3个线程 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行线程 Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); //获取执行结果 r1.get(); r2.get(); r3.get(); //关闭线程池 ser.shutdownNow(); } }
Lamda表达式
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
详见:https://www.cnblogs.com/ljjdyz/p/15402986.html
线程之静态代理模式
详见:https://www.cnblogs.com/ljjdyz/p/15405450.html
线程状态
五大状态
创建状态:new
就绪状态:start
运行状态:run
阻塞状态:sleep(sleep只是其中一个)
死亡状态:正常执行完
线程方法
方法 | 说明 |
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
– | – |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
不推荐使用jdk提供的stop(),destory()方法,建议使用一个标志位进行终止变量,当flag=false,则线程终止运行
public class Teststop implements Runnable { //1.线程中定义线程体使用的标识 private boolean flag = true; @Override public void run (){ / /2 .线程体使用该标识 while (flag) { systepaoit.println ( "run. . . Thread" ); } } //3.对外提供方法改变标识 public void stop(){ this.flag = false; } }
public class TestStop implements Runnable { private boolean flag=true; @Override public void run() { int i=0; while (flag){ System.out.println("run.....Thread"+(i++)); } } public void stop(){ this.flag=false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i <1000 ; i++) { System.out.println("main"+i); if (i==900){ testStop.stop(); System.out.println("run线程停止了!"); } } } }
线程休眠(sleep)
sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时(放大问题的发生性,比如多线程卖票,一票多卖),倒计时等。
每一个对象都有一个锁,sleep不会释放锁;
package MultiThread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.SimpleFormatter; //模拟倒计时 public class TestSleep implements Runnable { public static void main(String[] args) throws InterruptedException { tenDown(); //打印当前系统时间 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 tenDown() throws InterruptedException {//模拟倒计时 int num=10; while(true) { Thread.sleep(1000); System.out.println(num--); if(num<=0) { break; } } } @Override public void run() { } }
线程礼让(yield)
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功
package MultiThread; 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()+"线程结束执行"); } }
礼让成功:
礼让失败:
其实这里注释掉yield,还是会出现a先执行或者b先执行这种情况,因为是多线程嘛
线程强制执行(join)
join合并线程,待此线程执行完毕之后,在执行其他线程,其他线程阻塞,可以想象成插队
package MultiThread; public class TestJoin implements Runnable { public static void main(String[] args) throws InterruptedException { TestJoin testJoin=new TestJoin(); Thread thread= new Thread( testJoin); thread.start(); for (int i = 0; i < 50; i++) { if(i==25) { thread.join(); } System.out.println("主线程"+i); } } @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("join线程"+i); } } }
注意join是通过new Thread 对象来调用的,而sleep和yield是通过Thread直接调用
观测线程状态
/** *@className: TestStatus *@description: 测试观察线程运行状态 *@author: jj.liu *@create: 2021-10-22 11:20 */ public class TestStatus { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()-> { for (int i = 0; i < 5; i++) { try { //模拟5秒倒计时 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("////////////循环结束"); }); //线程刚被创建时得状态 Thread.State state = thread.getState(); System.out.println(state);//new //调用start方法后,线程进行运行状态 thread.start();//RUNNABLE state = thread.getState(); System.out.println(state); //只要线程不停止,输出阻塞时得状态 while (state != Thread.State.TERMINATED){ Thread.sleep(100); state = thread.getState(); System.out.println(state); } // thread.start(); //线程死亡状态以后就不能再重新再执行 } }
线程优先级
优先级高的不一定会先执行
package 多线程使用; public class Testpriority { public static void main(String[] args) { // 主线程默认优先级 System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); Mypriority mypriority = new Mypriority(); Thread th_01 = new Thread(mypriority,"二号线程"); Thread th_02 = new Thread(mypriority,"三号线程"); Thread th_03 = new Thread(mypriority,"一号"); Thread th_04 = new Thread(mypriority,"四号"); Thread th_05 = new Thread(mypriority,"五号"); // 先设置优先级 th_01.start(); th_02.setPriority(1); th_02.start(); th_03.setPriority(4); th_03.start(); th_04.setPriority(Thread.MAX_PRIORITY); th_04.start(); th_05.setPriority(7); th_05.start(); } } class Mypriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); } }
守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
Thread thread = new Thread(god);
thread.setDaemon(true);//设置守护线程 true
thread.start();
/** * @author: codeyuaiaio 守护线程 */ public class ThreadDaemon { public static void main(String[] args) { God god = new God(); You you = new You(); Thread thread = new Thread(god); //默认是false表示用户线程,正常的线程都是用户线程.... thread.setDaemon(true); //上帝守护线程启动 thread.start(); //你启动了 new Thread(you).start(); } } /** * 上帝 */ class God implements Runnable{ @Override public void run() { while(true){ System.out.println("上帝守护着你"); } } } /** * 你自己 */ class You implements Runnable{ @Override public void run() { for (int i = 0; i <= 100; i++) { System.out.println("我还开开心心的活着"+i+"岁"); } System.out.println("=====goodbye world====="); } }
线程同步机制 synchronized
并发:同一个对象被多个线程同时操作
队列+ 锁才能保证线程同步的安全性
线程不安全案例:
案例1:三人同时买票
package com.agentliu.sync; /** *@className: TestStatus *@description: 不安全的买票 *@author: jj.liu *@create: 2021-10-22 16:21 */ public class UnSafeByTicket { public static void main(String[] args) { BuyTicket t = new BuyTicket(); Thread t1 = new Thread(t,"老王"); Thread t2 = new Thread(t,"你"); Thread t3 = new Thread(t,"黄牛党"); t1.start(); t2.start(); t3.start(); } } class BuyTicket implements Runnable{ int ticketNumber = 10; Boolean flag = true; @Override public void run() { buy(); } void buy(){ while (flag){ if(ticketNumber <= 0){ flag = false; break ; } System.out.println(Thread.currentThread().getName()+"买到了第---》"+ticketNumber+"张票"); ticketNumber --; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
案例2:不安全的集合ArrayList
package com.agentliu.sync;
import java.util.ArrayList;
import java.util.List;
/**
*@className: UnSafeList
*@description: 线程不安全的集合
*@author: jj.liu
*@create: 2021-10-22 17:03
*/
public class UnSafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add("a");
}).start();
}
try {
//休息2秒,放大问题的发生性,防止for循环还没完就打印集合大小
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
案例3: 线程不安全的取钱
package com.agentliu.sync;
/**
*@className: UnSafeByBank
*@description: 线程不安全的取钱
*@author: jj.liu
*@create: 2021-10-25 14:59
*/
public class UnSafeByBank {
public static void main(String[] args) {
Accout accout = new Accout(100);
WithDraw t1 = new WithDraw(accout,100,"你女朋友");
WithDraw t2 = new WithDraw(accout,50,"你");
t1.start();
t2.start();
}
}
//账户
class Accout{
int money;
public Accout(int money){
this.money = money;
}
}
//取钱
class WithDraw extends Thread{
//账户
Accout accout;
//取钱金额
int withDrawMoney;
//余额
int nowMoney;
//取款人
String withDrawName;
public WithDraw(Accout accout,int withDrawMoney,String withDrawName){
this.accout = accout;
this.withDrawMoney = withDrawMoney;
this.withDrawName = withDrawName;
}
@Override
public void run(){
if(withDrawMoney - accout.money > 0){
System.out.println(Thread.currentThread().getName()+"钱不够取,取不了");
return;
}
// sleep放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额-你取的钱
accout.money = accout.money-withDrawMoney;
System.out.println(withDrawName+"取的钱为:"+withDrawMoney);
System.out.println(withDrawName+"账户余额为:"+accout.money);
}
}
synchronize线程锁
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
synchronize同步方法
在方法上加修饰词synchronize锁的是当前对象
同步方法的弊端性能低 因为只需要修改的变量资源才需要上锁
synchronize同步代码块
同步代码块可以锁任意指定对象
synchronized代码块同步锁的例子模拟银行操作: 需要锁的对象为账户,账户的金额在发生变化。如果将synchronized放到run()方法上,那么上锁的是this当前类。
synchronized同步方法的例子模拟买票
**synchronized 代码块同步锁的例子Arraylist **
ArrayList是线程不安全的,因为多线程在操作list的空间会同时写到同一块内存位置,然后会覆盖掉之前的内容
JUC并发下的线程安全的集合CopyOnWriteArrayList
package 多线程使用.线程安全; import java.util.concurrent.CopyOnWriteArrayList; //测试juc安全类型的集合 public class TestJuc { public static void main(String[] args) { CopyOnWriteArrayList list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 100; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
死锁问题
死锁: 多个线程互相持有对方的需要的资源 ,然后形成互相僵持
package com.agentliu.lock; /** *@className: DeadLock *@description: 死锁的形成 *@author: jj.liu *@create: 2021-10-25 17:20 */ public class DeadLock { public static void main(String[] args) { Makeup t1 = new Makeup(0,"小美"); Makeup t2 = new Makeup(1,"小丽"); t1.start(); t2.start(); } } //口红 class Lipstick{} //镜子 class Mirror{} //化妆 class Makeup extends Thread{ //用static来保证镜子和口红都只有一份 static Lipstick l = new Lipstick(); static Mirror m = new Mirror(); int choice; String girlName; Makeup(int choice,String girlName){ this.choice = choice; this.girlName = girlName; } @Override public void run(){ try { makeup();//化妆 } catch (InterruptedException e) { e.printStackTrace(); } } private void makeup() throws InterruptedException { //由于镜子和口红的资源都只有一份,他们都拿到了对方需要的资源并上锁,并且相互占用相互僵持形成死锁 if(choice == 0){ //第一个人获得口红的锁,她需要镜子 synchronized (l){ Thread.sleep(1000); System.out.println(girlName+"获得口红的锁,她需要镜子"); synchronized (m){ System.out.println(girlName+"获得镜子的锁,化妆完成"); } } }else{ //第二个人获得镜子的锁,她需要口红 synchronized (m){ Thread.sleep(2000); System.out.println(girlName+"获得镜子的锁,她需要口红"); synchronized (l){ System.out.println(girlName+"获得口红的锁,化妆完成"); } } } } }
释放掉对方需要的对象
Lock锁
在juc并发包下
//ReentrantLock :可重入锁
// 定义lock
private ReentrantLock re = new ReentrantLock();
synchronized与Lock的对比
线程通信
模拟生产者和消费者
生产者消费者模型->利用缓冲区解决:管程法
package 多线程使用.线程协作; //测试:生产者消费者模型->利用缓冲区解决:管程法 //生产者 消费者 产品 缓冲区 public class TestPc { public static void main(String[] args) { SynContainer sy = new SynContainer(); new Productor(sy).start(); new Consumer(sy).start(); } } //生产者 class Productor extends Thread{ private SynContainer sy; Productor (SynContainer sy){ this.sy=sy; } @Override public void run() { for (int i = 1; i < 100; i++) { this.sy.push(new Chicken(i)); System.out.println("正在生产第:"+i+"只鸡"); } } } //消费者 class Consumer extends Thread{ private SynContainer sy; Consumer (SynContainer sy){ this.sy=sy; } @Override public void run() { for (int i = 1; i < 100; i++) { System.out.println("正在消费第:"+this.sy.pop().id+"只鸡"); } } } //产品 class Chicken{ int id; public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer{ Chicken[] ch=new Chicken[10]; int cont=0; public synchronized void push(Chicken chicken){ if(cont==ch.length){ // 生产满了等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 生产产品 ch[cont]=chicken; cont++; // 通知消费者消费 this.notifyAll(); } public synchronized Chicken pop(){ // 没有产品了等待生产者 if(cont==0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 消费通知生产者生产 cont--; Chicken chicken = ch[cont]; this.notifyAll(); return chicken; } }
信号灯发
package 多线程使用.线程协作; public class Test_pc2 { public static void main(String[] args) { TV tv = new TV(); new Player(tv).start(); new Watcher(tv).start(); } } class Player extends Thread{ TV t; Player (TV t){ this.t=t; } @Override public void run() { for (int i = 0; i < 100; i++) { if(i%3==0){ t.paly("演员表示节目:"+i); }else t.paly("正在播放广告"+i); } } } class Watcher extends Thread{ TV t; Watcher (TV t){ this.t=t; } @Override public void run() { for (int i = 0; i < 100; i++) { t.watch(); } } } class TV{ // 表演的节目 String voice; boolean flag=true; public synchronized void paly(String voice){ if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演:"+voice); flag=!flag; // 通知观众线程观看 this.voice=voice; this.notifyAll(); } public synchronized void watch(){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看:"+this.voice); flag=!flag; // 通知演员生产 this.notifyAll(); } }
线程池的使用
package 多线程使用.线程协作; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //测试线程池 public class Testpool { public static void main(String[] args) { // 创建服务,创建线程池 // 参数为线程池大小 ExecutorService pool = Executors.newFixedThreadPool(10); pool.execute(new MyThread()); pool.execute(new MyThread()); pool.execute(new MyThread()); pool.execute(new MyThread()); pool.execute(new MyThread()); // 关闭连接 pool.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
多线程总结
多线程的创建
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口