多线程的实现方式
- 继承Thread方法
- 实现Runable接口
- 实现Callable接口
1.继承Thread方法
创建一个类继承Thread类,重写run方法
通过多态创建一个子线程,子线程调用start方法
//创建线程方式一:继承Thread类,重现run方法 public class TestThread1 extends Thread{ @Override public void run(){ for (int i=0;i<20;i++){ System.out.println(currentThread().getName()+"执行"+i); } } public static void main(String[] args) { //创建一个子线程 Thread thread = new TestThread1(); //线程启动 thread.start(); for (int i=0;i<200;i++){ System.out.println(currentThread().getName()+"执行"+i); } } }
2.实现Runnable接口
创建一个类实现Runnable接口
重写run方法
创建runnable接口实现类对象
创建Thread类对象,通过线程对象来开启我们的线程
// 创建线程的方式2:实现runanable接口,重新写run方法,执行线程需要丢入runnable接口实现类,调用start方法 public class TestThread3 implements Runnable{ @Override public void run() { //run方法线程体 for (int i=0 ; i<100; i++){ System.out.println(currentThread().getName()+":"+i); } } public static void main(String[] args) { //创建runnable接口的实现类对象 TestThread3 testThread3 = new TestThread3(); //创建线程对象,通过线程对象来开启我们的线程,代理 Thread thread = new Thread(testThread3,"子线程1"); thread.start(); currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { System.out.println(currentThread().getName()+":"+i); } } }
小结
- 继承Thread类
- 子类继承Thread类具有多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
并发线程遇到数据紊乱问题
//多个线程同时操作一个对象 //买火车票的例子 //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 public class TestThread4 implements Runnable{ //票数 private int sum = 20; @Override public void run() { while (true){ if (sum<=0){ break; } try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"拿到了第"+sum--+"票"); } } public static void main(String[] args) { TestThread4 testThread4 = new TestThread4(); new Thread(testThread4,"线程1").start(); new Thread(testThread4,"线程2").start(); } } 线程1拿到了第19票 线程2拿到了第20票 线程1拿到了第17票 线程2拿到了第18票 线程1拿到了第16票 线程2拿到了第15票 线程2拿到了第14票 线程1拿到了第13票 线程1拿到了第12票 线程2拿到了第11票 线程1拿到了第9票 线程2拿到了第10票 线程1拿到了第8票 线程2拿到了第8票 线程2拿到了第7票 线程1拿到了第6票 线程2拿到了第4票 线程1拿到了第5票 线程1拿到了第3票 线程2拿到了第2票 线程1拿到了第1票 线程2拿到了第0票
3.实现CallAble接口
- 创建执行服务:创建线程池
- 提交执行
- 获取结果
- 关闭服务
重写call方法,创建线程池,提交线程子类
/** * 可以拥有返回值 * 可以抛出异常 */ public class TestCallable implements Callable<Boolean> { @Override public Boolean call() throws Exception { String inputurl = "mvnw.cmd"; String outputurl = Thread.currentThread().getName()+".cmd"; InputStream inputStream = new FileInputStream(inputurl); OutputStream outputStream = new FileOutputStream(outputurl); byte[] bytes = new byte[1024]; while (inputStream.read(bytes)!=-1){ outputStream.write(bytes); } return Boolean.TRUE; } public static void main(String[] args) throws Exception { TestCallable t1 =new TestCallable(); TestCallable t2 =new TestCallable(); TestCallable t3 =new TestCallable(); //创建执行服务:创建线程池 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> submit1 = ser.submit(t1); Future<Boolean> submit2 = ser.submit(t2); Future<Boolean> submit3 = ser.submit(t3); //获取结果 Boolean aBoolean1 = submit1.get(); Boolean aBoolean2 = submit2.get(); Boolean aBoolean3 = submit3.get(); //关闭服务 ser.shutdown(); } }
线程的状态
- 创建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
方法 说明 setPriority(int newPriority) 更改线程优先级 static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠 void join() 等待该线程终止 static void yield() 暂停当前正在执行的线程对象,并执行其他线程 void interrrupt() 中断线程(不用) boolean isAlive 测试线程是否处于活动状态
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态;
- sleep可以模拟网络延时,倒计时等;
- 每一个对象都有一个锁,sleep不会释放锁;
线程礼让
yield不一定成功
/** * 测试yield * 礼让不一定成功 */ public class TestYield{ public static void main(String[] args) { MyYield myYield = new MyYield(); Thread t1 = new Thread(myYield,"a线程"); Thread t2 = new Thread(myYield,"b线程"); t1.start(); t2.start(); } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName()+"执行结束"); } }
线程优先级
- 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) { Mypriority mypriority = new Mypriority(); Thread t1 = new Thread(mypriority, "t1"); Thread t2 = new Thread(mypriority, "t2"); Thread t3 = new Thread(mypriority, "t3"); t2.setPriority(4); t3.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start(); t3.start(); System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority()); } } 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) { Thread human = new Thread(new Human()); Thread god = new Thread(new God()); Thread human2 = new Thread(new Human2()); god.setDaemon(true);//开启守护线程 human.start(); god.start(); human2.start(); } } class Human implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("用户进程"+i); } } } class God implements Runnable{ @Override public void run() { while (true){ try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("守护进程"); } } } class Human2 implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("用户线程2"+i); } } }
线程的同步机制
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被i访问时的正确性,在访问时加入锁机制sychronized,当一个线程获得对象的排他锁,独占资源,其他线程必须的等待,
使用后释放锁即可,存在下列问题:
- 一个线程持有锁会导致其他所有拥有此锁的线程挂起:
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
同步方法
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized放啊和synchronized块
- 同步方法:public synchronized void method(int args){}
- synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
- 缺陷:若将一个大的方法申明为synchronized将会影响效率
同步块
- 同步块:synchronized(OBJ){}
- Obj称之为同步监视器
- Obj可以是任何对象,到那时推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射】
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器
死锁
死锁:多个线程互相抱着对方需要的资源,然后形成僵持
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
package cn.cslg.kuangshen.lock; public class DeadLock extends Thread{ public static void main(String[] args) { Thread t1 = new DeadLock(0); Thread t2 = new DeadLock(2); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } private int choose = 0; private String lipstick ="a"; private String mirror ="b"; @Override public void run() { if (choose == 0){ synchronized (lipstick){ System.out.println(Thread.currentThread().getName()+"lipstick"); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (mirror){ System.out.println(Thread.currentThread().getName()+"mirror"); } } }else { synchronized (mirror){ System.out.println(Thread.currentThread().getName()+"lipstick"); synchronized (lipstick){ System.out.println(Thread.currentThread().getName()+"mirror"); } } } } public DeadLock(int choose){ this.choose = choose; } }
Lock(锁)
package cn.cslg.kuangshen.lock; import java.util.concurrent.locks.ReentrantLock; //测试Lock锁 public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); Thread t1 = new Thread(testLock2,"t1"); Thread t2 = new Thread(testLock2,"t2"); t1.start(); t2.start(); } } class TestLock2 implements Runnable{ private int ticketNums =10; //定义lock锁 private ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { //加锁 lock.lock(); while (ticketNums>0){ try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+ticketNums--); } } catch (Exception e) { throw new RuntimeException(e); }finally { //解锁 lock.unlock(); } } }
生产者消费者问题 略过
使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池大小
- maxinmumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长事件后会终止
//测试线程池 public class TestPool { public static void main(String[] args) { //创建服务,创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(2); //执行 executorService.submit(new TestThread()); executorService.submit(new TestThread()); executorService.submit(new TestThread()); //关闭连接 executorService.shutdown(); } } class TestThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }