多线程
1、区分单线程和多线程
- 单线程:就像是做饭,洗衣服,煮水,一个一个进行
- 多线程:在单线程的基础上,可以考虑煮水的时候,洗衣服节约时间
package com.kong.thread;
//创建线程,重写run方法,start方法开启线程
public class thread1 extends Thread {
@Override
public void run() {
//run方法线程体
for (int i = 0; i <1000 ; i++) {
System.out.println("你好");
}
}
public static void main(String[] args) {
thread1 thread1=new thread1();//创建线程
thread1.start();//start开启多线程
// thread1.run();//如果是run方法,只是将run方法运行,并不是开启多线程
for (int i = 0; i <1000 ; i++) {
System.out.println(i);
}
}
}
测试结果:如果是run方法,就是按照你好输出完毕再输出i,如果是start(多线程)方法,则run方法跟i交替输出
2、网图下载
- 首先我们要知道的是多线程的调度,是CPU在操作的,而代码执行过程中,下载的网图是先后顺序的,但是如果跟第一点写的煮水洗衣服,是不是煮水会先好,多线程也是如此,就是一个争取CPU调度的过程
- 我们编写一段代码来试着探讨一下吧
package com.kong.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//1.下载图片,需要一个下载的方法
//2.编写构造器
//3.new一个下载方法
//4.编写main方法下载图片
public class thread2 extends Thread {
private String url;
private String name;
public thread2(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
DownPicture downPicture=new DownPicture();
downPicture.down(url,name);
System.out.println(name+"下载完毕");
}
public static void main(String[] args) {
thread2 t1=new thread2("http://kr.shanghai-jiuxin.com/file/2021/0609/1af55d6d59a1624ff57f5f30e19eaa4d.jpg","逢考必过1");
thread2 t2=new thread2("http://kr.shanghai-jiuxin.com/file/2021/0609/1af55d6d59a1624ff57f5f30e19eaa4d.jpg","逢考必过2");
thread2 t3=new thread2("http://kr.shanghai-jiuxin.com/file/2021/0609/1af55d6d59a1624ff57f5f30e19eaa4d.jpg","逢考必过3");
//开启多线程
t1.start();
t2.start();
t3.start();
}
}
//图片下载方法
class DownPicture{
public void down(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 运行结果不难发现,三张相同的图片运行的顺序不一样,这是因为多线程的CPU调度,每个线程都在争取运行,导致相差不多的运行时间可以变化输出
3、Runable接口
启动线程方式:传入目标对象+Thread对象.start
package com.kong.thread;
public class runable implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
runable runable=new runable();//创建线程
new Thread(runable).start();//开启线程
for (int i = 0; i < 1000; i++) {
System.out.println("ni");
}
}
}
在Thread类里面,继承了runable接口
4、并发问题
package com.kong.thread;
//模拟购票
public class runable2 implements Runnable {
//定义一些票数
private int tickNum=10;
public void run() {
while(true){
if(tickNum<=1){
break;
}
//模拟购票操作所需时间
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到第"+tickNum--+"票");
}
}
public static void main(String[] args) {
runable2 runable2=new runable2();
new Thread(runable2,"小红").start();
new Thread(runable2,"小明").start();
new Thread(runable2,"小花").start();
}
}
注意:在模拟购票所需时间之后会出现一种一起同时购买同一张票的情况(主要原因是你还在买票时间,有人也在买这张票)
5、龟兔赛跑问题
package com.kong.thread;
public class race implements Runnable {
private String winner=null;
public void run() {
for (int i = 0; i <=100; i++) {
//模拟兔子睡觉
if(Thread.currentThread().getName()=="兔子"&&i%10==0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean b = gameOver(i);
if(b){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
}
private boolean gameOver(int foot){
if (winner!=null) {
return true;
}else if(foot>=100){
winner=Thread.currentThread().getName();
System.out.println(winner+"获得胜利");
return true;
}
return false;
}
public static void main(String[] args) {
race race=new race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
6、静态代理模式
package com.kong.thread;
public class staticProxy {
public static void main(String[] args) {
weddingCompany wedding=new weddingCompany(new you());
wedding.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//你去实现一个结婚的方法,此处的你为真实角色
class you implements Marry{
public void HappyMarry() {
System.out.println("你要结婚了");
}
}
//结婚公司是作为代理角色
class weddingCompany implements Marry{
private Marry target;
public weddingCompany(Marry target){
this.target=target;
}
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before(){
System.out.println("婚前准备");
}
private void after(){
System.out.println("婚后送客");
}
}
7、Lambda表达式
函数式接口的定义:
- 任何接口,如果只包含一个抽象方法,就称为函数式接口
- 对于函数式接口,我们可以通过lambda表达式创建该接口对象
package com.kong.thread;
public class lambda{
//静态内部类
// static class Like implements ILike{
// public void lambda() {
// System.out.println("你好啊");
// }
// }
public static void main(String[] args) {
//局部内部类
// class Like implements ILike{
// public void lambda() {
// System.out.println("你好啊");
// }
// }
// ILike like=new Like();
// like.lambda();
//匿名内部类,只能借助父类或者接口
// ILike like = new ILike() {
// public void lambda() {
// System.out.println("你好啊");
// }
// };
// like.lambda();
//lambda简化
ILike like=()->{
System.out.println("你好啊");
};
like.lambda();
}
}
//定义一个函数接口
interface ILike{
void lambda();
}
//实现类
//class Like implements ILike{
// public void lambda() {
// System.out.println("你好啊");
// }
//}
总结
- 就是一个继承接口之后的简化
- 只适用于函数式接口
8、线程状态
- 创建状态:new一个对象,线程对象一旦创建就进入新生状态
- 就绪状态:当用start()方法式,线程立即进入就绪状态,但不一定马上调度
- 阻塞状态:调用sleep,wait或者同步锁定,线程就会进入阻塞章台,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu的调度
- 执行状态:此处才是线程真正执行线程体的代码块
- 死亡状态:线程中断或者结束,都会让线程进入死亡状态,并且不可再重新启动
8.1、线程停止
- 建议线程正常停止—>利用次数,不建议死循环
- 建议使用标志位
- 不建议使用stop或者destroy
package com.kong.thread;
public class threadStop implements Runnable {
//设置一个标识位
private boolean flag=true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println(i++);
}
}
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
threadStop threadStop=new threadStop();
for (int i = 0; i < 100; i++) {
System.out.println(i);
if(i==90){
threadStop.stop();
System.out.println("此线程停止");
}
}
}
}
8.2、线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间到达后进程回进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
package com.kong.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
public class timeSleep {
public static void main(String[] args) {
tenDown();
}
public static void tenDown() {
// //倒计时
// int num = 10;
// while (true) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(num--);
// if (num <= 0) {
// break;
// }
// }
// }
//打印系统当前的事件
Date date = new Date(System.currentTimeMillis());//获取当前系统时间
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
}
}
}
作用:放大事件可能发生的概率
8.3、线程礼让
- 礼让线程,让当前正在执行的线程暂停,但是不阻塞
- 运行转为就绪
- cpu重新调度
package com.kong.thread;
public class threadYeid {
public static void main(String[] args) {
Myyied myyied = new Myyied();
new Thread(myyied,"a").start();
new Thread(myyied,"b").start();
}
}
class Myyied implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止");
}
}
结论:礼让不一定能成功
8.4、线程强制执行join(插队)
package com.kong.thread;
public class threadjoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
threadjoin threadjoin=new threadjoin();
Thread thread = new Thread(threadjoin);
thread.start();
for (int i = 0; i <50 ; i++) {
if(i==20){
thread.join();
}
System.out.println("停止");
}
}
}
8.5、线程状态观察
package com.kong.thread;
public class state {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ddd");
});
//观察状态
Thread.State state=thread.getState();
System.out.println(state);
//启动后观察
thread.start();
state=thread.getState();
System.out.println(state);
while(state!=Thread.State.TERMINATED){
Thread.sleep(100);
state=thread.getState();//更新线程状态
System.out.println(state);
}
}
}
8.6、线程优先级
- 范围:1~10
- 使用下列方法获取优先级
- getPriority setPriority
- 注意一点,一定要先设置优先级再启动线程
8.7、守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不需要等到守护线程执行完毕
package com.kong.thread;
import java.lang.management.ThreadMXBean;
//妈妈守护孩子,两个角色
public class daemon {
public static void main(String[] args) {
mother mother=new mother();
son son=new son();
Thread thread=new Thread(mother);
thread.setDaemon(true);//默认是false,是用户进程
thread.start();//守护线程开启
new Thread(son).start();
}
}
//妈妈
class mother implements Runnable{
@Override
public void run() {
while(true){
System.out.println("守护");
}
}
}
//儿子
class son implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("被守护");
}
}
}
9、线程同步机制(三大不安全案例)
形成的条件:队列+锁
就像是排队上厕所,排队是队列,锁是门,只有这样才能保证线程的安全
- 买票(存在多个人同时购买一张票的情况)
package com.kong.thread;
//模拟购票
public class runable2 implements Runnable {
//定义一些票数
private int tickNum=10;
public void run() {
while(true){
if(tickNum<=1){
break;
}
//模拟购票操作所需时间
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到第"+tickNum--+"票");
}
}
public static void main(String[] args) {
runable2 runable2=new runable2();
new Thread(runable2,"小红").start();
new Thread(runable2,"小明").start();
new Thread(runable2,"小花").start();
}
}
- 银行取钱(两人同时把钱取出,造成的不安全取款)
package com.kong.thread;
//模拟两人去银行取钱
public class marry {
public static void main(String[] args) {
//账户
Account account=new Account(100,"旅游基金");
bink kongkong=new bink(account,50,"小明");
bink yuanyuan=new bink(account,100,"小红");
kongkong.start();
yuanyuan.start();
}
}
//账户信息
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟银行
class bink extends Thread{
Account account;
int raceMoney;//取出的钱
int nowMoney;//现在的钱
public bink(Account account, int raceMoney, String name) {
super(name);
this.account = account;
this.raceMoney = raceMoney;
}
@Override
public void run() {
//取钱操作
try {
Thread.sleep(1000);//模拟取钱时间
} catch (InterruptedException e) {
e.printStackTrace();
}
if(account.money-raceMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够取了");
return;
}
account.money=account.money-raceMoney;//账户的余额
nowMoney=nowMoney+raceMoney;//当前获得到的钱数
System.out.println(account.name+"剩下"+account.money);
System.out.println(Thread.currentThread().getName()+"拿到了"+nowMoney);
}
}
- 不安全的线程集合(存在同一个空间存放两个)
package com.kong.thread;
import java.util.ArrayList;
import java.util.List;
public class threadIO {
public static void main(String[] args) throws InterruptedException {
List<String> list=new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->
{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(300);
System.out.println(list.size());
}
}
10、同步方法和同步块
-
同步方法
- 我们通过加synchronized关键字修饰即可将方法变为同步方法
- 相对于队列加锁
- 弊端:方法里面需要修饰的内容需要锁,但是锁的太多,浪费资源
-
同步块
- synchronized(Obj){}
- Obj称为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,同步方法的监视器就是this,就是这个对象本身,或者class
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,任何锁定并访问
-
修改购票为同步方法
public synchronized void run() {
while(true){
if(tickNum<=0){
break;
}
//模拟购票操作所需时间
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到第"+tickNum--+"票");
}
}
- 修改结婚基金
因为锁的是this,本质来说就是总存款像厕所,我们要把厕所锁起来,而排队跟线程就是如何利用这个锁(厕所)
@Override
public void run() {
//取钱操作
synchronized (account) {
if(account.money-raceMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够取了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money=account.money-raceMoney;//账户的余额
nowMoney=nowMoney+raceMoney;//当前获得到的钱数
System.out.println(this.getName()+"拿到了"+nowMoney);
System.out.println(account.name+"剩下"+account.money);
}
}
- 多线程集合方式
List<String> list=new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->
{
synchronized (list){
list.add(Thread.currentThread().getName());//锁住的是list对象
}
}).start();
}
集合的安全方法CopyOnWriteArrayList
package com.kong.thread;
import java.util.concurrent.CopyOnWriteArrayList;
public class threadsyn {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->
{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(300);
System.out.println(list.size());
}
}
11、死锁
死锁原因:多个线程各自占有一些共享资源,并且要等待占有资源运行完毕,才能让下一个线程占有;而导致两个或者两个以上同时占有同一个资源情况,称为死锁
package com.kong.thread;
public class deadLock{
public static void main(String[] args) {
people kongkong=new people(0,"kongkong");
people yuanyuan = new people(1, "yuanyuan");
kongkong.start();
yuanyuan.start();
}
}
//食物
class food{
}
//饮水
class drink{
}
class people extends Thread {
static food f = new food();
static drink d = new drink();
int choice;//选择
String name;
people(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
person();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void person() throws InterruptedException {
if (choice == 0) {
synchronized (f) {
System.out.println(this.name + "获得食物");
Thread.sleep(1000);
}
synchronized (d) {
System.out.println(this.name + "获得饮水");
}
} else {
synchronized (d) {
System.out.println(this.name + "获得食物");
Thread.sleep(2000);
}
synchronized (f) {
System.out.println(this.name + "获得饮水");
}
}
}
}
当两件事情同时放在同一个同步方法的时候,就会产生死锁,解决办法就是将事情放出来,让另一个线程拿到。
死锁产生条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求和保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放
- 不剥夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
12、Lock锁
Lock锁和synchronized对比
- Lock是显示锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块和方法锁
- Lock锁,Jvm将花费较少的事件来调度线程,性能更好,并且有更好的扩展性
- 优先级:Lock>同步代码块>同步代码方法
package com.kong.thread;
import java.util.concurrent.locks.ReentrantLock;
//模拟购票
class runable2 implements Runnable {
//定义一些票数
private int tickNum=100;
//定义lock锁
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try{
lock.lock();
if(tickNum>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到第"+tickNum--+"票");
}else {
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
public static void main(String[] args) {
runable2 runable2=new runable2();
new Thread(runable2,"小红").start();
new Thread(runable2,"小明").start();
new Thread(runable2,"小花").start();
}
}
13、线程协作
角色:生产者,消费者
背景:生产者生产东西,消费者拿东西,消费者拿东西的时候,生产者还没生产好,这时候我们就提出一个疑问,能不能让消费者知道生产者做完了,或者生产者知道消费者需要来拿东西了?
解决方案
- 管程法:在生产者和消费者之间新建一个缓冲区,这样就可以把东西储存到缓冲区,消费者看到就可以直接取,取不到,生产者马上生产
- 信号灯法:我们可以在生产者或者消费者设置一个true,当为true时,不给予通过,等待判断生产者有东西让消费者拿的时候,再将其放出
13.1、管程法
package com.kong.thread;
public class testPC {
public static void main(String[] args) {
syn container=new syn();
new Product(container).start();
new Consumer(container).start();
}
}
//生产者
class Product extends Thread{
syn container;
public Product(syn container){
this.container=container;
}
//生产
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"只鸡");
container.push(new Chicken(i));//生产鸡
}
}
}
//消费者
class Consumer extends Thread{
syn container;
public Consumer(syn container){
this.container=container;
}
//消费
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id){
this.id=id;
}
}
//缓冲区
class syn{
//需要一个容器大小
Chicken[] chickens=new Chicken[10];
//容器计数器
int count=0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,就需要等待消费者消费
while (count==10){
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,需要丢入产品
chickens[count] = chicken;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断是否消费
if(count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
//吃完通知生产者生产
this.notifyAll();
return chicken;
}
}
13.2、信号灯法
package com.kong.thread;
public class testPC2 {
public static void main(String[] args) {
TV tv=new TV();
new Player(tv).start();
new Watch(tv).start();
}
}
//演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
this.tv.play("金鹰卫视");
}else {
this.tv.play("儿童频道");
}
}
}
}
//观众
class Watch extends Thread{
TV tv;
public Watch(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//节目
class TV{
String voice;
boolean flag=true;
//表演
public synchronized void play(String voice) {
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了" + voice);
//通知观众观看
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
//观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了"+voice);
//通知演员表演
this.notifyAll();
this.flag=!this.flag;
}
}
14、线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,用完放回,避免频繁创建销毁,实现重复利用
优点
- 提高响应速度
- 减低资源消耗
- 便于线程管理
使用
- ExecutorService:真正的线程池接口
- Executors:工具类、线程池的工厂类,用于创建返回的线程池
package com.kong.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class testPool {
public static void main(String[] args) {
//创建线程池
//newFixedThreadPool线程池大小
ExecutorService service= Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}