多线程学习
简介
一个进程有多个线程
- 程序:程序是指令和数据的有序集合,其本身没有运行的含义,是一个静态的概念
- 进程:进程则是执行程序一次的过程,是个动态的概念,是系统资源分配的单位
- 线程:通常一个进程中有若干个线程,线程是CPU调度和执行的单位
重要概念
- 线程是独立执行的路径
- 在程序执行时,自己即使没有创建线程,也会有多个线程,如主线程、gc线程
- main()为主线程,程序入口,用于执行整个程序
- 在一个进程中,若开辟了多个线程,线程的运行和调度由调度器安排,与操作系统有关,不能认为干预
- 对于同一份操作资源,需加入并发控制
- 线程会带来额外开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建
三种创建方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,使用start()开启线程
TestThread1.java
package com.thread.demo1;
import com.oop.demo9.Test;
//Thread创建线程:继承Thread 重写run()方法 start()开启线程
public class TestThread1 extends Thread {
//重写run()方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("TestThread1 " + i);
}
}
public static void main(String[] args) {
TestThread1 testThread1 = new TestThread1();
testThread1.start();
for (int i = 0; i < 20000; i++) {
System.out.println("main " + i);
}
}
}
可发现主线程和新开辟的线程交替执行,结合简介中的图理解
线程开启后不一定开始执行,由CPU调度执行
从网络上下载图片例子
1、需要下载commons-io-2.8.0.jar,放到com.lib包下,然后add as Library
然后在Project Structure可找到加入的内容
2、TestThread2.java
package com.thread.demo1;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//联系Thread,实现多线程同步下载图片
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() {
new WebDownloader().downLoader(url, name);
System.out.println("下载的文件名为:" + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "1.JPG"); //注意构造方法
TestThread2 t2 = new TestThread2("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "2.JPG"); //注意构造方法
TestThread2 t3 = new TestThread2("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "3.JPG"); //注意构造方法
t1.start();
t2.start();
t3.start();
}
}
//下载器
class WebDownloader {
//下载方法
public void downLoader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoader异常");
}
}
}
结果:
线程执行的顺序不是根据代码所写的顺序执行,而是由CPU调度
注意,下载的文件位置,可发现下载的图片和项目为同一级
Runnable接口
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程体
- 创建线程对象,代理,start()方法启动线程
TestThread3.java
package com.thread.demo1;
//Runnable接口创建线程
public class TestThread3 implements Runnable {
@Override
public void run() {
//线程方法体
for (int i = 0; i < 20; i++) {
System.out.println("TestThread3 " + i);
}
}
public static void main(String[] args) {
//创建Runnable接口实现类
TestThread3 testThread3 = new TestThread3();
//创建线程,用来开启自己的线程,被称为代理
/*
Thread thread = new Thread(testThread3);
thread.start();
*/
new Thread(testThread3).start();
for (int i = 0; i < 2000; i++) {
System.out.println("main " + i);
}
}
}
同样,用Runnable实现图片下载
package com.thread.demo1;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//联系Runnable,实现多线程同步下载图片
public class TestThread4 implements Runnable {
private String url; //图片网址
private String name; //保存的文件名
public TestThread4(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
new WebDownloader2().downLoader(url, name);
System.out.println("下载的文件名为:" + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "1.JPG"); //注意构造方法
TestThread2 t2 = new TestThread2("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "2.JPG"); //注意构造方法
TestThread2 t3 = new TestThread2("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "3.JPG"); //注意构造方法
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
//下载器
class WebDownloader2 {
//下载方法
public void downLoader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoader异常");
}
}
}
Thread和Runnable对比:
Tread类:
- 继承Thread类来实现多线程
- 启动:子类对象.start()
- 不建议使用,避免单继承局限性
Runnable类:
- 接口Runnable实现多线程
- 启动:new Thread(目标对象).start(),采用代理
- 推荐使用,避免单继承局限性
并发问题
TestThread5.java
package com.thread.demo1;
//买票程序,认识并发问题
public class TestThread5 implements Runnable{
private int ticketNumber = 10;
@Override
public void run() {
while (true) {
if (ticketNumber <= 0) {
break;
}
//模拟延时
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNumber-- + "张票");
}
}
public static void main(String[] args) {
TestThread5 testThread5 = new TestThread5();
new Thread(testThread5, "A").start();
new Thread(testThread5, "B").start();
new Thread(testThread5, "C").start();
}
}
多线程操作同一资源情况下,会造成数据混乱,并发问题
龟兔赛跑
Race.java
package com.thread.demo1;
//模拟龟兔赛跑
public class Race implements Runnable {
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 200; i++) {
boolean flag = gameOver(i);
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + " 跑了" + i + "步");
if (Thread.currentThread().getName().equals("兔子")) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//兔子速度较快
i = i + 5;
}
}
}
//判断是否完成比赛
private boolean gameOver (int step) {
if (winner != null) {
return true;
}
if (step >= 200) {
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();
}
}
上述模拟通过定义两个线程来实现
Callable接口
-
实现Callable接口,需返回值类型
-
重写call方法,需抛出异常
-
创建目标对象
-
创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3); -
提交执行
Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3);submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future
-
获取结果
boolean rs1 = r1.get(); //call方法的返回值类型,本例中已经写了return true boolean rs2 = r2.get(); //需要抛出异常 boolean rs3 = r3.get(); -
关闭服务
ser.shutdownNow();
不使用以上线程池的方法,使用以下方法也可实现
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();
TestThread6.java
package com.thread.demo1;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//Callable,实现多线程同步下载图片
//返回值类型可以任意设置
public class TestThread6 implements Callable<Boolean> {
private String url; //图片网址
private String name; //保存的文件名
public TestThread6(String url, String name) {
this.url = url;
this.name = name;
}
//注意该方法的返回值的类型和Callable<返回值类型>相同
@Override
public Boolean call() {
new WebDownloader3().downLoader(url, name);
System.out.println("下载的文件名为:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestThread6 t1 = new TestThread6("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "1.JPG"); //注意构造方法
TestThread6 t2 = new TestThread6("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "2.JPG"); //注意构造方法
TestThread6 t3 = new TestThread6("https://images.cnblogs.com/cnblogs_com/sgKurisu/1928850/o_210206095542IDEA%E4%B8%8B%E8%BD%BD.JPG", "3.JPG"); //注意构造方法
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3); //创建线程池
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果
boolean rs1 = r1.get(); //call方法的返回值类型,本例中已经写了return true
boolean rs2 = r2.get(); //需要抛出异常
boolean rs3 = r3.get();
//关闭服务
ser.shutdownNow();
}
}
//下载器
class WebDownloader3 {
//下载方法
public void downLoader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoader异常");
}
}
}
静态代理模式
- 真实对象和代理对象需实现同一接口
- 代理对象要代理真实角色
优点:
- 真实对象做自己事件
- 代理对象可做真实对象其他事件
new Thread(()-> System.out.println("ABC")).start();
可开启一个线程,实现Runnable接口,run方法中的代码为
System.out.println("ABC");
以结婚,婚庆公司为例介绍静态代理:
package com.thread.staticproxy;
//静态代理模式理解
public class StaticProxy {
public static void main(String[] args) {
/*
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.HappyMarry();
*/
new Thread(()-> System.out.println("ABC")).start(); //开启一个线程,run()方法中的代码为 System.out.println("ABC")
new WeddingCompany(new You()).HappyMarry();
}
}
interface Marry {
void HappyMarry();
}
//真实角色
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("You结婚");
}
}
//代理角色
class WeddingCompany implements Marry {
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚后");
}
private void before() {
System.out.println("结婚前");
}
}
Lamda表达式
- 避免匿名内部类定义过多
- 使代码简洁
- 只留下核心逻辑
函数式接口:只包含一个抽象方法的接口
对于函数式接口,可通过Lamda表达式来创建该接口的对象
Lamda表达式属于函数式编程概念
TestLamda1.java
package com.thread.lamda;
public class TestLamda1 {
//静态内部类
static class Like2 implements ILike {
@Override
public void lamda() {
System.out.println("静态内部类");
}
}
public static void main(String[] args) {
ILike iLike1 = new Like();
iLike1.lamda();
//静态内部类
ILike iLike2 = new Like2();
iLike2.lamda();
//局部内部类
class Like3 implements ILike {
@Override
public void lamda() {
System.out.println("局部内部类");
}
}
ILike iLike3 = new Like3();
iLike3.lamda();
//匿名内部类,必须借用接口或父类
ILike ilike4 = new ILike() {
@Override
public void lamda() {
System.out.println("匿名内部类");
}
};
ilike4.lamda();
//Lamda表达式
ILike ilike5 = ()->{
System.out.println("Lamda表达式");
};
ilike5.lamda();
}
}
//定义一个函数式接口
interface ILike {
void lamda();
}
//实现类
class Like implements ILike {
@Override
public void lamda() {
System.out.println("Like");
}
}
TestLamda2.java
package com.thread.lamda;
//Lamda表达式简化
//若为多参数类型,去掉参数类型时,则需一起去掉,必须加括号
public class TestLamda2 {
public static void main(String[] args) {
//Lamda表达式
ILove love = (int a)->{
System.out.println("Lamda表达式 " + a);
};
love.iLove(2);
//简化:去掉参数类型
//通常用这种类型
love = (a)->{
System.out.println("Lamda表达式 " + a);
};
love.iLove(2);
//简化:去掉括号
love = a -> {
System.out.println("Lamda表达式 " + a);
};
love.iLove(2);
//简化:去掉花括号
love = a -> System.out.println("Lamda表达式 " + a); //本例代码仅为一行,故可省略花括号;若为多行,则不可简化花括号,应为代码块
love.iLove(2);
}
}
interface ILove {
void iLove(int a);
}
在Lamda表达式中
for (int i = 0; i < 10000; i++) {
int a = i;
new Thread(()->{
//list1.add(i);在Lamda表达式中应按照以下方式
list1.add(a);
}).start();
}
线程停止
根据“老猫1226”博客线程的5种状态详解

