多线程编程
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
main() 称之为主线程,为系统的入口,用于执行整个程序; - 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与
- 操作系统紧密相关的,先后顺序是不能认为的干预的。
- 对同一份资源操作时, 会存在资源抢夺的问题, 需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
如果有错,各位可以指出
进程和线程
进程
是一个正在执行中的程序就是一个进程,系统会为这个进程发配独立的内存资源。
是系统进行资源分配的基本单位
例如:正在运行的 QQ、IDE、浏览器就是进程
线程
又称轻量级进程
进程中的一条执行路径,也是CPU调度和执行的基本单位,一个进程由一个或多个线程组成,
彼此之间完成不同的工作,同时执行,称为多线程。
注意:很多 多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务
器。如果是模拟出来的多线程,即在一个cpu的情况下, 在同一个时间点,cpu只能
执行一个代码,因为切换的很快,所以就有同时执行的错局。
进程与线程的联系
序号 | 进程与线程的联系 |
---|---|
(1) | 一个进程最少拥有一个线程–主线程— 运行起来就执行的线程 |
(2) | 线程之间是共享内存资源的(这个内存资源是由进程申请的) |
(3) | 线程之间可以通信(进行数据传递:多数为主线程和子线程) |
创建子线程的原因
如果在主线程中存在有比较耗时的操作(例如:下载视频、上传文件 数据处理) 这些操作会阻塞主线程,后面的任务必须等这些任务执行完毕之后才能执行,用户体验比较差, 为了不阻塞主线程,需要将耗时的任务放在子线程去处理。
线程的组成
任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)会为每个线程分配执行时间。
- 运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
线程的特点
线程的特点
1、线程抢占式执行
- 效率高
- 可防正单一线程长时间独占CPU
2、在单核CPU中,宏观上同时执行,微观上顺序执行。
线程的创建
创建线程的三种方式:
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
继承Thread类
- 自定义线程类继承Thread类
- 重写run() 方法,编写线程的执行体
- 创建线程对象,调用start()方法启线程
/**
* 创建线程方式:继承Thread类,重写run() 方法,调用start开启线程
* 线程开启不一定立即执行,由cpu调度执行
*/
public class MyThread1 extends Thread{
@Override
public void run() {
for(int i=1;i<1000;i++) {
System.out.println("我是run()方法------"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
MyThread1 thread = new MyThread1();
//调用start()的方法,开启线程
thread.start();
for(int i=1;i<1000;i++) {
System.out.println("我在学习多线程-----"+i);
}
}
}
网图下载
添加依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//下载器
public class webDownloader {
//下载方法
public void downloader(String url,String name){
try {
//文件工具类,把网页地址变成一个文件
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread{
private String name;
private String url;
public MyThread2(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 class Test {
public static void main(String[] args) {
//创建线程对象
MyThread2 myThread1 = new MyThread2("https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2038406380,317017081&fm=5","1.jpg");
MyThread2 myThread2 = new MyThread2("https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2300875363,445064071&fm=5","2.jpg");
MyThread2 myThread3 = new MyThread2("https://blog.csdn.net/qian4517?spm=1000.2115.3001.5113","3.txt");
MyThread2 myThread4 = new MyThread2("https://blog.csdn.net/backbug/article/details/99572931","4.html");
myThread1.start();
myThread2.start();
myThread3.start();
myThread4.start();
}
}
实现Runnable接口
- 自定义线程类实现Runnable接口
- 实现run() 方法,编写线程的执行体
- 创建线程对象,调用start()方法启线程
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=1;i<1000;i++) {
System.out.println("我是run()方法------"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建runnable接口的实现类对象
Runnable runnable = new MyRunnable();
//创建线程对象,通过线程对象来开启我们的线程,代理
// Thread t1 = new Thread(runnable);
// t1.start();
new Thread(runnable).start();
for(int i=1;i<1000;i++) {
System.out.println("我在学习多线程-----"+i);
}
}
}
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象. start()
- 不建议使用:避免0OP单继承局限性
实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+ Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同- -个对象被多个线程使用
龟兔赛跑
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i <= 100 ; i++) {
//判断比赛是否结束
boolean flag = gameOver(i);
if(flag){ //比赛结束就停止程序
break;
}
System.out.println(name+"跑了"+i+"米");
//模拟兔子睡觉
if(name.equals("兔子") && i>= 100/2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//判断是否完成了比赛
private boolean gameOver(int distance){
//判断是否有胜利者,
if(winner != null){//存在胜利者
return true;
}
if(distance >= 100){
winner = Thread.currentThread().getName();;
System.out.println("胜利者:"+ winner);
return true;
}
return false;
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
Runnable race = new Race();
new Thread(race,"乌龟").start();
new Thread(race,"兔子").start();
//匿名内部内创建线程
//new 的是Runnable的子类,但是我们没有命名;
// Runnable runnable = new Runnable() {
// @Override
// public void run() {
//
// }
// };
// new Thread(runnable,"线程1");
//匿名内部类创建一个线程对象,
// new Thread(new Runnable() {
// @Override
// public void run() {
//
// }
// },"").start();
}
}
实现Callable接口
1.实现Callable接口, 需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.把Callable对象转成可执行任务:FutureTask task = new FutureTask<>(callable);
5.创建线程对象,调用start()方法启动线程
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Mycallable {
public static void main(String[] args) throws Exception{
//创建Callable对象,通过匿名内部类
Callable callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100 ; i++) {
sum+=i;
}
return sum;
}
};
//把Callable对象转成可执行任务
FutureTask<Integer> task = new FutureTask<>(callable);
//创建线程,启动线程
new Thread(task,"线程1").start();
//获取返回值,需要捕获或抛出异常
Integer sum1 = task.get();
System.out.println("结果为:"+sum1);
}
}
1.实现Callable接口, 需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行: Future result1 = ser.submit(t1);
6.获取结果: boolean r1 = result1.get()
7.关闭服务: ser.shutdownNow();
静态代理
在实现Runnable接口,创建线程使用了静态代理
/*
* 使用Runnable创建线程
* 1.类实现Runnable接口+重写run() --->真实角色类
* 2.启动多线程 使用静态代理
* 1)创建真实角色
* 2)创建代理角色+真实角色引用
* 3)调用start()方法启动线程
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
}
}
public class Test {
public static void main(String[] args) {
//创建runnable接口的实现类对象,真实对象
Runnable runnable = new MyRunnable();
//创建线程对象,通过线程对象来开启我们的线程,代理对象
Thread t1 = new Thread(runnable);
t1.start();
}
}
静态代理案例:
//真实对象,出售商品
public class Factory implements Product{
@Override
public void sale() {
System.out.println("卖出生产的产品");
}
}
//真实对象要干的事
public interface Product {
//出售
void sale();
}
//Proxy 代理角色,商品代理商
public class Agent implements Product{
//目标对象
private Factory target;
public Agent(Factory target) {
this.target = target;
}
//代理厂家出售商品
@Override
public void sale() {
before();//出售商品之前
this.target.sale();//目标对象
after();//出售商品之后
}
private void before() {
System.out.println("出售商品之前,代理商为商品做广告");
}
private void after() {
System.out.println("出售商品之后,代理商向厂家收取代理费用");
}
}
/**
* 静态代理总结
* 真实对象和代理对象要实现同一接口
* 代理对象要代理真实角色
*
*好处:
* 代理对象可以做很多真是对象做不了的事
* 真是对象专注做自己的事情
*/
public class StaticProxy {
public static void main(String[] args) {
//创建真实对象
Factory factory = new Factory();
//创建代理对象,把真是对象交给代理对象
Agent agent = new Agent(factory);
//代理对象为真实对象做事
agent.sale();
}
}
线程状态
线程的六种状态
参考:https://blog.csdn.net/pange1991/article/details/53860651
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。阻塞(BLOCKED):表示线程阻塞于锁。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
终止(TERMINATED):表示该线程已经执行完毕。
源码
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
线程的一些方法
方法 | 说明 |
---|---|
setPriority(int newPiority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
void join() | 强占CPU资源,执行该线程,其他线程等待该线程执行完 |
static void yield() | 暂停当前正在执行的线程,释放CPU资源,重新竞争时间片 |
void interrupt() | 中断线程,别使用这个方法 |
boolean isAlive() | 测试线程是否处于活动状态 |
线程停止
/**
* 停止线程
* 1.建议线程正常停止---> 利用次数,不建议死循环
* 2.建议使用标志位--->设置一个标志位
* 3.不要使用stop或者destroy等过时或者jdk不建议使用的方法
*/
public class MyThread implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run.....Thread"+i);
}
}
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
//创建Runnable实现类的对象
MyThread myThread = new MyThread();
//启动线程
new Thread(myThread).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if( i==900 ){
//调用stop方法,切换标志位
myThread.stop();
System.out.println("线程停止");
}
}
}
}
线程休眠_sleep
sleep (时间)指定当前线程阻塞的毫秒数;"
sleep存在异 常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时,倒计时等。’
每一个对象都有一 个锁,sleep不会释放锁;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSleep {
public static void main(String[] args){
timeDown();
startTime();
}
//模拟倒计时
public static void timeDown() {
int sumTime = 10;
while(true){
System.out.println(sumTime--);
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if(sumTime == 0){
break;
}
}
}
//每隔一秒,获取系统的时间
public static void startTime(){
//参数缺省,startTime会获取当前系统时间
// Date startTime = new Date();
Date startTime = new Date(System.currentTimeMillis());
while(true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date();//重新获取系统当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程让步_yield
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功!看CPU心情
/**
* 测试线程让步方法
* 让步不一定成功,看CPU调度
*/
public class TestYield implements Runnable{
public static void main(String[] args) {
//创建Runnable接口的实现类对象
TestYield testYield = new TestYield();
new Thread(testYield,"A").start();
new Thread(testYield,"B").start();
}
@Override
public void run() {
//可以多跑几遍
String name = Thread.currentThread().getName();
System.out.println(name+"线程开始执行");
Thread.yield();//线程让步
System.out.println(name+"线程停止执行");
}
}
线程强制执行_join
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
可以想象成插队
/**
* 测试join方法,想象成插队
*/
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("join 线程"+ i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();//启动
//主线程执行
for (int i = 0; i < 100; i++) {
if(i==50){
//抢占CPU资源,优先执行
thread.join(); //插队
}
System.out.println("主线程执行"+i);
}
}
}
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10.
Thread.MIN_ PRIORITY = 1;
Thread.MAX_ PRIORITY = 10;
Thread.NORM_ PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority() . setPriority(int xxx)
优先级的设定建议再start() 调度之前
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这要看CPU的调度,也有可能被调度在优先级高的线程之前。
public class TestPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
}
}
public class Test {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
TestPriority testPriority = new TestPriority();
Thread thread1 = new Thread(testPriority);
Thread thread2 = new Thread(testPriority);
Thread thread3 = new Thread(testPriority);
Thread thread4 = new Thread(testPriority);
Thread thread5 = new Thread(testPriority);
Thread thread6 = new Thread(testPriority);
//设置线程优先级
thread2.setPriority(4);
thread3.setPriority(Thread.MIN_PRIORITY);
thread4.setPriority(Thread.MAX_PRIORITY);
thread5.setPriority(6);
thread6.setPriority(7);
//设置优先级,再启动线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
thread6.start();
}
}
守护线程(daemon)
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如:后台记录操作日志,监控内存,垃圾回收等…
//用户线程
public class UserThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是用户线程,在活动"+i);
}
System.out.println("我结束了");
}
}
//守护线程
public class DaemonThread implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我是守护线程,一直在");
}
}
}
public class Test {
public static void main(String[] args) {
//创建守护线程对象
DaemonThread daemonThread = new DaemonThread();
//创建用户线程对象
UserThread userThread = new UserThread();
Thread daemon = new Thread(daemonThread);
//默认为false,表示用户线程,一般线程都是用户线程
daemon.setDaemon(true);//定义该线程为守线程
daemon.start();
Thread user = new Thread(userThread);
user.start();
}
}
线程同步
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需
要针对方法提出一套机制 ,这套机制就是synchronized关键字,它包括两种用法:
synchronized方法和synchronized块.
同步方法: public synchronized void method(int args) {}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个
synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,
方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获
得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
对于一个方法中,如果有只读代码和修改数据代码,但是修改的内容才需要加锁,
锁的太多,就浪费了资源
同步块
同步块: synchronized (Obj ){}
Obj 称之为同步监视器
Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是
这个对象本身,或者是class [反射中讲解]
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码.
- 第二个线程访问 ,发现同步监视器被锁定,无法访问.
- 第一个线程访问完毕,解锁同步监视器.
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
案例:1. 网上购票
//线程不安全,购买车票,需要使用线程同步
public class BuyTickets implements Runnable{
private int ticketNums = 10;
private boolean flag = true;//标志位
@Override
public void run() {
while (flag) {
buy();
}
}
//synchronized 同步方法,锁定的this
private synchronized void buy(){
//判断是否有票
if(ticketNums<=0){
System.out.println("余票不足");
flag = false;
return ; //结束当前程序块
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"购买了第"+ticketNums-- +"票");
}
}
public class Test {
public static void main(String[] args) {
Runnable buyTickets = new BuyTickets();
//模拟三个人同时购票
new Thread(buyTickets,"A---").start();
new Thread(buyTickets,"B---").start();
new Thread(buyTickets,"C---").start();
}
}
2.银行取钱
//账户
public class Account {
private String name; //账户名
private double RemainMoney;//余额
public Account(String name, double remainMoney) {
this.name = name;
RemainMoney = remainMoney;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getRemainMoney() {
return RemainMoney;
}
public void setRemainMoney(double remainMoney) {
RemainMoney = remainMoney;
}
}
//不安全的取钱
//两个人同时取同一个账户的钱
//原因:两个线程把账户拿到各自的内存中,不共享,同时操作了原数据
public class UnsafeBank extends Thread{
Account account;//账户
//取多少钱
double drawMoney;
//现在手里有多少钱
double nowMoney;
public UnsafeBank(Account account,double drawMoney,String name){
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
//取钱
@Override
public void run() {
Drawing();
}
public Account Drawing(){
//锁的对象是变化的量
synchronized (account) {
if (account.getRemainMoney() - drawMoney <= 0) {
System.out.println(Thread.currentThread().getName() + "取钱,不好意思,余额不足");
return account;
}
//确保两人都看到未取钱前的余额
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 取完后,账户剩下的余额
account.setRemainMoney(account.getRemainMoney() - drawMoney);
//手里的钱
nowMoney = nowMoney + drawMoney;
System.out.println(Thread.currentThread().getName() + "手里的钱为" + nowMoney);
System.out.println(account.getName() + "的账户余额为" + account.getRemainMoney());
return account;
}
}
}
public class Test {
public static void main(String[] args) {
//创建一个账户对象
Account account = new Account("小明",200);
//新建两个取钱对象
UnsafeBank you = new UnsafeBank(account,50,"你");
UnsafeBank girlFriend = new UnsafeBank(account,100,"girlFriend");
you.start();
girlFriend.start();
}
}
3.不安全的集合
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
//两个线程操作了同一个位置
public class unsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
//使用线程向集合中加入数据
//使用lambda表达式
new Thread(() -> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
//等待数据添加完全
Thread.sleep(3000);
//会出现小于10000的情况;
System.out.println(list.size());
}
}
死锁
多个线程各自占有一-些共享资源,并且互相等待其他线程占有的资源才能运行,而
导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块
同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.
public class DeadLock {
public static void main(String[] args) {
//两把锁
String A = "a";
String B = "b";
new Thread(()->{
String name = Thread.currentThread().getName();
//先获得A锁
synchronized (A){
System.out.println(name+"获得了锁"+A+",等待获得锁"+B);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再获得B锁
synchronized (B){
System.out.println(name+"获得了锁"+B+",运行结束");
}
}
},"线程1").start();
new Thread(()->{
String name = Thread.currentThread().getName();
//先获得B锁
synchronized (B){
System.out.println(name+"获得了锁"+B+",等待获得锁"+A);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再获得A锁
synchronized (A){
System.out.println(name+"获得了锁"+A+",运行结束");
}
}
},"线程2").start();
}
}
死锁避免方法
产生死锁的四个必要条件:
- 互斥条件: 一个资源每次只能被一个进程使用。
- 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件 :进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成-种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多 个条件
就可以避免死锁发生
Lock(锁)
-
从JDK 5.0开始,Java提供了更强大的线程同步机制一通过 显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock(可重入锁) 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
public class ReentrantLockTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class Ticket implements Runnable{
int num = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
try {//推荐使用
lock.lock();//加锁
if (num > 0) {
try {
//增加问题发生的概率
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
} else {
break;
}
} finally {
lock.unlock();//释放锁
}
}
}
}
synchronized和lock区别
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了
作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)
线程的交互
这三个方法是 Object 对象的不是线程的方法
在开始讲解等待唤醒机制之前,有必要搞清一个概念 - -线程之间的通信:多个线程在处理同
一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资
源。而这种手段即-----等待唤醒机制 。
I
等待唤醒机制所涉及到的方法:
wait () :等待, 将正在执行的线程释放其执行资格和执行权,并存储到线程池中。
notify () :唤醒,唤醒线程池中被wait ()的线程,-一次唤醒一个,而且是任意的。
notifyAll () :唤醒全部: 可以将线程池中的所有wait()线程都唤醒。
其实,所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在
同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是
哪个锁上的线程。
交替打印AB
public class Test {
public static void main(String[] args) {
String lock = "lock";
//lambda表达式
new Thread(() -> {
//同步代码块
synchronized (lock) {
try {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "-" + "A");
if(i !=6) lock.notify();//唤醒
if(i !=5) lock.wait();//等待,释放锁
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, "线程A").start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
//同步代码块
synchronized (lock) {
try {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "-" + "B");
if(i != 5) lock.notify();//唤醒
if(i !=5) lock.wait();//等待
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, "线程B").start();
}
}
等待与唤醒,案例2
/**
* 定义一个资源类,有两个成员变量
* 同时有两个线程,对资源中的变量进行操作
* 一个对变量赋值
* 一个对变量进行输出打印
*
* flag = true 表示赋值完成。等待输出
* flag = false 表示输出值完成,等待赋值
*/
public class Resource {
public String name;
public String sex;
public boolean flag = false;
}
/**
* 输入线程,对资源对象Resource的成员变量进行赋值
*
* 需不需要执行赋值语句,看标记,如果标记为true等待
* 如果为false不需要等待,进行赋值
*/
public class Input implements Runnable{
//定义Resource对象
private Resource r;
public Input(Resource r){
this.r = r;
}
@Override
public void run() {
int i = 0;
while(true){
//交叉打印
synchronized (r) {
//flag = true ,等待
if(r.flag){
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i % 2 == 0) {
r.name = "张三";
r.sex = "男";
} else {
r.name = "lisi";
r.sex = "nv";
}
i++;
//将对方线程唤醒,标记改为true,表示赋值完成
r.flag = true;
r.notify();//唤醒
}
}
}
}
/**
* 输出线程,对资源对象Resource的成员变量,输出值
*
* 如果 flag = true ,表示input完成了赋值,需要进行输出
* 输出完成后,把flag 改为 false,表示要进行赋值
*/
public class Output implements Runnable{
private Resource r;
public Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized (r) {
// 判断标记,是false,等待
if(!r.flag){
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(r.name + "..." + r.sex);
//标记改成false,唤醒对方线程
r.flag = false;
r.notify();
}
}
}
}
public class Test {
public static void main(String[] args) {
Resource r = new Resource();
//创建Runnable实现类对象
Input input = new Input(r);
Output output = new Output(r);
new Thread(input).start();
new Thread(output).start();
}
}
生产者和消费者
并发协作模型 ”生产者/消费者模式” -->管程法
◆生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
◆消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
◆缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//产品
public class Bread {
public int id;
public Bread(int id) {
this.id = id;
}
}
//缓冲区
public class SynContainer {
//需要一个容器大小
private Bread[] breads = new Bread[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Bread bread){
//如果容器满了,就需要等待消费者消费
if (count >= breads.length){
//通知消费者消费,生产者等待
try{
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
breads[count] = bread;
System.out.println("生产了第"+bread.id+"片面包");
count++;
//通知消费者消费
this.notify();
}
//消费者消费产品
public synchronized Bread pop(){
//判断能否消费
if(count <= 0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count --;
Bread bread = breads[count];
System.out.println("消费了--->第"+bread.id+"片面包");
//吃完了,通知生产者生产
this.notify();
return bread;
}
}
//消费者
public class Consumer extends Thread{
SynContainer synContainer;
public Consumer( SynContainer synContainer){
this.synContainer = synContainer;
}
//消费
@Override
public void run() {
for (int i = 1; i < 100; i++) {
synContainer.pop();
}
}
}
//生产者
public class Producer extends Thread{
SynContainer synContainer;
public Producer( SynContainer synContainer){
this.synContainer = synContainer;
}
//生产
@Override
public void run() {
for (int i = 1; i <100 ; i++) {
synContainer.push(new Bread(i));
}
}
}
public class Test {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Producer(synContainer).start();
new Consumer(synContainer).start();
}
}
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具~书
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(…)
- corePoolSize: 核心池的大小
- maximumPoolSize: 最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池使用
JDK 5.0起提供了线程池相关API: ExecutorServise和Executors
ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor
-
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执
行Runnable -
Future submit(Callable task):执行任务,有返回值,一般用来执行
Callable -
void shutdown() :关闭连接池
Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.Callable;
public class CPool implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
public class RPool implements Runnable{
@Override
public void run() {
System.out.println( Thread.currentThread().getName());
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建服务,2.创建线程池
//参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(2);
//执行 实现Runnable的,直接从线程池中获取,看到两条线程的name
service.execute(new RPool());
service.execute(new RPool());
service.execute(new RPool());
service.execute(new RPool());
//实现Callable的
Future<String> future1 = service.submit(new CPool());
Future<String> future2 = service.submit(new CPool());
Future<String> future3 = service.submit(new CPool());
Future<String> future4 = service.submit(new CPool());
//获取线程池大小
System.out.println(future1.get());
System.out.println(future2.get());
System.out.println(future3.get());
System.out.println(future4.get());
//关闭服务
service.shutdown();
}
}
参考视频:视频