JAVA多线程常用的概念解释,以及代码案例实现
目录
1、概念
多线程:
具有多线程能力的计算机因有硬件的支持而能够在同一时间执行多个线程,从而达到提高性能的目的。
并发和并行:
并行是指在同一时刻,多个指令在多个CPU上同时执行。
并发是指在同一时刻,多个指令在单个CPU上交替执行。
进程和线程:
进程:指操作系统中正在运行的一个应用程序。
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性:进程的实质时程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
线程:是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则称为单线程程序。
多线程:一个进程如果有多条执行路径,则称为多线程程序。
2、多线程的实现方式
第一种:继承thread类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了" + i);
}
}
}
测试类(两个线程交替执行)
public class TestMyThread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
run方法中呢就是我们需要执行的内容,表示创建对象调用的方法,并不会开启线程。
start方法就能启动线程。
第二种:实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了" + i);
}
}
}
实现类
public class TestMyThread {
public static void main(String[] args) {
//创建一个参数的对象
MyRunnable mr = new MyRunnable();
//将参数传递给线程
Thread t1 = new Thread(mr);
//线程启动执行的就是这个参数对象里面的run方法
t1.start();
}
}
第三种:使用callable和Future
public class MyCallable implements Callable<String> {
@Override
public String call() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了" + i);
}
//返回值表示线程运行完之后的结果
return "完了";
}
}
实现类
public class TestMyThread {
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类中的方法 | 可扩展性较差,不能再继承别的类 |
3、Thread类中的几种方法
获取当前线程对象
- public static Thread currentThread():返回当前正在执行的线程对象的引用
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"线程开启了" + i);
}
}
}
线程休眠
- public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);//当前线程执行1秒一次
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程开启了" + i);
}
}
守护线程
- setDaemon(true):开启了守护线程后,它会随之普通进程结束而结束,但不是立即结束。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"线程开启了" + i);
}
}
}
public class MyRunnable2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"线程开启了" + i);
}
}
}
实现类(执行后你会发现,天狗没执行完,它总是再女神执行完之后,过一会就停)
public class TestMyThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyRunnable mr1 = new MyRunnable();
MyRunnable2 mr2 = new MyRunnable2();
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
t1.setName("女神");
t2.setName("天狗");
t2.setDaemon(true);
t1.start();
t2.start();
}
}
4、线程中的优先级
首先了解什么是线程调度?
线程调度:
分时调度模型:所有线程轮流使用CPU,平均分配每个线程占用CPU的时间;
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么随机选择,优先级高的线程获取CPU的时间相对多一点。
java中是抢占式调度模型
set.setPriority(int num):可以设置参数的值,值越大优先级越高,范围是1~10
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"线程开启了" + i);
}
}
}
实现类(你会发现线程执行过程,大部分情况都是优先级高的那个线程抢占CPU成功率高)
public class TestMyThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyRunnable mr1 = new MyRunnable();
Thread t1 = new Thread(mr1);
t1.setName("汤姆");
t1.setPriority(10);
Thread t2 = new Thread(mr1);
t2.setName("杰瑞");
t2.setPriority(1);
t1.start();
t2.start();
}
}
5、线程的生命周期
6、线程安全问题-买票案例实现
需求:某电影院卖100张电影票,共有三个窗口在卖,请设计一个程序卖票。
public class Ticket implements Runnable{
private int ticketNum = 100;
@Override
public void run(){
while (true){
if(ticketNum<=0){
//卖完了
break;
}else {
ticketNum--;
System.out.println(Thread.currentThread().getName()+"在卖票,还剩"+ticketNum+"张");
}
}
}
}
实现
public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
执行后发现有相同的票和负号的票
同票原因:
由于一个线程在执行了ticketNum--操作时,此刻当它还未执行打印操作而另一个线程就也执行了ticketNum--,所以两者打印结果票数相同。
负票原因:
同样是由于,当最后一张票即ticketNum=1的时候,同时有多个线程进入了方法,即执行多次ticketNum--。
解决办法:
1.同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式
synchronized(任意对象){
多条语句操作共享数据的代码
}
- 默认情况是打开的,只要有一个线程进去执行了,锁就会关闭。
- 当线程执行完出来了,锁才会自动打开。
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步块上的锁,这个是耗费资源的,无形中降低了程序的运行效率。
案例
public class Ticket implements Runnable {
private int ticketNum = 100;
private final Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticketNum == 0) {
//卖完了
break;
} else {
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketNum + "张");
}
}
}
}
}
2、同步方法
即将synchronized关键字加到方法上
格式:
修饰符synchronized返回值类型方法名(方法参数){}
同步代码块和同步方法的区别:
- 同步代码块可以锁住指定代码,同步方法是锁住同步方法里面的所有代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
同步方法的锁对象是啥呢?
- this
public class Ticket implements Runnable {
private int ticketNum = 100;
private final Object obj = new Object();
@Override
public void run() {
while (true) {
if ("窗口一".equals(Thread.currentThread().getName())) {
boolean result = synchronizedMethod();
if (result) {
break;
}
}
if ("窗口二".equals(Thread.currentThread().getName())){
synchronized(this){
if (ticketNum == 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketNum + "张");
}
}
}
}
}
private synchronized boolean synchronizedMethod() {
if (ticketNum == 0) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketNum + "张");
return false;
}
}
}
实现
public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
}
同步静态方法
同步静态方法:就是把synchronized关键字加到静态方法上
格式:
修饰符static synchronized返回值类型 方法名(方法参数){}
同步静态方法的锁对象是啥?
类名.class
案例:
public class Ticket implements Runnable {
private static int ticketNum = 100;
private final Object obj = new Object();
@Override
public void run() {
while (true) {
if ("窗口一".equals(Thread.currentThread().getName())) {
boolean result = synchronizedMethod();
if (result) {
break;
}
}
if ("窗口二".equals(Thread.currentThread().getName())){
synchronized(Ticket.class){
if (ticketNum == 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketNum + "张");
}
}
}
}
}
private static synchronized boolean synchronizedMethod() {
if (ticketNum == 0) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketNum + "张");
return false;
}
}
}
demo略,和上面同步方法相同。
3、LOCK锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了能更清晰地表达如何加锁和释放锁,JDK5之后提供了新的锁对象LOCK。
LOCK实现比使用synchronized方法和语句可以获得更广泛的锁定操作。
- void lock():获得锁
- void unlock:释放锁
Lock接口不能直接实例化,这里采用的是实现类ReentrantLock来实例化
ReetrantLock():创建一个ReentrantLock的实例。
public class Ticket implements Runnable {
private int ticketNum = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticketNum <= 0) {
break;
} else {
Thread.sleep(100);
ticketNum--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketNum + "张");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
demo略,和上面同步方法相同
7、死锁的概念
死锁是由于两个或多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(() -> {
while (true) {
synchronized (objA) {
synchronized (objB) {
System.out.println("小明同学");
}
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (objB) {
synchronized (objA) {
System.out.println("小康同学正在走路");
}
}
}
}).start();
}
}
解决办法:不要写锁的嵌套。
8、生产者和消费者思路分析及代码实现
案例分析图示
等待和唤醒的方法
为了体现生产和消费过程中的等待和唤醒,Java提供了几个方法,这几个方法在Object类中。
方法名 | 说明 |
void mian() | 导致当前线程等待,直到另一个线程调用该对象的notifyAll()方法。 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
案例实现
桌子:
public class Desk {
//定义一个标记,true表示桌子上有汉堡,false没有
private boolean flag;
//厨师一共做汉堡的总数量
private int count;
//锁对象
private final Object lock = new Object();
public Desk(){
this(false,10);
}
public Desk(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
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 class Foodie extends Thread {
private Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
//模拟对应上图消费者
//while(true)死循环
//synchronized上锁(锁对象唯一)
//判断,共享数据是否结束了
while (true) {
//有锁就等待
synchronized (desk.getLock()) {
//判断厨师是不是10个做完了已经
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 Cooker extends Thread {
private Desk desk;
public Cooker(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 Demo {
public static void main(String[] args) {
//厨师和顾客各是一个线程,但是共享Desk数据
Desk desk = new Desk();
Foodie f = new Foodie(desk);
Cooker c = new Cooker(desk);
f.start();
c.start();
}
}