多线程
文章目录
1.线程,进程,多线程
- 多线程:多条执行路径,主线程与子线程并行交替执行(普通方法只有主线程一条路径)。
- 程序:指令和数据的有序集合,本身没有任何运行的含义,是一个静态的概念。
- 进程:在操作系统中运行的程序就是进程,即执行程序的一次执行过程,是一个动态的概念。
- 一个进程可以有多个线程,比如视频中同时听声音,看图像,看弹幕等。
- 线程是CPU调度和执行的基本单位。
注意:
很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,
cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使自己没有创建线程,后台也会有多个线程,比如主线程,GC线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行是由调度器(cpu)安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
- 对同一份资源操作时 会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2.线程创建
三种方式:
- 继承Thread类(重点)
- 实现Runnable接口(重点)
- 实现Callable接口(了解)
方法1:Thread类
- 自定义线程类 继承
Thread
类 - 重写
run()
方法,编写线程执行体 - 创建线程对象,调用
start()
方法启动线程
package demo01;
//调用多线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread1 extends Thread{
@Override
public void run() { //多线程方法体
for (int i = 0; i < 50; i++) {
System.out.println("我在学习多线程");
}
}
//main方法,主线程
public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start方法开启线程
testThread1.start();
for (int i = 0; i < 50; i++) {
System.out.println("我在看代码");
}
}
}
运行结果其实是 我在学习多线程 和 我在看代码 交替执行,因为一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉,实质上是交替执行。
问题:利用多线程同时下载多张照片
package demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//利用多线程同时下载图片
public class TestThread2 extends Thread{
private String url; //图片网络链接
private String name; //保存文件名
//有参构造
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDoanloader webDoanloader = new WebDoanloader();
webDoanloader.doanloader(url,name);
}
//定义一个下载器(一个类里面写下载方法)
class WebDoanloader{
//下载方法
public void doanloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
System.out.println("下载名为:"+name);
} catch (IOException e) {
System.out.println("文件下载异常");
}
}
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=sG3Ey32h&id=849A1B58327DE85B2DA9F594D962FDB0A7F2A8C2&thid=OIP.sG3Ey32h9wi9SNVwOoYTugHaNK&mediaurl=https%3a%2f%2fpic3.zhimg.com%2fv2-e97611f0a61c75662afaf15ed03002ce_r.jpg&exph=1920&expw=1080&q=%e5%b0%8f%e5%a7%90%e5%a7%90%e5%9b%be%e7%89%87&simid=608006123438289425&FORM=IRPRST&ck=93253D905D95339FEA3A64CD76DEFC04&selectedIndex=10", "1.jpg");
TestThread2 t2= new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=r9%2bCBVXH&id=E4F0F2392385789627B34CB97C7E157B053073F7&thid=OIP.r9-CBVXH8kmTv1t21TUL-QHaQD&mediaurl=https%3a%2f%2fpic1.zhimg.com%2fv2-afdf820555c7f24993bf5b76d5350bf9_r.jpg%3fsource%3d1940ef5c&exph=2340&expw=1080&q=%e5%b0%8f%e5%a7%90%e5%a7%90%e5%9b%be%e7%89%87&simid=608005169961369771&FORM=IRPRST&ck=B8176D685EECEB8A4F2FAD8D9DC6178C&selectedIndex=23", "2.jpg");
TestThread2 t3 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=fjh5iqpq&id=4E711ABF8F13BEE4E8E3DB1669AE8490C80FDB3B&thid=OIP.fjh5iqpqLE8pHaGfce3VFwHaNK&mediaurl=https%3a%2f%2fpic4.zhimg.com%2fv2-9e0efd1f631f806afa0aa97b16a3a010_r.jpg%3fsource%3d1940ef5c&exph=1920&expw=1080&q=%e5%b0%8f%e5%a7%90%e5%a7%90%e5%9b%be%e7%89%87&simid=608017642549226249&FORM=IRPRST&ck=36CDED3ABB8E0572351E5B3CC1EDACE3&selectedIndex=19", "3.jpg");
t1.start();
t2.start();
t3.start();
//交替运行,每次的结果都不一样
}
}
package demo01;
//创建线程
public class Download extends Thread {
private String url; // 文件网络地址(URL)
private String name; // 保存的文件名
// 有参构造器,用来给URL和 name赋值
public Download(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
//在重写的run()方法中,调用下载器
new WebDownload().download(url,name);
}
public static void main(String[] args) {
new Download("https://cn.bing.com/images/search?q=%E5%9B%BE%E7%89%87&FORM=IQFRBA&id=49E031AAC7715C25D8E03215367A7B4A50E14354","1").start();
new Download("https://cn.bing.com/images/search?q=%E5%9B%BE%E7%89%87&FORM=IQFRBA&id=1C8280D2D75B8653FE4F87817387F57189B5AA41","2").start();
new Download("https://cn.bing.com/images/search?q=%E5%9B%BE%E7%89%87&FORM=IQFRBA&id=1C8280D2D75B8653FE4F94F7C32E1B68E30CC400","3").start();
}
}
/*
package demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//自定义一个下载器
public class WebDownload {
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
System.out.println("文件"+name+"下载完成");
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件下载出错!");
} finally {
}
}
}
*/
方法2:Runnable接口
- 自定义线程类实现
Runnable
接口 - 重写
run()
方法,编写线程执行体 - 创建线程对象,调用
start()
方法启动对象 - 出现的问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
推荐使用Runnable对象,因为Java单继承的局限性
package demo02;
//创建线程第二种方法:实现runnable接口
public class TestRunnable implements Runnable{
@Override
public void run() { //run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在学习Runnable接口");
}
}
public static void main(String[] args) {
//创建一个线程对象
TestRunnable testRunnable = new TestRunnable();
//开启线程对象来start()开启线程,代理
Thread thread = new Thread(testRunnable);
thread.start();
//上面两句可以合并为一句
//new Thread(testRunnable). start();
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码");
}
}
}
例子:买火车票
package demo02;
//模拟买火车票
public class TestRunnableExample1 implements Runnable{
private int ticketNums = 10; //定义票数
@Override
public void run() {
while (true){
if (ticketNums <= 0) break;
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
//开启一份资源,多个代理
TestRunnableExample1 testRunnableExample1 = new TestRunnableExample1();
new Thread(testRunnableExample1,"张三").start();
new Thread(testRunnableExample1,"李四").start();
new Thread(testRunnableExample1,"王五").start();
}
//出现的问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
}
例子2:龟兔赛跑
思路:
1.首先来个赛道距离,然后要离重点越来越近
2.判断比赛是否结束
3.打印出胜利者
4.龟兔赛跑开始
5.故事中是因为兔子睡觉乌龟赢,所以要模拟兔子睡觉
6.最终,乌龟赢了
package demo02;
//模拟龟兔赛跑,兔子睡觉,乌龟赢
public class Race implements Runnable{
private String winner; //定义一个胜利者
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
boolean flag = gameOver(i);
if (flag) break;
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
//模拟兔子睡觉
if (Thread.currentThread().getName().equals("兔子") && i == 50){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//判断比赛是否完成
public boolean gameOver(int steps){
if (winner != null){
return true;
}else if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是"+winner);
return true;
}else {
return false;
}
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
方法3:Callable接口
-
实现Callable接口,需要返回值类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
-
提交执行:Future result1 = ser.submit(11);
-
获取结果:boolean r1 = result1.get()
-
关闭服务:ser.shutdownNow();
package demo02;
import java.util.concurrent.*;
/**
* 方式三:实现Callable接口
* 1.可以返回值
* 2.可以抛出异常
*/
public class TestCallable implements Callable {
private String url;
private String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
new WebDownloader().downloader(url,name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "1.png");
TestCallable t2 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "2.png");
TestCallable t3 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "3.png");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> s1 = ser.submit(t1);
Future<Boolean> s2 = ser.submit(t2);
Future<Boolean> s3 = ser.submit(t3);
//获取结果
Boolean b1 = s1.get();
Boolean b2 = s2.get();
Boolean b3 = s3.get();
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
//关闭服务
ser.shutdown();
}
}
静态代理(多线程的底层逻辑)
package staticproxy;
/*
静态代理:
1.真实对象与代理对象要实现同一接口
2.代理对象创建代理真实角色
好处:
1.代理对象可以做很多真实对象做不了的事情,比如布置结婚场景
2.真实对象专注做自己的事情,结婚
*/
public class StaticProxy {
public static void main(String[] args) {
/*You you = new You();
you.HappyMarry();*/ //原本方式,下面交给代理
//线程类代理,实际调用了Runnable接口中的run方法
new Thread(()-> System.out.println("我爱你")).start();
new WeddingCompany(new You()).HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("张三要结婚了!");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry target; //目标:真实对象
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry(); //调用真实对象的方法
after();
}
public void before(){
System.out.println("布置现场");
}
public void after(){
System.out.println("收尾款");
}
}
Lamda表达式
- λ 希腊字母表中排序第十一位的字母,英语名称为 Lamda
- 避免匿名内部类定义过多
- 其实质属于函数式编程的概念
- 去掉了一堆没有意义的代码,只留下核心逻辑
new Thread (()->System.out.println(“多线程学习。。。。”)).start();
函数式接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口.
public interface Runnable{
public abstract void run();
}
对于函数式接口,我们可以通过Lamda表达式来创建该接口的对象.
package lambda;
public class Test01 {
public static void main(String[] args) {
//2.用lambda简化,-->函数式接口
ILike iLike = () -> {
System.out.println("Lambda表达式");
};
iLike.ilike();
}
}
//1.定义一个函数式接口
interface ILike {
void ilike();
}
Lamda表达式进化之路:
package lambda;
/*
推导Lambda表达式
*/
public class Test02 {
//3.静态内部类
static class Demo2 implements Lambda {
@Override
public void study() {
System.out.println("我在学习Lambda表达式2");
}
}
public static void main(String[] args) {
//实现类调用
Lambda demo = new Demo1();
demo.study(); //我在学习Lambda表达式1
//静态内部类调用
demo = new Demo2();
demo.study(); //我在学习Lambda表达式2
//4.局部内部类实现
class Demo3 implements Lambda {
@Override
public void study() {
System.out.println("我在学习Lambda表达式3");
}
}
//局部内部类掉用
demo = new Demo3();
demo.study(); //我在学习Lambda表达式3
//5.匿名内部类:没有类的名称,必须借助接口(new 接口)或者父类
demo = new Lambda() {
@Override
public void study() {
System.out.println("我在学习Lambda表达式4");
}
};
//匿名内部类调用
demo.study(); //我在学习Lambda表达式4
//6.用Lambda表达式
demo = ()-> {
System.out.println("我在学习Lambda表达式5");
};
//Lambda调用
demo.study(); //我在学习Lambda表达式5
}
}
//1.定义一个函数式接口
interface Lambda {
void study();
}
//2.函数式接口的实现类
class Demo1 implements Lambda {
@Override
public void study() {
System.out.println("我在学习Lambda表达式1");
}
}
Lamda表达式继续简化:
package lambda;
public class Test03 {
public static void main(String[] args) {
//1.Lambda表达式普通形式
UserDao1 userDao1 = () -> {
System.out.println("UserDao1");
};
userDao1.add();
//2.方法体只有一行代码时可以去掉花括号{}
UserDao2 userDao2 = () -> System.out.println("UserDao2");
userDao2.add();
//3.当有返回值时不能去掉花括号{}
UserDao3 userDao3 = () -> {
return 0;
};
//有返回值要接收并输出才能看见
System.out.println(userDao3.add());
//4.有一个参数时,参数类型可以去掉,参数类型外边的括号也能去掉()
UserDao4 userDao4 = a -> {
System.out.print("UserDao4,a=");
return a;
};
System.out.println(userDao4.add(10));
//5.有多个参数时,参数类型可以去掉,但需要全部去掉,或者全部不去,但是参数外边的括号不能去掉()
UserDao5 userDao5 = (a,b) -> {
System.out.print("UserDao5,a+b=");
return a + b;
};
System.out.println(userDao5.add(1, 2));
}
}
//定义一个函数式接口
interface UserDao1 {
void add();
}
interface UserDao2 {
void add();
}
interface UserDao3 {
int add();
}
interface UserDao4 {
int add(int a);
}
interface UserDao5 {
int add(int a, int b);
}
3.线程状态及方法
五大状态
线程五大状态:
线程停止
package threadstop;
public class TestStop01 implements Runnable {
//1.设置一个标识位
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag){
System.out.println("兔跑了第" + i++ +"步");
}
}
//2.设置公开的方法,转换标识符,让线程停止
private void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop01 testStop01 = new TestStop01();
new Thread(testStop01).start();
for (int i = 1; i <= 100; i++) {
System.out.println("乌龟跑了第"+i+"步");
//3.调用stop()方法让线程停止
if (i == 80){
testStop01.stop();
System.out.println("兔该停下了");
}
}
}
}
线程休眠(sleep)
- sleep(时间) 指定当前线程阻塞的毫秒数;
- sleep时间到达后线程就进入就绪状态;
- sleep存在异常InterruptedException;
- sleep可以模拟网络延时,倒计时等。(故意设置延时收优化钱💴,不道德,比如某盘)
- 每一个对象都有一个锁🔒,sleep不会释放锁。
package threadsleep;
//模拟网络延时,买火车票
//放大问题的发生性
public class TestSleep1 implements Runnable{
//定义票的总数
private int tickets = 10;
//线程体
@Override
public void run(){
while (true) {
if (tickets <= 0) break;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第" + tickets-- + "张票");
}
}
public static void main(String[] args) {
//一份资源,三个代理
Runnable testSleep1 = new TestSleep1();
new Thread(testSleep1,"张三").start();
new Thread(testSleep1,"李四").start();
new Thread(testSleep1,"王五").start();
}
}
package threadsleep;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟倒计时
public class TestSleep2 {
public static void main(String[] args) {
//主线程中调用倒数计时方法
try {
new TestSleep2().countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印系统当前时间
Date date = new Date(System.currentTimeMillis()); //获取当前时间
//模拟时钟
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis()); //更新当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//倒数计时方法
public void countDown() throws InterruptedException {
//倒数从低级秒开始
int num = 10;
while (true){
if (num < 0) break;
Thread.sleep(1000);
System.out.println(num--);
}
}
}
package threadsleep;
public class TestSleep3 {
public static void main(String[] args) {
//在新创建的线程中运行
new Thread(new CountDown()).start();
}
}
//模拟倒计时类
class CountDown implements Runnable{
private int num = 10;
@Override
public void run() {
while (true){
if (num < 0) break;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
}
}
}
package threadsleep;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadSleep4 {
public static void main(String[] args) {
//new Clock().run(); //在主线程运行
new Thread(new Clock()).start(); //在新创建的线程运行
}
}
class Clock implements Runnable{
//打印系统当前时间
Date date = new Date(System.currentTimeMillis()); //获取当前时间
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis()); //更新时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让(Yield)
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转化为就绪状态
- 让CPU重新调度,但礼让不一定成功,看CPU心情
package threadjoin;
//join()方法,线程插队,强制执行,阻塞其他线程,有重要的线程就可以使用线程插队让这个主要的线程先执行。尽量少用
public class TestJoin1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("VIP线程" + i);
}
}
//主线程
public static void main(String[] args) {
Runnable join = new TestJoin1();
Thread thread = new Thread(join);
thread.start(); //开启线程
for (int i = 0; i <= 200; i++) {
if (i == 150){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("普通" + i);
}
}
}
package threadjoin;
public class TestJoin2 {
//主线程
public static void main(String[] args) {
//启动VIP线程
Thread vip = new Thread(new Vip());
vip.start();
try {
vip.join(); //把主要线程用join()方法,让其先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动普通线程
Thread a = new Thread(new A());
a.start();
//主线程任务
for (int i = 0; i < 200; i++) {
System.out.println("主线程");
}
}
}
//重要线程
class Vip implements Runnable{
@Override
//VIP线程任务
public void run() {
System.out.println("VIP线程====================");
}
}
//普通线程
class A implements Runnable{
@Override
//普通线程任务
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("普通线程");
}
}
}
观测线程状态
线程五大状态:新生状态,就绪状态,运行状态,阻塞状态,死亡状态
- Thread.State方法
线程状态, 线程可以处于以下状态之一:
- NEW 尚未启动的线程处于此状态。
- RUNNABLE 在Java虚拟机中执行的线程处于此状态。
- BLOCKED被阻塞等待监视器锁定的线程处于此状态。
- WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
package threadstate;
//观察线程状态
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
int num = 5;
//线程等待五秒
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("================");
}
);
Thread.State state = thread.getState(); //获取当前线程状态
System.out.println(state); //打印当前线程状态
thread.start(); //启动该线程
state = thread.getState(); //获取当前线程状态
System.out.println(state); //打印当前线程状态
//state != Thread.State.TERMINATED 表示线程 不等于 线程停止(线程不停止)
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();
System.out.println(state); //每隔一秒打印一次线程状态
}
}
}
//打印结果
/*
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
================
TERMINATED
*/
优先级
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2IQ1OxQ9-1649992930038)(F:\image\截屏\20210508195913713.png)]
package threadpriority;
//CPU不一定按优先级调度,但是优先级大的线程权重大
public class TestPriority {
public static void main(String[] args) {
//main()线程(主线程) 默认优先级为 5
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
Runnable priority = new MyPriority();
Thread t1 = new Thread(priority,"t1");
Thread t2 = new Thread(priority,"t2");
Thread t3 = new Thread(priority,"t3");
Thread t4 = new Thread(priority,"t4");
Thread t5 = new Thread(priority,"t5");
Thread t6 = new Thread(priority,"t6");
t1.start(); // 不设置优先级,默认优先级为5
t2.setPriority(10); //设置优先级为10
t2.start();
t3.setPriority(1); //设置优先级为1
t3.start();
t4.setPriority(8); //设置优先级为8
t4.start();
t5.setPriority(3); //设置优先级为3
t5.start();
//优先级等级只有[1,10],否则异常
//t6.setPriority(0);
//t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
}
}
守护线程
- 线程分为用户线程和守护线程(daemon)
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待…
package threaddaemon;
public class TestDaemon {
public static void main(String[] args) {
//God为守护线程,用户现场结束守护线程也就结束了
Thread thread = new Thread(new God());
thread.setDaemon(true); //设置为守护线程,默认是false(即用户线程)
thread.start(); //守护线程启动
//守护线程关闭需要一定的时间
new Thread(new You()).start(); //用户线程启动
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("你开心的度过了一年");
}
System.out.println("=======一年结束了======");
}
}
4.线程同步
- 并发:同一对象被多个线程同时操作(抢票例子)
- 线程同步是一个等待机制,多个需要同时访问次对象的线程进入这个对象的等待池形成队列,等待前一个线程使用完毕,下一个线程才能使用。
形成条件:队列+锁
三大不安全案例
案例1.车站抢票:
package syn;
//模拟抢票,一个对象被多个线程操作,线程的并发问题 不安全
public class UnSafeBuyTickets {
public static void main(String[] args) { //主线程
//一个对象被三个线程操作
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets,"张三").start();
new Thread(buyTickets,"李三四").start();
new Thread(buyTickets,"可恶的黄牛党").start();
}
}
//买票
class BuyTickets implements Runnable {
//票数
private int ticketnum = 10;
//线程停止标志位
private boolean flag = true;
@Override
public void run() {
try {
buyTickets();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void buyTickets() throws InterruptedException {
//判断是否有票
if (ticketnum <= 0) {
System.out.println("没有票了");
return;
}
//利用循环买票
while (flag) {
if (ticketnum <= 0) flag = false; //线程停止标志位转换符
Thread.sleep(100); //线程睡眠,放大问题的发生下性
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketnum-- + "张票");
}
}
}
/*
多人同时拿到同一张票是因为每个线程在自己的工作内存交互,当多人同时看到一张票,就会出现张三,李四都抢到第10张票的结果
可恶的黄牛党拿到了第-1张票 是因为张三和黄牛党同时看到最后一张票,但是张三却先抢走了,系统内就剩下0张票,这时候黄牛党再拿一张票,系统内就会出现-1张票
*/
案例2.银行取钱
package syn;
//模拟银行取钱
/*
1.需要一个账户(属性有账户余额,账户名字)
2.需要一个银行(模拟取钱)
*/
public class UnSafeBank {
public static void main(String[] args) {
Account bankcard = new Account(50,"积蓄");
Bank you = new Bank(bankcard,30,"你");
Bank girlfriend = new Bank(bankcard,50,"女朋友");
you.start();
girlfriend.start();
}
}
//定义一个账户
class Account {
//账户余额
int account_money;
//账户名称
String account_name;
public Account(int account_money, String account_name) {
this.account_money = account_money;
this.account_name = account_name;
}
}
//定义一个银行
class Bank extends Thread {
//需要一个账户
Account account;
//取了多少钱
int drawing_money;
//手里现在有多少钱
int now_money;
public Bank(Account account, int drawing_money, String account_name) {
super(account_name);
this.account = account;
this.drawing_money = drawing_money;
}
@Override
public void run() {
//判断钱够不够
if (account.account_money - drawing_money < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//账户余额
account.account_money = account.account_money - drawing_money;
//手里的钱
now_money = now_money + drawing_money;
System.out.println(account.account_name + "账户余额为:" + account.account_money);
System.out.println(this.getName() + "取走了" + now_money);
}
}
/*
问题:
出现账户余额为负数的情况
*/
案例3.不安全的集合
package syn;
import java.util.ArrayList;
//不安全的集合
public class UnSafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
同步方法
弊端:方法里需要修改的内容才需要锁,只读可以不用锁,锁太多会浪费资源
package syn.synchronizedmethods;
//模拟抢票,一个对象被多个线程操作,线程的并发问题 不安全
public class UnSafeBuyTickets {
public static void main(String[] args) { //主线程
//一个对象被三个线程操作
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets,"张三").start();
new Thread(buyTickets,"李四").start();
new Thread(buyTickets,"可恶的黄牛党").start();
}
}
//买票
class BuyTickets implements Runnable {
//票数
private int ticketnum = 10;
//设置线程停止标志位
private boolean flag = true;
@Override
public void run() {
//循环调用买票方法,模拟进行买票
while (flag){
try {
buyTickets();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//用systhronized关键字实现线程同步方法,锁的是this,解决了线程的并发问题
//public systhronsized 返回值类型 methods(参数){} 为同步方法
public synchronized void buyTickets() throws InterruptedException {
//判断是否有票
if (ticketnum <= 0) {
System.out.println("没有票了");
this.flag = false; 让标志位反转,使线程停止
return;
}
//线程睡眠,模拟网络延迟
Thread.sleep(100);
//模拟买票
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketnum-- + "张票");
}
}
/*
多人同时拿到同一张票是因为每个线程在自己的工作内存交互,当多人同时看到一张票,就会出现张三,李四都抢到第10张票的结果
可恶的黄牛党拿到了第-1张票 是因为张三和黄牛党同时看到最后一张票,但是张三却先抢走了,系统内就剩下0张票,这时候黄牛党再拿一张票,系统内就会出现-1张票
*/
package syn.synchronizedmethods;
//模拟银行取钱
/*
1.需要一个账户(属性有账户余额,账户名字)
2.需要一个银行(模拟取钱)
*/
public class UnSafeBank {
public static void main(String[] args) {
Account bankcard = new Account(100,"积蓄");
Bank you = new Bank(bankcard,50,"你");
Bank girlfriend = new Bank(bankcard,100,"女朋友");
you.start();
girlfriend.start();
}
}
//定义一个账户
class Account {
//账户余额
int account_money;
//账户名称
String account_name;
public Account(int account_money, String account_name) {
this.account_money = account_money;
this.account_name = account_name;
}
}
//定义一个银行
class Bank extends Thread {
//需要一个账户
Account account;
//取了多少钱
int drawing_money;
//手里现在有多少钱
int now_money;
public Bank(Account account, int drawing_money, String account_name) {
super(account_name);
this.account = account;
this.drawing_money = drawing_money;
}
@Override
public void run() {
//synchronized(Obj){} 为同步块
//锁的对象是要变化的量,需要增删改的对象
synchronized (account){
//判断钱够不够
if (account.account_money - drawing_money < 0) {
System.out.println(Thread.currentThread().getName() + "想取钱,钱不够了,取不了");
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//账户余额
account.account_money = account.account_money - drawing_money;
//手里的钱
now_money = now_money + drawing_money;
System.out.println(account.account_name + "账户余额为:" + account.account_money);
System.out.println(this.getName() + "手里有" + now_money);
}
}
}
/*
问题:
出现账户余额为负数的情况
*/
package syn.synchronizedmethods;
import java.util.ArrayList;
//不安全的集合
public class UnSafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
//变化的对象是list
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印集合长度
System.out.println(list.size());
}
}
JUC安全的集合类型 CopyOnWriteArrayList
package syn.synchronizedmethods;
import java.util.concurrent.CopyOnWriteArrayList;
// 测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
死锁
- 多个线程各自占用一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方资源释放,都停止的情形。某一个同步块同时拥有两个以上对象的锁时,就可能会死锁。
package syn.deadLock;
//死锁:多线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0, "灰姑娘");
MakeUp g2 = new MakeUp(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
//化妆
class MakeUp extends Thread {
//需要的资源只有一份,用static来保证只有一份
//需要一支口红
static Lipstick lipstick = new Lipstick();
//需要一面镜子
static Mirror mirror = new Mirror();
//选择
int choice;
//使用者名字
String girlName;
public MakeUp(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆的方法
//互相持有对方的锁,就是需要拿到对方的资源
public void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
package syn.deadLock;
//死锁:多线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0, "灰姑娘");
MakeUp g2 = new MakeUp(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
//化妆
class MakeUp extends Thread {
//需要的资源只有一份,用static来保证只有一份
//需要一支口红
static Lipstick lipstick = new Lipstick();
//需要一面镜子
static Mirror mirror = new Mirror();
//选择
int choice;
//使用者名字
String girlName;
public MakeUp(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆的方法
//互相持有对方的锁,就是需要拿到对方的资源
public void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
}
//把镜子的锁放到外面就可以避免这种情况
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
}
//把镜子的锁放到外面就可以避免这种情况
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
Lock(锁)
- 从JDK5.0开始,Java提供了更强大的同步线程机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
- Lock接口是控制躲个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock加锁,线程开始访问资源前必须先获得Lock对象。
- ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。
package syn.lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock1 {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets).start();
new Thread(buyTickets).start();
new Thread(buyTickets).start();
}
}
class BuyTickets implements Runnable {
//定义一个锁
private final ReentrantLock lock = new ReentrantLock();
int ticketnums = 10;
@Override
public void run() {
while (true) {
try {
//上锁
lock.lock();
if (ticketnums <= 0) {
break;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketnums--);
}
} finally {
//开锁
lock.unlock();
}
}
}
}
5.线程通信
这是一个线程同步问题,生产这与消费者共享同一个资源,他们之间相互依赖,互为条件。
- 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又要马上通知消费者消费。
- 对于消费者,消费之后,要通知生产者生产新的产品。
- synchronized可阻止并发更新同一个共享资源,而不能实现不同进程之间消息的传递。
管程法
package communication.monitor;
//生产者消费者问题:管程法
public class Test {
public static void main(String[] args) {
SynContainter synContainter = new SynContainter();
new Producter(synContainter).start();
new Consumer(synContainter).start();
}
}
//生产者
class Producter extends Thread {
SynContainter containter;
public Producter(SynContainter containter) {
this.containter = containter;
}
//生产生产
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
containter.push(new Chicken(i));
System.out.println("生产了" + i + "只鸡");
}
}
}
//消费者
class Consumer extends Thread {
SynContainter containter;
public Consumer(SynContainter containter) {
this.containter = containter;
}
//消费者消费
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("消费了-->" + containter.pop().id + "只鸡");
}
}
}
//产品
class Chicken {
int id; //产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainter {
//定义一个容器
Chicken[] chickens = new Chicken[10];
int count = 0; //产品计数器
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果产品数量等于容器大小,就让生产者停止生产进行等待
if (count == chickens.length) {
//生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//让生产者生产
chickens[count] = chicken;
count++;
//做好了,可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
synchronized Chicken pop() {
//判断是否消费
//如果容器中没有产品,那么消费者就要等待,然后通知生产者生产
if (count == 0) {
try {
//消费者等待生产者生产
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//否则就让消费者消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
信号灯法
package communication.monitor;
//生产者消费者问题:信号灯法
public class Test2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Person(tv).start();
}
}
//生产者:演员
class Actor extends Thread{
TV tv;
public Actor(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 Person extends Thread{
TV tv;
public Person(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.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 = !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;
}
}
线程池
- 背景:经常销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不用每次都创建)
- 便于线程管理
- corePoolSize (核心池大小)
- maximumPoolSize (最大线程数)
- keepAliveTime (当线程没有任务,保持多长时间终止)
package communication.monitor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
生产者消费者问题:线程池
public class Test3 {
public static void main(String[] args) {
//1.创建服务,创建线程池
//Executors.newFixedThreadPool(参数:线程池大小) 创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}