多线程
线程创建方式 :
- 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
- 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对 象,然后使用Thread类型的对象调用start方法。
因为Java中不支持多继承,所以一般使用接口Runnable的方式。
线程的编号和名称:
package com.lagou.task18; public class ThreadIdNameTest extends Thread { public ThreadIdNameTest(String name) { super(name); // 表示调用父类的构造方法 } @Override public void run() { System.out.println("子线程的编号是:" + getId() + ",名称是:" + getName()); // 14 Thread-0 guanyu // 修改名称为"zhangfei" setName("zhangfei"); System.out.println("修改后子线程的编号是:" + getId() + ",名称是:" + getName()); // 14 zhangfei } public static void main(String[] args) { ThreadIdNameTest tint = new ThreadIdNameTest("guanyu"); tint.start(); // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用 Thread t1 = Thread.currentThread(); System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); } }
常用方法:
package com.lagou.task18; public class ThreadNoNameTest { public static void main(String[] args) { // 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型() { 方法的重写 }; // 1.使用继承加匿名内部类的方式创建并启动线程 /*Thread t1 = new Thread() { @Override public void run() { System.out.println("张三说:在吗?"); } }; t1.start();*/ new Thread() { @Override public void run() { System.out.println("张三说:在吗?"); } }.start(); // 2.使用实现接口加匿名内部类的方式创建并启动线程 /*Runnable ra = new Runnable() { @Override public void run() { System.out.println("李四说:不在。"); } }; Thread t2 = new Thread(ra); t2.start();*/ /*new Thread(new Runnable() { @Override public void run() { System.out.println("李四说:不在。"); } }).start();*/ // Java8开始支持lambda表达式: (形参列表)->{方法体;} /*Runnable ra = ()-> System.out.println("李四说:不在。"); new Thread(ra).start();*/ new Thread(()-> System.out.println("李四说:不在。")).start(); } }
设置线程优先级:
public class ThreadPriorityTest extends Thread { @Override public void run() { //System.out.println("子线程的优先级是:" + getPriority()); // 5 10 优先级越高的线程不一定先执行。 for (int i = 0; i < 20; i++) { System.out.println("子线程中:i = " + i); } } public static void main(String[] args) { ThreadPriorityTest tpt = new ThreadPriorityTest(); // 设置子线程的优先级 tpt.setPriority(Thread.MAX_PRIORITY); tpt.start(); Thread t1 = Thread.currentThread(); //System.out.println("主线程的优先级是:" + t1.getPriority()); // 5 普通的优先级 for (int i = 0; i < 20; i++) { System.out.println("--主线程中:i = " + i); } } }
设置守护线程:
package com.lagou.task18; public class ThreadDaemonTest extends Thread { @Override public void run() { //System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程 // 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止 // 当子线程是守护线程时,当主线程结束后,则子线程随之结束 for (int i = 0; i < 50; i++) { System.out.println("子线程中:i = " + i); } } public static void main(String[] args) { ThreadDaemonTest tdt = new ThreadDaemonTest(); // 必须在线程启动之前设置子线程为守护线程 tdt.setDaemon(true); tdt.start(); for (int i = 0; i < 20; i++) { System.out.println("-------主线程中:i = " + i); } } }
希望子线程随着主线程的结束而结束,就可以把子线程设置为守护线程.
线程同步机制:
- 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线 程之间进行通信和协调,该机制就叫做线程的同步机制。
- 多个线程并发读写同一个临界资源时会发生线程并发安全问题。
- 异步操作:多线程并发的操作,各自独立运行。
- 同步操作:多线程串行的操作,先后执行的顺序。
经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。
使用synchronized保证线程同步应当注意:
- 多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用,一般有 空字符串,当前类的类模板对象
- 在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
线程安全类和不安全类:
- StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
- Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
- Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。
注意: 在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
使用Lock(锁)实现线程同步:
- 从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
- 该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程 安全控制中,经常使用ReentrantLock类显式加锁和释放锁
使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。
package com.lagou.task18; import java.util.concurrent.locks.ReentrantLock; public class AccountRunnableTest implements Runnable { private int balance; // 用于描述账户的余额 private Demo dm = new Demo(); private ReentrantLock lock = new ReentrantLock(); // 准备了一把锁 public AccountRunnableTest() { } public AccountRunnableTest(int balance) { this.balance = balance; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } @Override public /*synchronized*/ void run() { // 开始加锁 lock.lock(); // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account //synchronized (this) { // ok System.out.println("线程" + Thread.currentThread().getName() + "已启动..."); //synchronized (dm) { // ok //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象 // 1.模拟从后台查询账户余额的过程 int temp = getBalance(); // temp = 1000 temp = 1000 // 2.模拟取款200元的过程 if (temp >= 200) { System.out.println("正在出钞,请稍后..."); temp -= 200; // temp = 800 temp = 800 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("请取走您的钞票!"); } else { System.out.println("余额不足,请核对您的账户余额!"); } // 3.模拟将最新的账户余额写入到后台 setBalance(temp); // balance = 800 balance = 800 //} lock.unlock(); // 实现解锁 } public static void main(String[] args) { AccountRunnableTest account = new AccountRunnableTest(1000); //AccountRunnableTest account2 = new AccountRunnableTest(1000); Thread t1 = new Thread(account); Thread t2 = new Thread(account); //Thread t2 = new Thread(account2); t1.start(); t2.start(); System.out.println("主线程开始等待..."); try { t1.join(); //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二 t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终的账户余额为:" + account.getBalance()); // 600 800 } } class Demo{}
Object类常用的方法:
package com.lagou.task18; public class ThreadCommunicateTest implements Runnable { private int cnt = 1; @Override public void run() { while (true) { synchronized (this) { // 每当有一个线程进来后先大喊一声,调用notify方法 notify();//唤醒另外一个线程 if (cnt <= 100) { System.out.println("线程" + Thread.currentThread().getName() + "中:cnt = " + cnt); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } cnt++; // 当前线程打印完毕一个整数后,为了防止继续打印下一个数据,则调用wait方法 try { wait(); // 当前线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中调用 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } public static void main(String[] args) { ThreadCommunicateTest tct = new ThreadCommunicateTest(); Thread t1 = new Thread(tct); t1.start(); Thread t2 = new Thread(tct); t2.start(); } }
生产者消费者模型:
package com.lagou.task18; /** * 编程实现仓库类 */ public class StoreHouse { private int cnt = 0; // 用于记录产品的数量 public synchronized void produceProduct() { notify(); if (cnt < 10) { System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品..."); cnt++; } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void consumerProduct() { notify(); if (cnt > 0) { System.out.println("线程" + Thread.currentThread().getName() + "消费第" + cnt + "个产品"); cnt--; } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程池:
从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口.
当需要线程的返回值,可以使用这种方式创建线程.
FutureTask类可以获取call的返回值:
总结: Callable 接口 的call方法返回值 是通过FutureTask类的get方法获取.
package com.lagou.task18; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadCallableTest implements Callable { @Override public Object call() throws Exception { // 计算1 ~ 10000之间的累加和并打印返回 int sum = 0; for (int i = 1; i <= 10000; i++) { sum +=i; } System.out.println("计算的累加和是:" + sum); // 50005000 return sum; } public static void main(String[] args) { ThreadCallableTest tct = new ThreadCallableTest(); FutureTask ft = new FutureTask(tct); Thread t1 = new Thread(ft); t1.start(); Object obj = null; try { obj = ft.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("线程处理方法的返回值是:" + obj); // 50005000 } }
线程池的概念和原理:
- 首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就 从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
- 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务 后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程 池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
- 从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和 java.util.concurrent.ExecutorService接口。
- 其中Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法如 下:
- 其中ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法 如下:
总结:通过Executors 的静态方法获取线程池对象.
package com.lagou.task18; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolTest { public static void main(String[] args) { // 1.创建一个线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 2.向线程池中布置任务 executorService.submit(new ThreadCallableTest()); // 参数为实现 callable 或实现 runnable接口的对象. // 3.关闭线程池 executorService.shutdown(); } }
可以使用invokeAll方法提交多个任务,该方法接收一个泛型是callable类型的集合.
多线程版单例:
package com.lagou.singlton; import com.lagou.collect.LogCollectorTask; import java.io.IOException; import java.util.Properties; public class PropTool2 { //volatile关键字是java中禁止指令重排序的关键字,保证有序性和可见性 private static volatile Properties prop=null; //出现线程安全问题 public static Properties getProp() throws IOException { if(prop ==null){ synchronized ("lock"){ if(prop ==null){ prop=new Properties(); prop.load(LogCollectorTask.class.getClassLoader() .getResourceAsStream("collector.properties")); } } } return prop; } }