1. 线程简介
1.1 多任务
现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时间只做了一件事情
1.2 多线程
原来是一条路,慢慢因为车太多了,道路堵塞,效率极低.为了提高使用的效率,能够充分利用道路,于是加了多个车道,从此再也不用担心道路堵塞了
1.2.1 普通方法调用和多线程
1.3 程序-进程-线程
一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕
等等
1.3.1 Process与Thread
- 说起进程,就不得不说下程序,程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
- 而进程则是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意思了,线程是CPU调度和执行的单位.
注意
:很多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器.如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉
1.4 核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程(main),和垃圾回收线程(gc)
- main()称之为主线程,为程序的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为的干预
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,例如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不会造成数据不一致
2. 线程实现
2.1 三种创建方式
2.1.1 Thread
package com.bigdata.demo01;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo01
* Version: 1.0
*
* @author qingzhi.wu 2020/7/14 21:42
*/
//创建线程方式一: 继承Thread类,重写run()方法,调用start开启线程
// 总结: 注意,线程开启不一定立即执行,由CPU调度执行
public class TestThread1 extends Thread {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20 ; i++) {
System.out.println("我在看代码--"+i);
}
}
public static void main(String[] args) {
//mian线程,主线程
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程--"+i);
}
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start()方法开启线程
testThread1.start();
}
}
2.1.2 网图下载
maven
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
网图下载
package com.bigdata.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo01
* Version: 1.0
*
* @author qingzhi.wu 2020/7/14 21:53
*/
//练习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() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载了文件名为: " + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test1.png");
TestThread2 t2 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test2.png");
TestThread2 t3 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test3.png");
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方法出现问题");
}
}
}
2.2.3 实现Runnable
推荐使用Runnable对象,因为Java单继承的局限性
package com.bigdata.demo01;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo01
* Version: 1.0
*
* @author qingzhi.wu 2020/7/14 22:14
*/
public class TestThread3 implements Runnable {
public void run() {
//run方法线程体
for (int i = 0; i < 20 ; i++) {
System.out.println("我在看代码--"+i);
}
}
public static void main(String[] args) {
//mian线程,主线程
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程--"+i);
}
//创建一个线程对象,通过线程对象来开启我们的线程
Thread thread = new Thread(new TestThread3());
thread.start();
}
}
2.3.4 修改网图下载为Runnable实现方式
package com.bigdata.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo01
* Version: 1.0
*
* @author qingzhi.wu 2020/7/14 21:53
*/
//练习Thread,首先多线程同步下载图片
public class TestThread2 implements Runnable {
private String url;
private String name;
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载了文件名为: " + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test1.png");
TestThread2 t2 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test2.png");
TestThread2 t3 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test3.png");
new Thread(t1).start();
new Thread(t2).start();
new Thread(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方法出现问题");
}
}
}
2.4.5 实现Callable接口
package com.bigdata.demo02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo02
* Version: 1.0
* 线程的创建方式三 实现Callable
* Callable 接口 可以获取返回值 和 抛出异常
* @author qingzhi.wu 2020/7/15 11:31
*/
public class TestCallable implements Callable {
private String url;
private String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test1.png");
TestCallable t2 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test2.png");
TestCallable t3 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test3.png");
//创建执行服务:
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();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
ser.shutdown();
}
public Boolean call() throws Exception {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载了文件名为: " + name);
return true;
}
}
//下载器
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方法出现问题");
}
}
}
2.4.6 龟兔赛跑-Race
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
package com.bigdata.demo01;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo01
* Version: 1.0
* 模拟龟兔赛跑
* @author qingzhi.wu 2020/7/15 10:31
*/
public class Race implements Runnable{
//胜利者
private static String winner;
public void run() {
for (int i = 1; i < 101; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子")){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (Thread.currentThread().getName().equals("乌龟")){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() +"-->跑了" +i+"步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
if(winner != null){
return true;
}
{
if(steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
2.4.7 总结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程: 子类对象.start()
- 不建议使用: 避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程: 传入目标对象+ Thread对象.start()
- 推荐使用: 避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
- 实现Callable接口
- 实现Callable接口,需要抛出异常
- 创建目标对象
- 创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3);
- 提交执行:
Future<Boolean> r1 = ser.submit(t1);
- 获取结果:
boolean rs1 = r1.get();
- 关闭服务:
ser.shutdown();
3. 初识并发
3.1 超卖问题
package com.bigdata.demo01;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo01
* Version: 1.0
*
* @author qingzhi.wu 2020/7/14 22:29
*/
//发现问题:多个线程操作同一个资源的时候,出现数据紊乱的问题
public class TestThread4 implements Runnable {
private int ticketNums = 10;
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) {
TestThread4 t4 = new TestThread4();
new Thread(t4,"小明").start();
new Thread(t4,"老师").start();
new Thread(t4,"黄牛党").start();
}
}
4. 静态代理模式
package com.bigdata.demo02;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo02
* Version: 1.0
*
* 1. 静态代理
* @author qingzhi.wu 2020/7/15 11:53
*/
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany company = new WeddingCompany(new You());
company.happyMarray();
new Thread(()->System.out.println("我爱你"));
}
}
interface Marry{
void happyMarray();
}
class You implements Marry{
public void happyMarray() {
System.out.println("秦老师要结婚了,超开心");
}
}
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
public void happyMarray() {
before();
this.target.happyMarray(); //这个就是真实对象的结婚
after();
}
private void after() {
System.out.println("收尾款");
}
private void before() {
System.out.println("布置现场");
}
}
5. 线程状态
5.1 线程停止
package com.bigdata.demo03;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 13:56
*/
public class TestStop implements Runnable{
public boolean isRunning = true;
@Override
public void run() {
while (isRunning){
System.out.println("我一直运行");
}
}
public void stop(){
this.isRunning = false;
}
public static void main(String[] args) throws InterruptedException {
TestStop testStop = new TestStop();
Thread t1 = new Thread(testStop);
t1.start();
// t1.sleep(500);
for (int i = 0; i < 1000; i++) {
System.out.println("main...."+i);
if (i == 900){
testStop.stop();
System.out.println("线程该停止了.");
}
}
}
}
5.2 线程休眠
package com.bigdata.demo03;
import com.bigdata.demo01.TestThread4;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 14:59
*/
public class TestSleep {
private int ticketNums = 10;
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) {
TestThread4 t4 = new TestThread4();
new Thread(t4,"小明").start();
new Thread(t4,"老师").start();
new Thread(t4,"黄牛党").start();
}
}
5.3 线程礼让
package com.bigdata.demo03;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 15:16
*/
public class TestYield {
public static void main(String[] args) {
MyYield m1 = new MyYield();
MyYield m2 = new MyYield();
new Thread(m1).start();
new Thread(m2).start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--> 正在执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "--> 正在停止");
}
}
5.4 线程插队
package com.bigdata.demo03;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 15:22
*/
public class TestJoin {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
for (int i = 0; i < 900; i++) {
System.out.println("main ... "+i);
if (i == 100 ){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("thread run....."+i);
}
}
}
5.5 线程的状态
package com.bigdata.demo03;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 15:53
*/
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread 正在运行");
}
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state);
while (!state.equals(Thread.State.TERMINATED)){
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
//System.out.println(thread.getState());
}
}
5.6 线程的优先级
package com.bigdata.demo03;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 16:25
*/
public class TestPro {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
MyThread1 t3 = new MyThread1();
MyThread1 t4 = new MyThread1();
t1.setPriority(8);
t1.start();
t2.setPriority(10);
t2.start();
t3.setPriority(7);
t3.start();
t4.setPriority(5);
t4.start();
}
}
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getPriority());
}
}
5.7 守护线程
package com.bigdata.demo03;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo03
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 16:37
*/
public class TestDaemon {
public static void main(String[] args) {
Thread god = new Thread(new God());
god.setDaemon(true);
Thread you = new Thread(new You());
god.start();
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 < 100; i++) {
System.out.println("我活着");
}
System.out.println("dead");
}
}
6. 线程同步
6.1 并发简介
并发: 同一个对象被多个线程同时操作
- 现实生活中,我们也会遇到同一个资源,多个人都想使用的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个的来
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成队列,等待前面线程使用完毕,下一个线程再使用
6.2 队列和锁
类似于厕所,你得排队上厕所,然后我锁门,谁也进不来
由于同一进程的多个线程共享同一块存储空间,在带来方便的时候,也带了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待
使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程被挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
6.3 三大不安全案例
不安全的集合
package com.bigdata.demo04;
import java.util.ArrayList;
import java.util.List;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 20:59
*/
public class UnsafeList {
public static void main(String[] args) {
List<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 com.bigdata.demo04;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
* 不安全的买票
* @author qingzhi.wu 2020/7/15 20:32
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket sta = new BuyTicket();
new Thread(sta,"苦逼的我").start();
new Thread(sta,"黄牛").start();
new Thread(sta,"你").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;
//买票
@Override
public void run() {
while (flag){
buy();
}
}
private void buy(){
if(ticketNums <= 0){
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到"+ticketNums--);
}
}
不安全的取钱
package com.bigdata.demo04;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 20:45
*/
public class TestUnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing gridFriend = new Drawing(account,100,"女朋友");
you.start();
gridFriend.start();
}
//模拟取款
static class Drawing extends Thread{
Account account;//账户
int drawingMoney;
String name;
public Drawing(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
this.name = name;
}
@Override
public void run() {
if (account.money -drawingMoney <0){
System.out.println(Thread.currentThread().getName()+ "钱不够,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
// nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为:"+account.money);
//System.out.println(this.getName() + "手里的钱: "+nowMoney);
}
}
//银行:账户
static class Account{
int money;
String name;
public Account() {
}
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
}
6.4 同步方法
- 由于我们可以通过
private
关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized
关键字,它包含两种用法:synchronized
方法和synchronized
块public synchronized void mehtod(int args)
synchronized
方法控制对象
的访问,每个对象对应一把锁,每个synchronized
方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷 若将一个大的方法申明为
synchronized
将会影响效率
package com.bigdata.demo04;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
* 不安全的买票
* @author qingzhi.wu 2020/7/15 20:32
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket sta = new BuyTicket();
new Thread(sta,"苦逼的我").start();
new Thread(sta,"黄牛").start();
new Thread(sta,"你").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;
//买票
@Override
public void run() {
while (flag){
buy();
}
}
private synchronized void buy(){
if(ticketNums <= 0){
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到"+ticketNums--);
}
}
6.5 同步块
- 同步块
synchronized(Obj){}
- Obj称之为同步监视器
- Obj可以使任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程
- 第一个线程访问,锁住同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
package com.bigdata.demo04;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 20:45
*/
public class TestUnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing gridFriend = new Drawing(account,100,"女朋友");
you.start();
gridFriend.start();
}
//模拟取款
static class Drawing extends Thread{
Account account;//账户
int drawingMoney;
String name;
public Drawing(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
this.name = name;
}
@Override
public void run() {
synchronized (account){
if (account.money -drawingMoney <0){
System.out.println(Thread.currentThread().getName()+ "钱不够,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
// nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为:"+account.money);
//System.out.println(this.getName() + "手里的钱: "+nowMoney);
}
}
}
//银行:账户
static class Account{
int money;
String name;
public Account() {
}
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
}
package com.bigdata.demo04;
import java.util.ArrayList;
import java.util.List;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 20:59
*/
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->
{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}
).start();
}
System.out.println(list.size());
}
}
锁住变化的量
6.6 CopyOnWriteArrayList
package com.bigdata.demo04;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo04
* Version: 1.0
* 测试JUC 安全类型的集合
* @author qingzhi.wu 2020/7/15 21:24
*/
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(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
6.7 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有
两个以上对象的锁
时,就可能发生死锁
的问题
package com.bigdata.demo05;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo05
* Version: 1.0
* 死锁: 多个线程互相抱着对方需要的资源,然后形成僵持
* @author qingzhi.wu 2020/7/15 21:34
*/
public class DeadLock {
public static void main(String[] args) {
Mackeup g1 = new Mackeup(0,"灰姑凉");
Mackeup g2 = new Mackeup(1,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Mackeup extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
public Mackeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
makeup()
}
private 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(2000);
synchronized (lipstick){
System.out.println(this.girlName +"获得口红的锁");
}
}
}
}
}
package com.bigdata.demo05;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo05
* Version: 1.0
* 死锁: 多个线程互相抱着对方需要的资源,然后形成僵持
* @author qingzhi.wu 2020/7/15 21:34
*/
public class DeadLock {
public static void main(String[] args) {
Mackeup g1 = new Mackeup(0,"灰姑凉");
Mackeup g2 = new Mackeup(1,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Mackeup extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
public Mackeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private 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(2000);
}
synchronized (lipstick){
System.out.println(this.girlName +"获得口红的锁");
}
}
}
}
-
产生死锁的四个必要条件:
- 互斥条件: 一个资源每次只能被一个进程使用
- 请求与保持条件: 一个进程因请求资源而阻塞时,对获得的资源保持不放
- 不剥夺条件: 进程已获得资源,在未使用完成之前,不能强行剥夺
- 循环等待条件: 若干进程之间形成一种头尾相连的循环等待资源的关系.
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
6.8 Lock(锁)
- 从JDK5.0开始,Java提供了更强大的线程同步机制—通过显示定义同步锁对对象来实现同步.同步锁使用Lock对象充当
- Lock接口是控制多个线程对共享资源进行访问的工具.锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与
synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
package com.bigdata.demo05;
import java.util.concurrent.locks.ReentrantLock;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo05
* Version: 1.0
*
* @author qingzhi.wu 2020/7/15 21:55
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
int ticketNums = 10;
//定义Lock 锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticketNums > 0) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(ticketNums--);
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
synchronized 与 Lock的对比
- Lock是显示锁(手动开启和关闭锁,别忘记关闭锁) synchronized 是隐士锁,出了作用域自动释放
- Lock只有代码块锁,synchronized 有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序 Lock ->同步代码块 (已经进入了方法体,分配了相应的资源) -> 同步方法(在方法体之外)
7. 线程通信问题
7.1 生产者消费者模式
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间互为依赖,互为条件
- 对于生产者,没有生产产品之前要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
- 在生产者消费者问题中,仅有synchronized 是不够的
- synchronized 可阻止并发更新同一个共享资源,实现了同步
- synchronized 不能用来实现不同县城之间的消息传递(通信)
7.2 管程法
- 生产者: 负责生产数据的模块
- 消费者: 负责处理数据逇模块
- 缓冲区: 消费者不能直接使用生产者的数据,他们之间有个缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
package com.bigdata.demo06;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo06
* Version: 1.0
* 测试: 生产者消费者模型 --> 利用缓冲区解决 管程法
*
* @author qingzhi.wu 2020/7/15 22:19
*/
public class TestPC {
public static void main(String[] args) {
SyncContainer container = new SyncContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
class Productor extends Thread {
SyncContainer container;
public Productor(SyncContainer container) {
this.container = container;
}
@Override
public void run() {
int i = 0;
while(true){
System.out.println("生产了" + i++ + "只鸡");
container.push(new Chicken(i));
}
}
}
class Consumer extends Thread {
SyncContainer container;
public Consumer(SyncContainer container) {
this.container = container;
}
@Override
public void run() {
for (; ; ) {
System.out.println("消费了-->" + container.pop().id);
}
}
}
class Chicken {
public int id;
public Chicken(int id) {
this.id = id;
}
}
class SyncContainer {
//需要一个容器大小
public Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了
if (count == chickens.length) {
// 通知消费者消费,生产等待
try {
this.notify();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
chickens[count++] = chicken;
this.notify();
}
//消费者消费产品
public synchronized Chicken pop() {
if (count == 0) {
//通知生产者生产,消费者等待
try {
this.notify();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
//Chicken chickens = null;
count--;
Chicken chicken = chickens[count];
return chicken;
}
}
7.2 信号灯法
package com.bigdata.demo06;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo06
* Version: 1.0
* 信号灯法
*
* @author qingzhi.wu 2020/7/16 10:31
*/
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("快乐大本营,播放中");
}else {
this.TV.play("抖音,记录美好生活");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
TV tv;
public Watcher(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 = !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;
}
}
8.0 线程池
经常创建和销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建消费,实现重复利用,类似生活中的公共交通工具
好处:
- 提高响应速度 减少了创建新线程的时间
- 降低资源消耗 重复利用线程池中线程,不需要每次都创建
- 便于线程管理
package com.bigdata.demo06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Copyright (c) 2020 bigdata ALL Rights Reserved
* Project: learning
* Package: com.bigdata.demo06
* Version: 1.0
*
* @author qingzhi.wu 2020/7/16 11:49
*/
public class TestPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new Thread1());
service.execute(new Thread1());
service.execute(new Thread1());
service.execute(new Thread1());
service.shutdown();
}
}
class Thread1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
}