线程方法:
setPriority(int newPriority) //改变线程优先级
static void sleep(long millis) //线程休眠,单位毫秒
void join() //插队
static void yield() //暂停该线程,执行其他线程
boolean isAlive() //测试该线程是否处于活动状态
线程停止不推荐使用JDK提供的stop()和destory()方法,推荐使用一个标志位,来终止线程
TestStop.java
package com.thread.state;
import com.oop.demo9.Test;
public class TestStop implements Runnable {
//1、设置一标志位flag
boolean flag = true;
@Override
public void run() {
int i = 0;
if (flag) {
System.out.println("Thread is running... " + i++);
}
}
//2、设置方法转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) throws InterruptedException {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
if (i == 900) {
testStop.stop();
System.out.println("Thread Stop===============");
}
System.out.println("main " + i);
}
}
}
线程休眠
- sleep阻塞当前线程的毫秒数
- sleep存在InterruptedException异常
- sleep可模拟倒计时,网络延迟等
- 每个对象都有一个锁,sleep不会释放锁
TestSleep.java
package com.thread.state;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.SimpleFormatter;
public class TestSleep {
//十秒倒计时
public void tenDown () throws InterruptedException {
int num = 10;
while (true) {
if (num == 0) {
break;
}
System.out.println(num);
Thread.sleep(1000);
num--;
}
}
public static void main(String[] args) throws InterruptedException {
new TestSleep().tenDown();
//打印当前系统时间
Date startTime = new Date(System.currentTimeMillis()); //获得当前系统时间
//打印十次
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}
}
}
获取当前系统时间
//System.currentTimeMillis()来获取自1970-1-01 00:00:00.000到当前系统时间的毫秒数,类型为long
Date startTime = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
System.out.println(sdf.format(startTime));
//用Date startTime = new Date();代替Date startTime = new Date(System.currentTimeMillis());也可以输出当前时间
new Date()和System.currentTimeMillis()获取当前时间戳区别,参考“No Silver Bullet”的博客Java进阶(十六)使用new Date()和System.currentTimeMillis()获取当前时间戳 可知,查看java源码
new Date()所做的事情其实就是调用了System.currentTimeMillis()
线程礼让
让CPU重新调度,礼让不一定成功
TestYield.java
package com.thread.state;
public class TestYield {
public static void main(String[] args) {
/*
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
*/
new Thread(new MyYield(), "a").start();
new Thread(new MyYield(), "b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程结束");
}
}
礼让成功
礼让失败
线程强制执行
强制执行该线程,直到该线程结束
TestJoin.java
package com.thread.state;
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Vip线程 " + i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TestJoin());
thread.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i == 50) {
thread.join();
}
System.out.println("主线程 " + i);
}
}
}
观测线程状态
根据java帮助文档
六种状态区别参考线程的六种状态(Runnable、Blocked、Waiting、TimedWating详解
线程一旦死亡,不可再次启动start()
TestState.java
package com.thread.state;
//观测线程状态
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("======================");
});
/*Thread.State state = thread.getState();
System.out.println(state);*/
System.out.println(thread.getState());
//启动
thread.start();
while (thread.getState() != Thread.State.TERMINATED) {
System.out.println(thread.getState());
}
System.out.println(thread.getState());
}
}
线程优先级
获取或改变优先级
getPriority()
setPriority(int xx)
TestPriority.java
package com.thread.state;
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority, "t1");
Thread t2 = new Thread(myPriority, "t2");
Thread t3 = new Thread(myPriority, "t3");
Thread t4 = new Thread(myPriority, "t4");
//先设置优先级,再启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY); //Thread.MAX_PRIORITY=10
t4.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
可发现,优先级低意味着获得调度概率较低,而不是一定按照线程优先级的顺序来执行
守护线程
线程分为用户线程和守护线程,虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕,守护线程如操作日志,监控内存,垃圾回收等
TestDaemo.java
package com.thread.state;
public class TestDaemo {
public static void main(String[] args) {
Test1 test1 = new Test1();
Test2 test2 = new Test2();
Thread t1 = new Thread(test1, "test1");
t1.setDaemon(true); //设置为守护线程,默认为false
t1.start();
new Thread(test2, "test2").start();
}
}
class Test1 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("Daemo");
}
}
}
class Test2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
}
System.out.println("==================Test2结束=================");
}
}
线程同步
并发:同一个对象被多个线程同时操作
多线程问题时,多个线程同时操作一个对象,并且某些线程还想修改这个对象,就需要线程同步。
线程同步其实是一种等待机制,多个需要访问此对象的线程进入到该对象的等待池,形成队列,等待前面线程执行完后下一个线程再使用。
由队列+锁,构成线程同步的安全性,由性能代价换来安全性
锁机制存在问题:
- 一个线程持有锁会导致其他需要此锁的线程挂起
- 多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题
- 优先级高的等待优先级低的释放锁,会导致优先级倒置,引发性能问题
三大不安全案例
1、多线程买票问题
UnsafeBuyTicket.java
package com.thread.syn;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "A").start();
new Thread(buyTicket, "B").start();
new Thread(buyTicket, "C").start();//多个线程同时操作一个对象
}
}
class BuyTicket implements Runnable {
//票
private int ticketNum = 10;
boolean flag = true; //线程停止标志
@Override
public void run() {
//买票
while (flag) {
buy();
}
}
private void buy() {
//判断是否有票
if (ticketNum <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到第" + ticketNum-- + "张票");
}
}
2、模拟银行取钱问题
package com.thread.syn;
//分别采用了Thread和Runnable创建线程方法实现此问题
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account("Account", 100); //新建一个账户
/* Drawing d1 = new Drawing(account, 50, "A");
Drawing d2 = new Drawing(account, 100, "B");
d1.start();
d2.start();*/
new Thread(new Drawing(account, 50), "A").start();
new Thread(new Drawing(account, 100), "B").start();
}
}
//账户
class Account {
int money; //余额
String name; //名称
public Account(String name, int money) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing implements Runnable {
Account account; //账户
int drawingMoney; //取出金额
int nowMoney; //手中剩余金额
public Drawing (Account account, int drawingMoney) {
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//判断是否可取
if (account.money < drawingMoney) {
System.out.println(Thread.currentThread().getName() + "钱不够,不能取出");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(Thread.currentThread().getName() + "手中钱" + nowMoney);
System.out.println(account.name + "余额" + account.money);
}
}
/*class Drawing extends Thread {
Account account; //账户
int drawingMoney; //取出金额
int nowMoney; //手中剩余金额
public Drawing (Account account, int drawingMoney, String name) {
super(name); //线程名字
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断是否可取
if (account.money < drawingMoney) {
System.out.println(Thread.currentThread().getName() + "钱不够,不能取出");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
//this.getName() = Thread.currentThread().getName()
System.out.println(this.getName() + "手中钱" + nowMoney);
System.out.println(account.name + "余额" + account.money);
}
}*/
3、线程不安全集合
package com.thread.syn;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
可发现list.size()并未达到10000
同步方法及同步块
同步方法,通过synchronized关键字,包括synchronized方法和synchronized块
synchronized来控制对象的访问,每个对象都有一把锁,每个synchronized方法必须获得该方法对象的锁才能执行,否则会阻塞。方法一旦执行,就会独占该锁,知道该方法结束,后面线程才能获得该锁。
方法中需要修改的内容才需要加锁,否则将会浪费资源
同步块:synchronized (Obj){}
- Obj称为同步监视器,可以为任何对象,推荐使用共享资源,为变化的量
- 同步方法中的监视器就为this,对象本身
对上节中的三个例子进行修改:
1、买票模拟中,仅需在买票方法前加synchronized关键字
//synchronized变为同步方法
private synchronized void buy() {
//判断是否有票
if (ticketNum <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到第" + ticketNum-- + "张票");
}
2、银行取钱模拟中,使用synchronized同步块,同步监视器为变化的account
package com.thread.syn;
//分别采用了Thread和Runnable创建线程方法实现此问题
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account("Account", 100); //新建一个账户
/* Drawing d1 = new Drawing(account, 50, "A");
Drawing d2 = new Drawing(account, 100, "B");
d1.start();
d2.start();*/
new Thread(new Drawing(account, 50), "A").start();
new Thread(new Drawing(account, 100), "B").start();
}
}
//账户
class Account {
int money; //余额
String name; //名称
public Account(String name, int money) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing implements Runnable {
Account account; //账户
int drawingMoney; //取出金额
int nowMoney; //手中剩余金额
public Drawing (Account account, int drawingMoney) {
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//锁的为变化的量
synchronized (account) {
//判断是否可取
if (account.money < drawingMoney) {
System.out.println(Thread.currentThread().getName() + "钱不够,不能取出");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(Thread.currentThread().getName() + "手中钱" + nowMoney);
System.out.println(account.name + "余额" + account.money);
}
}
}
/*class Drawing extends Thread {
Account account; //账户
int drawingMoney; //取出金额
int nowMoney; //手中剩余金额
public Drawing (Account account, int drawingMoney, String name) {
super(name); //线程名字
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断是否可取
if (account.money < drawingMoney) {
System.out.println(Thread.currentThread().getName() + "钱不够,不能取出");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
//this.getName() = Thread.currentThread().getName()
System.out.println(this.getName() + "手中钱" + nowMoney);
System.out.println(account.name + "余额" + account.money);
}
}*/
3、ArrayList集合
package com.thread.syn;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
还可能会出现并发问题,可使用Collections中的同步方法
List<String> list = Collections.synchronizedList(new ArrayList<>());
CopyOnWriteArrayList
根据“住手丶让我来”的博客高并发编程之CopyOnWriteArrayList介绍
- 支持高效率并发且是线程安全的,读操作无锁的ArrayList,所有可变操作都是通过对底层数组进行一次新的复制来实现。
- CopyOnWriteArrayList 合适读多写少的场景,CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。
package com.thread.syn;
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(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
经测试线程是安全同步的
死锁
多个线程各自占有一些共享资源,并且互相等待对方释放资源后才能运行,导致两个或多个线程都在等待对方释放资源
package com.thread.lock;
//死锁:多个线程相互持有对方需要资源,僵持
public class DeadLock {
public static void main(String[] args) {
AB a1 = new AB(0, "a1");
AB a2 = new AB(1, "a2");//互相需要对方资源
a1.start();
a2.start();
}
}
class A {
}
class B {
}
class AB extends Thread {
//用static来保证每份资源仅有一份
static A a = new A();
static B b = new B();
int choice;
String name;
public AB(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
make();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void make() throws InterruptedException {
if (choice == 0) {
synchronized (a) { //获得A的锁
System.out.println(this.name + "获得A");
Thread.sleep(1000);
synchronized (b) {
System.out.println(this.name + "获得B");
}
}
} else {
synchronized (b) { //获得A的锁
System.out.println(this.name + "获得B");
Thread.sleep(1000);
synchronized (a) {
System.out.println(this.name + "获得A");
}
}
}
}
}
导致陷入死锁
对make()方法进行修改:
private void make() throws InterruptedException {
if (choice == 0) {
synchronized (a) { //获得A的锁
System.out.println(this.name + "获得A");
Thread.sleep(1000);
}
synchronized (b) {
System.out.println(this.name + "获得B");
}
} else {
synchronized (b) { //获得A的锁
System.out.println(this.name + "获得B");
Thread.sleep(1000);
}
synchronized (a) {
System.out.println(this.name + "获得A");
}
}
}
死锁产生的四个必要条件:
- 互斥条件:一个资源被一个进程占用
- 请求与保持条件:一个资源请求对方资源陷入等待,另一个线程不释放资源
- 不剥夺条件:进程获得的资源在未释放之前不可被强行剥夺
- 循环等待条件:若干进程间形成循环等待关系
Lock锁
java.util.concurrent.locks.Lock接口来控制多个线程对共享资源进行访问
ReentrantLock类实现了Lock,拥有与synchronized相同的并发性,常用的是ReentrantLock,来进行显示加锁,释放锁
private final ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
需要加锁的代码
} finally {
lock.unlock();
}
package com.thread.lock;
import java.util.concurrent.locks.ReentrantLock;
//用买票程序测试Lock锁
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "A").start();
new Thread(ticket, "B").start();
new Thread(ticket, "C").start();
}
}
class Ticket implements Runnable {
int ticketNum = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//使用try……finally加锁
try {
lock.lock();
if (ticketNum <= 0) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "获得第" + ticketNum-- + "张票");
} finally {
lock.unlock();
}
}
}
}
synchronized和Lock对比
- Lock是显示锁,需要手动开锁和关锁,而synchronized是隐式锁
- 锁,出了作用区域后自动释放
- Lock只有代码块锁,而synchronized有代码块锁和方法锁
生产者消费者问题
这是个线程同步问题,生产者和消费者共享同一资源,相互依赖,互为条件。
仅用synchronized不够,synchronized不能够实现线程通信
线程间通信问题,可用以下方法
- wait():线程等待,等待其他线程唤醒
- notify():唤醒处于等待状态的线程,当线程较多时,被唤醒的线程是随机选择的
- notifyall():唤醒一个对象上所有调用wait()方法的线程,优先级高的调用
以上方法均为Object类的方法,仅能在同步方法或同步代码块中使用
管程法
package com.producer;
//管程法测试生产者消费者问题
//生产者,消费者,缓冲区,产品
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Thread(new Producer(container),"生产者").start();
new Thread(new Consumer(container),"消费者").start();
}
}
//生产者
class Producer implements Runnable {
SynContainer container = new SynContainer();
public Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
//生产
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了第" + i + "只鸡");
}
}
}
//消费者
class Consumer implements Runnable {
SynContainer container = new SynContainer();
public Consumer (SynContainer 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 SynContainer {
//容器大小
Chicken[] chickens = new Chicken[10]; //容器大小为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();
}
//消费者消费产品
public synchronized Chicken pop () {
//判断是否可以消费
if (count == 0) {
//生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取出chicken
count--;
Chicken chicken = chickens[count];
//通知生产者生产
this.notifyAll();
//返回取出的chicken
return chicken;
}
}
信号灯法
使用标志位来进行线程间通信调度
package com.producer;
//信号灯法,使用标志位
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(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("节目A");
} else {
this.tv.play("节目B");
}
}
}
}
//消费者
class Watcher extends Thread {
TV tv;
public Watcher (TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品
class TV {
//演员表演,观众等待 T
//观众观看,演员等待 F
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;
}
}
线程池
提前创建多个线程,放到线程池中,使用时获取,用完后放入到线程池中,可避免频繁创建销毁线程,提高利用率
优点:
- 提高响应速度
- 降低资源消耗
- 便于线程管理
- corePoolSize:核心池大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
相关类和接口
- ExecutorService:线程池接口
- void execute(Runnable command):用来执行任务或命令,无返回值
Future submit(Callable task):执行任务,有返回值 - void shutdown():关闭线程池
- Executors:线程池工厂类,用于创建和返回不同类型的线程池
总结
线程的三种创建方式
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadInclude {
public static void main(String[] args) {
new MyThread1().start();
new Thread(new MyThread2()).start();
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("MyThread1");
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread2");
}
}
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("MyThread3");
return 100;
}
}

浙公网安备 33010602011771号