多线程
并发和并行
并行:在同一时刻,有多个指令在多个CPU上同时执行。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
进程和线程
进程:就是操作系统中正在运行的一个应用程序。
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
线程:是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
多线程的实现方案
1.继承Thread类的方式进行实现
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法
创建MyThread类的对象
启动线程
1 public class MyThread extends Thread{ 2 @Override 3 public void run() { 4 //代码就是线程在开启之后执行的代码 5 for (int i = 0; i < 100; i++) { 6 System.out.println("线程开启了" + i); 7 } 8 } 9 }
1 public static void main(String[] args) { 2 //创建一个线程对象 3 MyThread t1 = new MyThread(); 4 //创建一个线程对象 5 MyThread t2 = new MyThread(); 6 //t1.run();//表示的仅仅是创建对象,用对象去调用方法,并没有开启线程. 7 //t2.run(); 8 //开启一条线程 9 t1.start(); 10 //开启第二条线程 11 t2.start(); 12 }
重写run()方法:run()用来封装被线程执行的代码。
run()方法和start()方法的区别:
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。
start():启动线程,然后由JVM调用此线程的run()方法。
2.实现Runnable接口的方式进行实现
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
启动线程
1 public class MyRunnable implements Runnable{ 2 @Override 3 public void run() { 4 //线程启动后执行的代码 5 for (int i = 0; i < 100; i++) { 6 System.out.println( "实现多线程" + i); 7 } 8 } 9 }
1 public static void main(String[] args) { 2 //创建了一个参数的对象 3 MyRunnable mr = new MyRunnable(); 4 //创建了一个线程对象,并把参数传递给这个线程. 5 //在线程启动之后,执行的就是参数里面的run方法 6 Thread t1 = new Thread(mr); 7 //开启线程 8 t1.start(); 9 10 MyRunnable mr2 = new MyRunnable(); 11 Thread t2 = new Thread(mr2); 12 t2.start(); 13 }
3.用Callable和Future进行实现
定义一个类MyCallable实现Callable接口
在MyCallable类中重写call()方法
创建MyCallable类的对象
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
创建Thread类的对象,把FutureTask对象作为构造方法的参数
启动线程
1 public class MyCallable implements Callable<String> { 2 @Override 3 public String call() throws Exception { 4 for (int i = 0; i < 100; i++) { 5 System.out.println("实现多线程" + i); 6 } 7 //返回值就表示线程运行完毕之后的结果 8 return "结束"; 9 } 10 }
1 public static void main(String[] args) throws ExecutionException, InterruptedException { 2 //线程开启之后需要执行里面的call方法 3 MyCallable mc = new MyCallable(); 4 //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象 5 FutureTask<String> ft = new FutureTask<>(mc); 6 //创建线程对象 7 Thread t1 = new Thread(ft); 8 //开启线程 9 t1.start(); 10 //获取call方法线程运行完毕之后的结果,如果线程未结束,get方法会在这里等待;所以get方法需在start方法后 11 String s = ft.get(); 12 System.out.println(s); 13 }
三种方式的对比
实现Runnable、Callable接口
优点:扩展性强,实现该接口的同时还可以继承其他的类。
缺点:编程相对复杂,不能直接使用Thread类中的方法。
继承Thread类
优点:编程比较简单,可以直接使用Thread类中的方法。
缺点:扩展性较差,不能再继承其他的类。
线程类的常见方法
获取和设置线程名称
获取线程的名字
String getName() 返回此线程的名称
Thread类中设置线程的名字
void setName(String name) 将此线程的名称更改为等于参数 name
通过构造方法也可以设置线程名称
获得当前线程的对象
public static Thread currentThread() 返回对当前正在执行的线程对象的引用
String name = Thread.currentThread().getName();
线程休眠
public static void sleep(long time) 让线程休眠指定的时间,单位为毫秒。
线程调度
线程有两种调度模型
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
Java使用的是抢占式调度模型
线程的优先级
public final void setPriority(int newPriority) 设置线程的优先级(优先级: 1 - 10 默认值:5)
public final int getPriority() 获取线程的优先级
后台线程/守护线程
public final void setDaemon(boolean on) 设置为守护线程
当普通线程执行完之后,守护线程也没有继续运行下去的必要了(守护线程不会执行完毕)
线程的安全问题
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完出来了,锁才会自动打开
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
1 public class MyThread extends Thread { 2 private static int ticketCount = 100; //由于main中创建了两个MyThread对象,所以加static,使得MyThread中创建的所有对象共享ticketCount 3 private static Object obj = new Object(); 4 5 @Override 6 public void run() { 7 while(true){ 8 synchronized (obj){ 9 if(ticketCount <= 0){ 10 break; //卖完了 11 }else{ 12 try { 13 Thread.sleep(100); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 ticketCount--; 18 System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票"); 19 } 20 } 21 } 22 } 23 }
1 public static void main(String[] args) { 2 MyThread t1 = new MyThread(); 3 MyThread t2 = new MyThread(); 4 5 t1.setName("窗口一"); 6 t2.setName("窗口二"); 7 8 t1.start(); 9 t2.start(); 10 }
同步方法
同步方法
把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数) { }
同步代码块和同步方法的区别:
同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
同步代码块可以指定锁对象,同步方法不能指定锁对象
同步方法的锁对象是this
同步静态方法
把synchronized关键字加到静态方法上
格式:
修饰符 static synchronized 返回值类型 方法名(方法参数) { }
同步静态方法的锁对象是类名.class
1 public class MyRunnable implements Runnable { 2 //private int ticketCount = 100; //同步方法 3 private static int ticketCount = 100; //同步静态方法 4 5 @Override 6 public void run() { 7 while(true){ 8 if("窗口一".equals(Thread.currentThread().getName())){ 9 //同步方法 10 boolean result = synchronizedMthod(); 11 if(result){ 12 break; 13 } 14 } 15 16 if("窗口二".equals(Thread.currentThread().getName())){ 17 //同步代码块 18 //synchronized (this){ //同步方法 19 synchronized (MyRunnable.class){ //同步静态方法 20 if(ticketCount == 0){ 21 break; 22 }else{ 23 try { 24 Thread.sleep(10); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 ticketCount--; 29 System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票"); 30 } 31 } 32 } 33 34 } 35 } 36 37 //private synchronized boolean synchronizedMthod() { //同步方法 38 private static synchronized boolean synchronizedMthod() { //同步静态方法 39 if(ticketCount == 0){ 40 return true; 41 }else{ 42 try { 43 Thread.sleep(10); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 ticketCount--; 48 System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票"); 49 return false; 50 } 51 } 52 }
1 public static void main(String[] args) { 2 MyRunnable mr = new MyRunnable(); 3 4 Thread t1 = new Thread(mr); 5 Thread t2 = new Thread(mr); 6 7 t1.setName("窗口一"); 8 t2.setName("窗口二"); 9 10 t1.start(); 11 t2.start(); 12 }
Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock中提供了获得锁和释放锁的方法
void lock() 获得锁
void unlock() 释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock() 创建一个ReentrantLock的实例
1 public class Ticket implements Runnable { 2 private int ticket = 100; //票的数量 3 private Object obj = new Object(); 4 private ReentrantLock lock = new ReentrantLock(); 5 6 @Override 7 public void run() { 8 while (true) { 9 try { 10 lock.lock(); 11 if (ticket <= 0) { 12 break; //卖完了 13 } else { 14 Thread.sleep(100); 15 ticket--; 16 System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票"); 17 } 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } finally { 21 lock.unlock(); 22 } 23 } 24 } 25 }
死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
避免:不要写锁的嵌套
生产者消费者模式
等待和唤醒的方法
为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中
Object类的等待和唤醒方法:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法(使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法)
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程