进程和线程
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是程序使用CPU的基本单位,一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路径,则称为多线程程序。并行:前者是逻辑上同时发生,其实是指在某一个时间内同时运行多个程序。
并发:后者是物理上同时发生,指在某一个时间点同时运行多个程序。
多线程的实现
实现一、继承Thread类
* 1)自定义一个类 继承自 Thread(线程类)
* 2)该子类应重写 Thread 类的 run 方法
* 3)创建当前子类对象,然后启动线程:start()而非run方法()
* 启动线程:使用start 方法public class ThreadTest { public static void main(String[] args) throws InterruptedException { //创建线程对象 MyThread mt = new MyThread(); MyThread mt1 = new MyThread(); //启动线程 mt.setName("线程1"); mt1.setName("线程2"); mt.start(); mt1.start(); } } class MyThread extends Thread{ @Override public void run() { //线程执行的代码 for (int i = 0; i < 100; i++) { System.out.println(getName()+"--"+i); } } }
注意:
- 启动线程时start()方法不是run(),run方法只是封装了线程需要执行的代码,调用和平常方法没区别,不能体现多线程效果
- 一个线程对象不能多次调用start()方法会出现异常(IllegalThreadStateException:非法的线程状态异常),可以创建多个对象分别调用start()方法
为什么要重写run()方法?
* 不是Thread类中的所有代码都需要被线程执行的。
* 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。线程的几种状态
- NEW 新建状态
- RUNNABLE 运行状态
- BLOCKED 阻塞状态
- WAITING 一个线程在等待另一个线程执行一个动作时在这个状态
- TIMED_WAITING 一个线程在一个特定的等待时间内等待另一个线程完成一个动作会在这个状态
- TERMINALED 线程死亡状态
Tread类的方法
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。yield()将导致线程从运行状态转到就绪状态,但有可能没有达到效果。因为就绪状态也可能被cpu调度重新选中
线程的优先级: 优先级越大的线程,抢占CPU的执行权(cpu一点点时间片,可以高效的进行切换)的几率大,优先级越小的线程:抢占CPU的执行权几率越小! 但也不是一定的,线程的执行具有随机性!
- public static final int MAX_PRIORITY 10 最大值
- public static final int MIN_PRIORITY 1 最小值
- public static final int NORM_PRIORITY 5 :默认值
- public final void setPriority(int newPriority)设置优先级
- public final int getPriority() 获取优先级
public final void join() throws InterruptedException :等待该线程终止。可以使线程进入阻塞状态
public static void sleep(long millis)throws InterruptedException:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
public static Thread currentThread():表示正在执行的线程对象的引用
public final void setName(String name) :设置线程名称举例:实现电影院买票,三个窗口卖100张票,加入网络延迟100ms
public class MyThread extends Thread{ private static int TICKET = 100; @Override public void run() { while(true){ try { //让线程睡眠模拟网络延迟100毫秒 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(TICKET>0){ System.out.println(getName()+"正在卖第"+(TICKET--)+"张票"); }else{ break; } } } } class Test{ public static void main(String[] args) { MyThread m = new MyThread(); MyThread m1 = new MyThread(); MyThread m2 = new MyThread(); m.setName("窗口1"); m1.setName("窗口2"); m2.setName("窗口3"); m.start(); m1.start(); m2.start(); } }
这种方式无法体现资源共享,因为创建了三个对象
实现二、实现Runnable接口
1)自定义一个类,实现Runnable接口,重写run方法
2)在用户线程(main)创建当前"资源类"对象,然后创建Thread类对象,将"资源类"对象作为参数传递。也可以使用Runable的匿名内部类作为参数传递,但是无法实现多个线程共享资源
public Thread(Runnable target,String name)
3)分别启动线程public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i); } } } class Test{ public static void main(String[] args) { Thread t = new Thread(new MyRunnable(),"线程1");//传入Runnable子实现类和线程名 Thread t1 = new Thread(new MyRunnable(),"线程2"); //匿名内部类作为参数传递 Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i); } } }, "线程3"); //启动线程 t.start(); t1.start(); t2.start(); } }
举例:实现电影院买票,三个窗口卖100张票
public class MyRunable implements Runnable{ private static int TICKET = 100; @Override public void run() { while(true){ if(TICKET>0){ //让线程睡眠100毫秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖第"+(TICKET--)+"张票"); } } } } class Test{ public static void main(String[] args) { MyRunable m = new MyRunable(); Thread t = new Thread(m,"窗口1"); Thread t1 = new Thread(m,"窗口2"); Thread t2 = new Thread(m,"窗口3"); t.start(); t1.start(); t2.start(); } }
总结:这种方式体现了”资源共享“
但可能出现了同票和负票,这中由于线程(具有随机性,原子性操作)存在安全问题,可能前一个线程正在记录数据还没输出记录,第二个线程进来使用之前的记录进行了输出,就会出现同票。负票是由于加入延迟导致。
所有需要使用线程锁。
实现三、线程池
线程池的使用:
* 1)接口:ExecutorService :跟踪一个或者多个异步任务
* Executors:工厂类
* 提供创建线程池对象的方法
* public static ExecutorService newFixedThreadPool(int nThreads)
* ExecutorService
* 方法:
* Future<?> submit(Runnable task) :通过线程池提交异步任务
* <T> Future<T> submit(Callable<T> task):提交异步任务
* Future:异步任务计算的结果!
* 第三种方式:线程池实现---->自定义一个类 实现Callable接口public class ThreadDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { //创建一个线程池:静态工厂模式 ExecutorService pool = Executors.newFixedThreadPool(2); //提交异步任务 // Future<?> submit(Runnable task) :通过线程池提交异步任务 /*pool.submit(new MyRunnable()) ; pool.submit(new MyRunnable()) ;*/ //使用Future接收异步计算结果) Future<Integer> future = pool.submit(new MyCallable(100)); Future<Integer> future1 = pool.submit(new MyCallable(200)); Integer integer = future.get(); Integer integer1 = future1.get(); //使用完毕,关闭线程池--将底层产生的线程归还线程池中 pool.shutdown(); System.out.println(integer); System.out.println(integer1); } } class MyCallable implements Callable<Integer> { private int num ; public MyCallable(int num){ this.num = num; } //call方法:本身要计算的结果 int sum; @Override public Integer call() throws Exception { for(int x = 0 ; x <num ; x ++ ){ sum+=x; } return sum; } }
线程组
线程组 ThreadGroup 线程组表示一个线程的集合
*
* Thread类中方法:
* public final ThreadGroup getThreadGroup():获取当前所有线程的默认线程组ThreadGroup类的方法
* public final String getName() :获取线程组的名称: (默认就是main)
* 构造方法:
* ThreadGroup(String name):构造一个新的名称的线程组
* 所有的线程默认的线程组就是main线程(用户线程)public class ThreadGroupDemo { public static void main(String[] args) { method() ; method2() ; } //设置线程组名称 private static void method2() { // ThreadGroup(String name):构造一个新的名称的线程组 ThreadGroup tg = new ThreadGroup("myMain") ; //创建线程类对象:可以将线程组对象作为参数传递 //public Thread(ThreadGroup group,String name) Thread t1 = new Thread(tg,"线程1") ; Thread t2 = new Thread(tg,"线程2") ; System.out.println(t1.getThreadGroup().getName()); System.out.println(t2.getThreadGroup().getName()); } private static void method() { //创建线程类对象 Thread my1 = new Thread() ; //获取对应的线程组名称 ThreadGroup tg1 = my1.getThreadGroup(); String name = tg1.getName(); System.out.println(name); } }
synchronized:关键字
内置语言实现的(jvm来实现锁定操作: 锁的释放:自动释放)
* 可以是通过代码块(代码中),可以在方法上使用(同步方法)
* synchronized同步锁---属于"悲观锁"
* 悲观锁:自己线程本身在执行的时候,其他线程不能进入同步代码块中,其他线程不能进入
* 修改数据,保证数据的安全性! (针对频繁的写入操作)校验多线程安全问题的标准是什么?
*
* 1)查看当前程序是否是多线程环境
* 2)是否存在共享数据
* 3)是否有多条语句对共享数据进行操作 在这里使用锁举例:实现电影院买票,三个窗口卖100张票
public class MyRunable implements Runnable{ private static int TICKET = 100; Object obj = new Object(); @Override public void run() { while(true){ //加入线程锁,三个线程使用同一把锁 synchronized(obj){ if(TICKET>0){ //让线程睡眠100毫秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖第"+(TICKET--)+"张票"); } } } } } class Test{ public static void main(String[] args) { MyRunable m = new MyRunable(); Thread t = new Thread(m,"窗口1"); Thread t1 = new Thread(m,"窗口2"); Thread t2 = new Thread(m,"窗口3"); t.start(); t1.start(); t2.start(); } }
注意:
- 锁对象可以是任意java对象
- 模拟多线程时多个线程必须使用同一把锁,不然无效果
- 当锁在方法上使用时,锁对象是this,即调用方法的对象本身,当在静态方法上时,锁对象是:类名.class属性,即是类的字节码文件对象
Lock
JDK5以后Java提供了比syncrhonized(jvm来操作:自动释放锁)更广泛的锁定操作,程序员可以在某个位置自己去加锁,(弊端):手动释放锁
java.util.concurrent.locks.Lock 接口 不能实例化
ReentrantLock :子实现类
* 提供成员方法
* void lock() :获取锁
* void unlock():手动试图释放锁synchronized: 关键字---> 内置语言实现---通过jvm调用的,由jvm自动释放锁
它不仅仅可以应用在方法体中(同步代码块),也可以引用在方法上(同步方法)
Lock :是一个接口 手动加锁和手动释放锁
一般都是在方法中的语句中使用public class LockDemo implements Runnable{ private static int Ticket = 100; private Lock lock = new ReentrantLock();//创建Lock接口的子类ReentrantLock的对象 @Override public void run() { while (true){ lock.lock();//线程进入这里必须持有锁 //线程执行代码放在try....finally的try中 try{ if(Ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖第"+(Ticket--)+"张票"); }else{ break; } }finally { lock.unlock();//释放锁放在finally中 } } } } class Test{ public static void main(String[] args) { LockDemo l = new LockDemo(); Thread t = new Thread(l,"窗口1"); Thread t1 = new Thread(l,"窗口2"); Thread t2 = new Thread(l,"窗口3"); t.start(); t1.start(); t2.start(); } }
虽然syncrhonized可以解决线程安全问题:同步代码块/同步方法,但是执行效率低,可能出现死锁
* 两个线程或者多个线程出现互相等待的情况!
* 解决死锁问题方案:"生产者消费者思想" 必须保证多个多线程必须使用的同一个资源对象!经典联系题:龟兔赛跑
public class Race implements Runnable{ private static String WINNER = null;//定义冠军者 public static void main(String[] args) { Race a = new Race(); Thread t = new Thread(a,"兔子"); Thread t1 = new Thread(a,"乌龟"); t.start(); t1.start(); } @Override public void run() { //定义跑道100米,使用for循环 for (int i = 1; i <= 100; i++) { if (Thread.currentThread().getName().equals("兔子")&&i % 10==0){//如果是兔子并且每10米睡100毫秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } boolean flag = gameOver(i);//获取flag是否结束循环 if(flag){ break; } System.out.println(Thread.currentThread().getName()+"跑了"+i+"米"); } } //取得冠军 private boolean gameOver(int i) { if(WINNER!=null){ return true; }else { if(i>=100){ System.out.println("game over"); System.out.println(Thread.currentThread().getName()+"赢了!"); WINNER=Thread.currentThread().getName(); return true; } } return false; } }
代理模式
Java代理模式----结构型设计模式
代理:
* 让别人替自己本身完成一些事情!
* 代理:
* 代理角色:帮助真实角色对他本身的功能进行增强(完成代理完成不了的事情!)
* 真实角色:只只专注于自己完成的事情多线程第二种实现方式就是代理模式
MyRunnable myrunnable = new MyRunnable() ; //真实角色
//线程类
Thread t1 = new Thread(myrunnable,"t1") ; //Thread类为代理角色
Thread t2 = new Thread(myrunnable,"t2") ;
Thread t3 = new Thread(myrunnable,"t3") ;静态代理
* 同一个接口
* 特点:代理角色和真实角色必须同一个接口
动态代理
* JDK动态代理:基于接口
* CGLib动态代理:基于子类静态代理举例:
结婚这件事情 真实角色:you :你 代理角色:weddingCompany:婚庆公司
//定义接口 interface Marry{ void marry(); } //真实角色 public class You implements Marry { @Override public void marry() { System.out.println("我要结婚了。。"); } } //代理 class WeddingCompany implements Marry{ private You you;//创建真实角色变量 public WeddingCompany(You you){ this.you = you;//通过构造方法传入真实对象 } @Override public void marry() {//其他事情代理完成 System.out.println("结婚之前,婚庆公司布置婚礼现场..."); you.marry();//真实角色专注自己的事情 System.out.println("结婚完毕,很高兴,给婚庆付尾款..."); } } class StaticProxyDemo{ public static void main(String[] args) { You you = new You(); WeddingCompany wc = new WeddingCompany(you);//传入真是角色对象 wc.marry(); } }
生产者消费者模式
可以通过等待唤醒机制解决死锁现象
需要使用Object类的wait()和notify()方法
public final void notify() 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程
public final void wait() throws InterruptedException 在其他线程调用此对象的
notify()
方法或notifyAll()
方法前,导致当前线程等待举例:
学生类:Student
生产者:SetStudent
消费者:GetStudent
测试类:Test
学生有属性name、和age,通过生产者进行赋值、消费着负责输入打印,想实现赋值一次打印一次,就需要使用线程锁(synchronized)和等待唤醒
class SetStudent implements Runnable{ private Student s; public SetStudent(Student s){ this.s = s; } int x = 0;//统计变量 @Override public void run() { while (true) { synchronized (s){ if(s.flag){//当学生赋值后线程进入等待 try { s.wait();//线程进入等待并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } if (x % 2 == 0) { s.setName("吕布"); s.setAge(50); } else { s.setName("貂蝉"); s.setAge(40); } x++; s.flag=true;//改变标记 自身进入等待并告诉输出不用进入等待 s.notify();//当学生属性被赋值后,唤醒输出的线程 } } } } class GetStudent implements Runnable{ private Student s; public GetStudent(Student s){ this.s = s; } @Override public void run() { while(true){ synchronized (s){ if(!s.flag){//当没有赋值时进入等待 try { s.wait();//线程进入等待并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } s.flag=false;//改变标记 自身进入等待并告诉赋值线程不用进入等待 s.notify();//当学生属性被打印后,唤醒赋值的线程 System.out.println(Thread.currentThread().getName()+": "+s.getName()+"--"+s.getAge()); } } } } class Test{ public static void main(String[] args) { Student s = new Student(); SetStudent sd = new SetStudent(s); GetStudent gd = new GetStudent(s); Thread t = new Thread(sd,"生产者"); Thread t1 = new Thread(gd,"消费者"); t.start(); t1.start(); } }
定时器
java.util.Timer:定时器 :有线程安排执行务执行一次,或者定期重复执行。
* 构造方法:
* public Timer() 无参构造方法
*
* 成员方法
* public void cancel():取消定时器
* public void schedule(TimerTask task,Date time) :在指定日期时间内执行这个任务
* public void schedule(TimerTask task,long delay):在指定的延迟时间后执行task任务(时间:毫秒)
*
* public void schedule(TimerTask task,
* long delay,
* long period) :在指定的delay延迟时间后开始重复时间间隔period来执行task任务public class TimerTest { public static void main(String[] args) { Timer t = new Timer(); t.schedule(new TimerTask() { @Override public void run() { System.out.println("哈哈哈"); t.cancel();//结束 } },3000);//3秒后打印 } }