多线程
CPU在多个软件间作高速切换
并发和并行
- 并行:在同一时刻,有多个指令在多个CPU上同时执行。
- 并发:在同一时刻,有多个指令在单个CPU上交替执行。
进程和线程
进程:是正在运行的软件
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
- 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的;
- 并发性:任何进程都可以同其他进程一起并发执行;
线程:是进程中的单个顺序控制流,是一条执行路径。
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序;
- 多线程:一个进程如果有多条执行路径,则称为多线程程序;
多线程的实现方案
- 继承Thread类的方式进行实现
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开始了"+i);
}
}
}
测试类:
public class ThreadTest {
public static void main(String[] args) {
//创建一个线程对象
MyThread t1 = new MyThread();
//创建一个线程对象
MyThread t2 = new MyThread();
//启动线程
t1.start();
//启动另一条线程
t2.start();
}
}
补充:
- 重写run()方法原因:因为run()是用来封装被线程执行的代码
- run()和start()的区别:
- run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程;
- start():启动线程,然后由JVM调用此线程的run()方法;
- 实现Runnable接口的方式进行实现
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了"+i);
}
}
}
public class RunnableTest {
public static void main(String[] args) {
//创建了一个参数对象
MyRunnable mr = new MyRunnable();
//创建了一个线程对象,并把参数传递给线程
//线程启动后,执行的就是参数里的run()方法
Thread t1 = new Thread(mr);
//开启线程
t1.start();
}
}
- 利用Callable和Future接口方式实现
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 1; i <= 100; i++) {
System.out.println("第"+i+"次浇100g水");
}
return "种树!!!";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后执行里面的call方法
MyCallable mc = new MyCallable();
//可以获取线程执行完毕之后的结果,也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
//开启线程
t1.start();
//获取线程结束之后的结果
String s = ft.get();
System.out.println(s);
}
}
三种实现方式的对比
优点 | 缺点 | |
---|---|---|
实现Runnable、Callable接口 | 扩展性强,实现该接口的同时,还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性较差,不能再继承其他的类 |
获取和设置线程名称
获取线程的名字
- String getName():返回此线程的名称
Thread类中设置线程的名字
- void setName(String name):将此线程的名称更改为等于参数name
- 通过构造方法也可以设置线程名称
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@@@" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//线程是有默认名字的,格式:Thread-编号
MyThread mt1 = new MyThread("阿花");
MyThread mt2 = new MyThread("阿岚");
//mt1.setName("阿花");
//mt2.setName("阿岚");
mt1.start();
mt2.start();
}
}
获得当前线程对象
- public static Thread currentThread():返回当前正在执行的线程对象的引用;
线程休眠
- public static void sleep(long time):让线程休眠指定的时间,单位为毫秒。
线程优先级
- public final void setPriority(int new Priority) 设置线程的优先级
- public final int getPriority() 获取线程的优先级
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
return "线程执行完毕了";
}
}
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) {
MyCallable mc1 = new MyCallable();
FutureTask<String> ft1 = new FutureTask<>(mc1);
Thread t1 = new Thread(ft1);
t1.setName("兔子");
//设置优先级(1~10)
t1.setPriority(10);
//获取优先级
System.out.println(t1.getPriority());
//t1.start();
MyCallable mc2 = new MyCallable();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2);
t2.setName("乌龟");
//设置优先级(1~10)
t2.setPriority(5);
//获取优先级
System.out.println(t2.getPriority());
//t2.start();
}
}
线程安全问题
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
-
格式:
synchronized(任意对象){
多条语句操作共享数据的代码
}
-
默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
-
当线程执行完出来了,所才会自动打开
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:耗费资源,降低程序运行效率
案例:买票
某电影院有三个售票窗口,共有100张票,设计一个程序模拟售票;
public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){ //多个线程必须使用同一把锁
if(ticket<=0){
//卖完了
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()+"正在买票,还剩"+ticket+"张票");
}
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
同步方法
同步方法就是把synchronized关键字加到方法上
-
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){}
同步代码块和同步方法的区别
- 同步代码块可以锁住指定的代码,同步方法是锁住方法中所有的代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
同步方法的锁对象:this
示例
public class MyRunnable implements Runnable {
private int ticketCount = 100;
@Override
public void run() {
while (true){
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMethod();
if (result == true){
break;
}
}
if("窗口二".equals((Thread.currentThread().getName()))){
//同步代码块
synchronized (this){
if(ticketCount == 0){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName()+"正在买票,还剩"+ticketCount+"张票");
}
}
}
}
}
private synchronized boolean synchronizedMethod() {
if(ticketCount == 0){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName()+"正在买票,还剩"+ticketCount+"张票");
return false;
}
}
}
public class RunnableTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
Lock锁
- void lock():获得锁
- void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- ReentrantLock():创建一个ReentrantLock的实例
死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行;
等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象notify()方法或notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
生产者和消费者
交易场所:
public class Desk {
//定义一个标记
//ture 就表示桌子上有汉堡包,允许吃货执行
//false 表示桌子上没有汉堡包,允许厨师执行
//public static boolean flag = false;
private boolean flag;
//汉堡包的总数量
//public static int count = 10;
private int count;
//锁对象
//public static final Object lock = new Object();
private final Object lock = new Object();
public Desk() {
this(false,10);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
public Desk(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
@Override
public String toString() {
return "Desk{" +
"flag=" + flag +
", count=" + count +
", lock=" + lock +
'}';
}
}
生产者:
public class Cook extends Thread {
Desk desk;
public Cook(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true){
synchronized (desk.getLock()){
if (desk.getCount() == 0){
break;
}else{
if (!desk.isFlag()){
//生产
System.out.println("给干饭王做汉堡");
desk.setFlag(true);
desk.getLock().notifyAll();
}else {
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
消费者:
public class Foodie extends Thread {
Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
//套路:
//while(true)死循环
//synchronized锁,锁对象是唯一
//判断共享数据是否结束,结束
//判断共享数据是否结束,没有结束
while (true){
synchronized (desk.getLock()){
if(desk.getCount() == 0){
break;
}else {
if (desk.isFlag()){
//有
System.out.println("开始干饭!!!");
desk.setFlag(false);
desk.getLock().notifyAll();
desk.setCount(desk.getCount()-1);
}else{
//没有就等待
//使用什么对象当锁,就必须使用这个对象去调用等待和唤醒的方法
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
/*消费者步骤:
* 1.判断桌子上是否有汉堡
* 2.如果没有就等待
* 3.如果有就开吃
* 4.吃完后,桌子上的汉堡就没了,唤醒等待的生产者继续生产
* 汉堡的总数量减一*/
/*生产者步骤:
* 1.判断桌子上是否有汉堡
* 2.如果有就等待,如果没有才生产
* 3.把汉堡放在桌子上
* 4.叫醒等待的消费者开吃*/
Desk desk = new Desk();
Foodie f = new Foodie(desk);
Cook c = new Cook(desk);
f.start();
c.start();
}
}
阻塞队列
阻塞队列继承结构
阻塞队列实现等待唤醒机制
BlockingQueue的核心方法:
-
put(anObject):将参数放入队列,如果放不进去会阻塞;
-
take():取出第一个数据,取不到会阻塞;
干饭王:
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
private ArrayBlockingQueue<String> list;
public Foodie(ArrayBlockingQueue<String> list) {
this.list = list;
}
@Override
public void run() {
while (true){
try {
String take = list.take();
System.out.println("干饭王从队列中获取了"+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
厨师:
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread {
private ArrayBlockingQueue<String> list;
public Cook(ArrayBlockingQueue<String> list) {
this.list = list;
}
@Override
public void run() {
while (true) {
try {
list.put("汉堡包");
System.out.println("厨师放了一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类:
import java.util.concurrent.ArrayBlockingQueue;
public class Zuse {
public static void main(String[] args) throws InterruptedException {
//阻塞队列的基本用法
//创建阻塞队列的对象,容量为1
ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);
//创建线程
Foodie f = new Foodie(list);
Cook c = new Cook(list);
f.start();
c.start();
}
}