多线程学习
一、 多线程
1. 并行与并发
并行:同一时刻,有多个指令在多个CPU上同时执行
并发:同一时刻,有多个指令在单个CPU上交替执行
2.进程和线程
进程:正在运行的软件
线程:进程中的单个顺序控制流,是一条执行路径
3. *****多线程的实现方式
3.1 继承Thread类的方式进行实现
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启"+i);
}
}
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
t1.start();
t2.start();
}
}
- 为什么要重写run()方法?
多线程开启之后执行run()方法,run()是用来封装被线程执行的代码 - run()方法和start()方法的区别?
run方法调用不开启线程,start开启线程
3.2 实现Runnable接口方式进行实现
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了"+i);
}
}
public static void main(String[] args) {
MyRunnable mr=new MyRunnable();
Thread t1=new Thread(mr);
t1.start();
MyRunnable mrs=new MyRunnable();
Thread t2=new Thread(mrs);
t2.start();
}
}
3.3 利用Callable和Future接口方式实现
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 50; i++) {
System.out.println("向女孩表白"+i);
}
return "答应";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mt=new MyCallable();
FutureTask<String> ft=new FutureTask<String>(mt);
Thread t=new Thread(ft);
t.start();
String s = ft.get();
System.out.println(s);
}
}
get方法在线程开启之后调用
三种方式对比
3.4 设置获取线程名字
- 获取线程名字
public class Mythread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"****"+i);
}
}
public static void main(String[] args) {
Mythread mt=new Mythread();
mt.start();
Mythread ss=new Mythread();
ss.start();
}
}
- 设置线程名字
- set形式
Mythread mt=new Mythread();
mt.start();
mt.setName("线程1");
Mythread ss=new Mythread();
ss.start();
ss.setName("线程2");
- 构造器形式
public class Mythread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"****"+i);
}
}
public Mythread(String name) {
super(name);
}
public static void main(String[] args) {
Mythread mt=new Mythread("线程1");
mt.start();
Mythread ss=new Mythread("线程2");
ss.start();
}
}
4.获得线程对象
在runnable中可以获取线程对象名字
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"running"+i);
}
}
public static void main(String[] args) {
// MyRunnable myRunnable=new MyRunnable();
// Thread thread=new Thread(myRunnable);
// thread.setName("666");
// thread.start();
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
5.线程休眠
Thread.sleep(3000);
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"running"+i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6.线程调度[线程的优先级]
线程的优先级用数字表示,范围从1~10
Thread.MIN_PRIORITY = 1; 线程获得最小优先级
Thread.MAX_PRIORITY = 10; 线程获得最大优先级
Thread.NORM_PRIORITY = 5; 分配给线程的默认优先级
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"***"+i);
}
return "线程执行结束";
}
public static void main(String[] args) {
MyCallable mc=new MyCallable();
FutureTask<String> ft=new FutureTask<>(mc);
Thread thread=new Thread(ft);
thread.start();
thread.setPriority(2);
thread.setName("飞机");
// System.out.println(thread.getPriority());
MyCallable mc2=new MyCallable();
FutureTask<String> ft2=new FutureTask<>(mc2);
Thread t2=new Thread(ft2);
t2.start();
t2.setPriority(10);
t2.setName("坦克");
// System.out.println(t2.getPriority());
}
}
7.后台线程/守护线程
普通线程结束 守护线程也随之结束
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"****"+i);
}
}
public MyThread(String name) {
super(name);
}
public static void main(String[] args) {
MyThread mt=new MyThread("女神");
mt.start();
//当普通线程执行完后 守护线程也没有继续运行下去的必要了
MyThread2 t2=new MyThread2("备胎");
t2.setDaemon(true);//该线程为守护线程
t2.start();
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"****"+i);
}
}
public MyThread2(String name) {
super(name);
}
}
二、 线程安全问题
1. 案例
public class Ticket implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()+"在卖票,"+"还剩"+ticket+"张票");
}
}
}
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("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
出现问题:窗口卖了 同一张票给多个人 且出现了负票的现象,后面使用同步方法块进行同步
2. 同步代码块
private Object obj=new Object();
synchronized(obj){}
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 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("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
通过同步代码块解决上述问题,没有重复票和负号票
3. 锁对象唯一
synchronized中的锁对象要唯一
public class MyThread extends Thread{
private static int ticketCount = 100;
private static Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){ //当前线程对象
if (ticketCount <= 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
}
}
}
}
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}
4. 同步方法
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
同步方法的锁对象是this
synchronized的使用
- 同步代码块的地方
- 直接加在方法上;变成同步方法
public class Myrunnable implements Runnable {
private int ticketCount = 100;
@Override
public void run() {
while (true) {
if ("窗口1".equals(Thread.currentThread().getName())) {
//同步方法
boolean result = synchronizedMthod();
if (result) {
break;
}
}
if ("窗口2".equals(Thread.currentThread().getName())) {
synchronized (this) {
if (ticketCount == 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票," + "还剩" + ticketCount + "张票");
}
}
}
}
}
private synchronized boolean synchronizedMthod() {
if (ticketCount == 0) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票," + "还剩" + ticketCount + "张票");
return false;
}
}
public static void main(String[] args) {
Myrunnable mr=new Myrunnable();
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}
5. 同步静态方法
同步静态方法的锁对象是 类名.class
public class MyRunnable implements Runnable {
private static int ticketCount = 100;
@Override
public void run() {
while (true) {
if ("窗口1".equals(Thread.currentThread().getName())) {
//同步方法
boolean result = synchronizedMethod();
if (result) {
break;
}
}
if ("窗口2".equals(Thread.currentThread().getName())) {
//同步代码块
synchronized (MyRunnable.class) {
if (ticketCount == 0) {
break;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
}
}
}
}
}
private static 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 static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}
6. Lock锁
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int ticket = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
// synchronized (obj){
if (ticket <= 0) {
break;
} else {
Thread.sleep(10);
ticket--;
System.out.println(Thread.currentThread().getName() + "在卖票," + "还剩" + ticket + "张票");
// }
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
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("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
7. 死锁
死锁eg
public class Test {
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("A running");
}
}
}
}).start();
new Thread(()->{
while (true){
synchronized (objB){
synchronized (objA){
System.out.println("B running");
}
}
}
}).start();
}
}
三、生产者与消费者
生产者消费模式是一个十分经典的多线程协作模式,假设有 厨师生产汉堡,顾客吃汉堡,以及桌子放汉堡这样一个场景,进行多线程实现。
1. 生产者与消费者_代码实现
public class Chef extends Thread{
/**
* 生产者步骤
* 判断是否桌子上有汉堡
* 如果有就等待,没有就生产
* 将汉堡放在桌子上
* 唤醒消费者吃汉堡
*/
@Override
public void run() {
while (true){
synchronized (Desk.lock){
//没有汉堡包了
if (Desk.count==0){
break;
}else{
//桌子上没有汉堡包就生产
if (!Desk.flag){
System.out.println("厨师正在生产汉堡包");
Desk.flag=true;
Desk.lock.notifyAll();
}else{
//桌子上有汉堡包,等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Costumer extends Thread{
/**
* 消费者步骤
* 判断桌子上是否有汉堡
* 如果没有就等待
* 如果有就开吃
* 吃完之后,桌子上的汉堡没有了
* 唤醒等待的生产者生产汉堡
* 汉堡总数-1
*/
@Override
public void run() {
//套路: 1.while(true) 死循环
//2.synchronized() 锁对象要唯一
//3.判断 共享数据是否结束 结束
//4.判断 共享数据是否结束 没有结束
while (true){
synchronized(Desk.lock){
//还有没有汉堡包
if (Desk.count==0){
break;
}else{
//桌子上有汉堡包
if (Desk.flag){
System.out.println("顾客在吃汉堡包");
Desk.flag=false;
Desk.lock.notifyAll();
Desk.count--;
}else{
//桌子上没有汉堡包,就等待
//锁对象来调用wait()
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Desk {
//定义一个标记
//判断桌子上是否有汉堡
//true 有汉堡包 顾客执行
//false 没有汉堡包 厨师执行
public static boolean flag = false;
// 汉堡包总数量
public static int count = 10;
//锁对象
public static final Object lock = new Object();
}
public class Demo {
/**
* 消费者步骤
* 判断桌子上是否有汉堡
* 如果没有就等待
* 如果有就开吃
* 吃完之后,桌子上的汉堡没有了
* 唤醒等待的生产者生产汉堡
* 汉堡总数-1
*/
/**
* 生产者步骤
* 判断是否桌子上有汉堡
* 如果有就等待,没有就生产
* 将汉堡放在桌子上
* 唤醒消费者吃汉堡
*/
public static void main(String[] args) {
Costumer costumer=new Costumer();
Chef chef=new Chef();
chef.start();
costumer.start();
}
}
2. 生产者与消费者_代码改进实现
public class Desk {
//定义一个标记
//判断桌子上是否有汉堡
//true 有汉堡包 顾客执行
//false 没有汉堡包 厨师执行
// public static boolean flag = false;
// 汉堡包总数量
// public static int count = 10;
// public static final Object lock = new Object();
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;
}
@Override
public String toString() {
return "Desk{" +
"flag=" + flag +
", count=" + count +
", lock=" + lock +
'}';
}
}
public class Chef extends Thread{
private Desk desk;
public Chef(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 Costumer extends Thread{
private Desk desk;
public Costumer(Desk desk) {
this.desk=desk;
}
/**
* 消费者步骤
* 判断桌子上是否有汉堡
* 如果没有就等待
* 如果有就开吃
* 吃完之后,桌子上的汉堡没有了
* 唤醒等待的生产者生产汉堡
* 汉堡总数-1
*/
@Override
public void run() {
//套路: 1.while(true) 死循环
//2.synchronized() 锁对象要唯一
//3.判断 共享数据是否结束 结束
//4.判断 共享数据是否结束 没有结束
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{
//桌子上没有汉堡包,就等待
//锁对象来调用wait()
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Demo {
/**
* 消费者步骤
* 判断桌子上是否有汉堡
* 如果没有就等待
* 如果有就开吃
* 吃完之后,桌子上的汉堡没有了
* 唤醒等待的生产者生产汉堡
* 汉堡总数-1
*/
/**
* 生产者步骤
* 判断是否桌子上有汉堡
* 如果有就等待,没有就生产
* 将汉堡放在桌子上
* 唤醒消费者吃汉堡
*/
public static void main(String[] args) {
Desk desk=new Desk();
Costumer costumer=new Costumer(desk);
Chef chef=new Chef(desk);
chef.start();
costumer.start();
}
}
3. 阻塞队列_基本写法
public class Demo_ {
public static void main(String[] args) throws InterruptedException {
//阻塞队列实现
//创建阻塞队列对象,容量为2
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(2);
//存储元素
arrayBlockingQueue.put("汉堡包");
arrayBlockingQueue.put("热狗");
//取元素
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
//因为队列中只有两个元素 所以当取出后 现在为阻塞状态
System.out.println(arrayBlockingQueue.take());
System.out.println("程序运行结束");
}
}
4. 阻塞队列_实现唤醒阻塞
ArrayBlockingQueue 的take和put方法中的源码是加了锁的
实现代码
public class Chef extends Thread{
private ArrayBlockingQueue<String> list;
public Chef(ArrayBlockingQueue<String> list) {
this.list=list;
}
@Override
public void run() {
while (true) {
try {
list.put("汉堡包");
System.out.println("Chef 放了一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Costumer extends Thread{
private ArrayBlockingQueue<String> list;
public Costumer(ArrayBlockingQueue<String> list) {
this.list=list;
}
@Override
public void run() {
while (true){
try {
String take = list.take();
System.out.println("Costumer 吃了一个"+ take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
//创建一个阻塞队列 容量为1
ArrayBlockingQueue<String> list=new ArrayBlockingQueue<>(1);
//创建线程并开启
Costumer costumer=new Costumer(list);
Chef chef=new Chef(list);
chef.start();
costumer.start();
}
}
打印结果:
这里打印有问题的原因在于:输出语句没有在锁中,因此打印可能不会呈现效果,但是程序是正常执行的,获取锁然后释放锁