多线程
多线程笔记
线程简介

Process与Thread
- 程序是指令和数据的有序集合,本身没有任何运行的含义,是一个静态的概念。
- 进程(Process)则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位
- 通常在一个进程中可以包含若干个线程(Thread),一个进程中至少有一个主线程(main)。线程是CPU调度和执行的单位
Tips:大多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,就有同时执行的错觉。
核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
- main()称之为主线程,是系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由cpu安排调度,cpu是与操作系统紧密相关的,先后顺序不能人为干预
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制
- 线程会带来额外开销,如cpu调度时间,并发控制开销
- 每个线程只能在自己的工作内存交互,内存控制不当会造成数据不一致。
线程创建

继承Thread类

package com.test.demo01;
//创建线程方式一:继承Thread类,重写run方法,调用start开启线程
//线程开启不一定立即执行,由CPU调度执行
public class TestThread extends Thread{
@Override
public void run() {
//run方法线程体
for(int i=1;i<=10;i++){
System.out.println("Thread:"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建线程对象
TestThread thread=new TestThread();
//调用start方法开启线程
thread.start();
for(int i=1;i<=100;i++){
System.out.println("main:"+i);
}
}
}
实现Runnable接口

package com.test.demo01;
//创建线程方式2 :实现Runnable接口,重写run方法,执行线程需要丢入Runnable接口实现类,调用start方法
public class TestRunnable implements Runnable{
@Override
public void run() {
//run方法线程体
for(int i=1;i<=10;i++){
System.out.println("Thread:"+i);
}
}
public static void main(String[] args) {
//main线程,主线程y6
//创建Runnable接口的实现类对象
TestRunnable runnable=new TestRunnable();
// //创建线程对象,通过线程对象开启线程,代理
// Thread thread=new Thread(runnable);
// //调用start方法开启线程
//
// thread.start();
new Thread(runnable).start();
for(int i=1;i<=100;i++){
System.out.println("main:"+i);
}
}
}
小结:

实现Callable接口

package com.test.demo02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//练习 thread ,实现多线程同步下载图片
public class TestCallable implements Callable<String> {
private String url; //图片地址
private String name; //保存的文件名 要加后缀名!
public TestCallable(String url, String name) {
this.url = url;
this.name= name;
}
@Override
public String call() {
WebDownLoader webDownLoader=new WebDownLoader();
webDownLoader.downLoader(url,name);
System.out.println("下载了文件名为:"+name);
return "下载了文件名为:"+name;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable thread1=new TestCallable("https://wx3.sinaimg.cn/mw690/0026VLyFgy1gukfxaiw37j635s2dc1ky02.jpg","早安.jpg");
TestCallable thread2=new TestCallable("https://wx4.sinaimg.cn/mw690/0026VLyFly1guk1gauncxj60u00jztec02.jpg","雨夜.jpg");
TestCallable thread3=new TestCallable("https://wx1.sinaimg.cn/mw690/0026VLyFgy1gujl757wejj60tv0tvwoc02.jpg","o(=•ェ•=)m.jpg");
//创建线程池
ExecutorService service= Executors.newFixedThreadPool(3);
//提交服务执行
Future<String> res1= service.submit(thread1);
Future<String> res2= service.submit(thread2);
Future<String> res3= service.submit(thread3);
//获取返回值
String r1= res1.get();
String r2= res2.get();
String r3= res3.get();
System.out.println("最后的返回值按顺序输出");
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
//关闭服务
service.shutdownNow();
}
}
//下载器
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出现问题!");
}
}
}
静态代理模式
package com.test.StaticProxy;
//好处
//代理角色可以增加真实角色的功能:如记录日志
//真实角色专注自己的功能
public class StaticProxy {
public static void main(String[] args) {
Person person=new Person();
WeddingCompany company=new WeddingCompany(person);
company.HappyMarry();
//new WeddingCompany(new Person()).HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class Person implements Marry{
public void HappyMarry() {
System.out.println("我要结婚了");
}
}
//代理角色
class WeddingCompany implements Marry{
//代理谁--->真是目标角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚后");
}
private void before() {
System.out.println("结婚前");
}
}
Lamda表达式

package com.test.lamda;
//推导lamda表达式
public class LamdaTest {
//3.静态内部类
static class OU2 implements IOU {
@Override
public void lamda() {
System.out.println("静态内部类:lamda is nice");
}
}
public static void main(String[] args) {
IOU ou=new OU1();
ou.lamda();
ou=new OU2();
ou.lamda();
//4.局部内部类
class OU3 implements IOU{
@Override
public void lamda() {
System.out.println("局部内部类:lamda is nice");
}
}
ou=new OU3();
ou.lamda();
//5.匿名内部类:没有类的名称,必须借助接口或者父类
ou=new IOU() {
@Override
public void lamda() {
System.out.println("匿名内部类:lamda is nice");
}
};
ou.lamda();
//6.用lamda简化
ou=()-> {
System.out.println("lamda:lamda is nice");
};
ou.lamda();
}
}
//1.定义一个函数式接口
interface IOU{
void lamda();
}
//2.实现类
class OU1 implements IOU{
@Override
public void lamda() {
System.out.println("实现类:lamda is nice");
}
}

package com.test.lamda;
public class TestLamda {
public static void main(String[] args) {
Love love=((p1, p2) -> {System.out.println(p1+":I love you"); System.out.println(p2+":I love you too");});
//数据类型要么全省要么都不省
love.Person("JOJO","Ray");
}
}
interface Love{
void Person(String p1,String p2);
}
线程状态


线程方法
| 方法 | 说明 |
|---|---|
| setPriority(int newPriority) | 更改线程的优先级 |
| static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
| void join() | 等待该线程终止 |
| static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
| boolean isAlive | 测试线程是否处于活动状态 |
线程停止
package com.test.State;
//测试stop
//建议线程正常停止——利用次数循环
//使用标志位
public class TestStop implements Runnable{
//设置一个标志位
private boolean flag=true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("Run:"+i++); //循环结束?线程应该没结束吧?
}
}
//设置公开的方法停止线程,转换标志位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop=new TestStop();
new Thread(testStop).start();
for(int i=1;i<=500;i++){
System.out.println("main:"+i);
if(i==250){
//调用stop方法转换标志位。停止线程
testStop.stop();
System.out.println("线程结束!主线程继续执行到500");
}
}
}
}
线程休眠
- sleep(millions):指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
package com.test.State;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟倒计时
public class TestSleep2 {
public static void main(String[] args) throws InterruptedException {
//模拟倒计时
time();
System.out.println("开始计时!");
//获取系统当前时间
Date currenTime=new Date();
while(true){
System.out.println(new SimpleDateFormat("HH:mm:ss").format(currenTime));
Thread.sleep(1000);
currenTime=new Date();
}
}
public static void time() throws InterruptedException {
int num=10;
while(true){
if(num<0){
break;
}
else{
System.out.println(num--);
}
Thread.sleep(1000);
}
}
}
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,不一定成功
package com.test.State;
//礼让就是让应执行的线程退回就绪状态,即与重新争取执行权
//礼让不一定成功,具有随机性,决定权在CPU
public class TestYield {
public static void main(String[] args) {
Yield yield=new Yield();
new Thread(yield,"A").start();
new Thread(yield,"B").start();
}
}
class Yield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
if(Thread.currentThread().getName()=="A"){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"结束执行");
}
}
线程强制执行
- Join合并线程,待此线程执行完成后,再执行其他线程。其他线程为阻塞状态
package com.test.State;
//可以想象为插队
//前100 VIP和main同时执行,当到了100时,VIP插队,main线程需等待其执行完后再执行
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 {
TestJoin join=new TestJoin();
Thread thread=new Thread(join);
thread.start();
for (int i = 0; i < 200; i++) {
if(i==100){
thread.join();
}
System.out.println("main:"+i);
}
}
}
线程状态观测
-
NEW
尚未启动的线程处于此状态
-
RUNNABLE
在Java虚拟机中执行的线程
-
BLOCKED
被阻塞等待监视器锁定的线程
-
WAITING
正在等待另一个线程执行待定动作的线程
-
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程
-
TERMINATED
已退出的线程
package com.test.State;
public class TestState {
public static void main(String[] arg) {
Time time=new Time();
Thread thread=new Thread(time);
Thread.State state=thread.getState();
System.out.println(state);// new
thread.start();
state=thread.getState();//
System.out.println(state);//RUNNABLE
while(state!=Thread.State.TERMINATED){ //线程未结束一直输出状态
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
state=thread.getState();
System.out.println(state);
}
}
}
class Time implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 线程的优先级用1~10表示,默认为5
- Thread.MIN_PRIORITY=1
- Thread.MAX_PRIORITY=10
- Thread.NORM_PRIORITY=5
- 使用getPriority()获取优先级
- 使用setPriority(int num)设置优先级
package com.test.State;
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"——>"+Thread.currentThread().getPriority());
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread);
Thread thread2=new Thread(myThread);
Thread thread3=new Thread(myThread);
Thread thread4=new Thread(myThread);
Thread thread5=new Thread(myThread);
//需在线程启动前指定优先级;优先级只是让概率变高,并不会直接影响线程调度(等于没用?,还是得看我CPU大哥)
thread1.setPriority(1);
thread1.start();
thread2.setPriority(2);
thread2.start();
thread3.setPriority(Thread.MIN_PRIORITY);
thread3.start();
thread4.setPriority(4);
thread4.start();
thread5.setPriority(Thread.MAX_PRIORITY);
thread5.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"——>"+Thread.currentThread().getPriority());
}
}
守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 守护线程用于后台记录操作日志,监控内存,垃圾回收等待。。
package com.test.State;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class TestDaemon {
public static void main(String[] args) {
JOJO jojo= new JOJO();
RAY ray=new RAY();
Thread thread=new Thread(jojo);
thread.setDaemon(true); //默认为false 用户线程
thread.start(); //JOJO启动
new Thread(ray).start(); // RAY启动
//虚拟机停止需要等待时间,此时守护线程JOJO仍继续跑
}
}
class JOJO implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("JOJO LOVE RAY!");
}
}
}
class RAY implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("RAY 很 开 心");
}
System.out.println("RAY LOVE JOJO");
}
}
线程同步
并发
- 同一个对象被多个线程同时操作
- 上万人同时100张票
- 网上,银行同时取钱
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,需要线程同步。
- 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用
队列和锁
- 线程同步需要队列+锁,保证线程安全
- 每个对象都有锁
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可
- 存在以下问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
三大不安全案例
买票
package com.test.syn;
//不安全的买票
//会造成多人买同一张票的情况
public class UnsafeBuyTickets {
public static void main(String[] args) {
BuyTicket buyTicket=new BuyTicket();
new Thread(buyTicket,"JOJO").start();
new Thread(buyTicket,"RAY").start();
new Thread(buyTicket,"FISH").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;
}
//模拟延时 放大问题发生性
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+TicketNums--+"张票");
}
}
两人一卡同时取钱
package com.test.syn;
public class UnsafeBank {
public static void main(String[] args) {
User user=new User("奖学金",2000);
Draw Owner=new Draw(user,500,"我");
Draw Parents=new Draw(user,2000,"父母");
Owner.start();
Parents.start();
}
}
class User{
String name;
int money;
public User(String name, int money) {
this.name = name;
this.money = money;
}
}
//模拟取钱
class Draw extends Thread{
User user; //账户
int DrawMoney; //取的钱数
int HandMoney;//现在余额
public Draw(User user,int DrawMoney,String name){
super(name); //=Thread(name)
this.user=user;
this.DrawMoney=DrawMoney;
}
//取钱
@Override
public void run() {
//账户余额-取的钱数<=0,钱不够
if(user.money-DrawMoney<=0){
System.out.println(Thread.currentThread().getName()+"余额不足,无法取款!");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//现在的余额=账户余额-取出的钱数
user.money=user.money-DrawMoney;
//手上的钱=原来持有的钱+取出的钱
HandMoney = HandMoney +DrawMoney;
System.out.println(Thread.currentThread().getName()+"取出"+DrawMoney+"元成功!");
System.out.println(user.name+"余额:"+user.money); //可能取钱的人不是银行卡持有者
//Draw 继承 Thread,Thread.currentThread().getName()=this.getName();
System.out.println(this.getName()+"持有:"+HandMoney);
}
}
ArrayList
package com.test.syn;
// 多个线程看到的是同一片内存,造成复用
import java.util.ArrayList;
import java.util.List;
public class UnsafeArrayList {
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(); //lamda
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
同步方法
-
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要对方法提出一套机制,这套机制就是 synchronized 关键字
-
synchronized 方法
public synchronized void method(int args){} -
synchronized 块
synchronized (Obj){}- Obj称为 同步监视器
- Obj可以是任何对象,推荐使用共享资源(即需要变化的对象)作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
-
-
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程就获得这个锁,继续执行该方法。
缺陷:方法里需要修改的资源才需要锁,锁的太多,造成资源浪费,若一个大的方法申明为synchronized方法将会影响效率
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁定,然后锁定并访问
买票
package com.test.syn;
//不安全的买票
//会造成多人买同一张票的情况
public class UnsafeBuyTickets {
public static void main(String[] args) {
BuyTicket buyTicket=new BuyTicket();
new Thread(buyTicket,"JOJO").start();
new Thread(buyTicket,"RAY").start();
new Thread(buyTicket,"FISH").start();
}
}
class BuyTicket implements Runnable{
private int TicketNums=100;
boolean flag=true;
@Override
public void run() {
while (flag){
Buy();
//模拟延时 放大问题发生性
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized void Buy() {
if(TicketNums<=0){
flag=false;
return;//停止,退出run方法
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+TicketNums--+"张票");
}
}
两人一卡同时取钱
package com.test.syn;
public class UnsafeBank {
public static void main(String[] args) {
//这里new了两个user,锁的是两个不同的对象,单纯在方法上加syn锁不到两个对象,要用sync块
User user=new User("奖学金",2000);
Draw Owner=new Draw(user,500,"我");
Draw Parents=new Draw(user,2000,"父母");
Owner.start();
Parents.start();
}
}
class User{
String name;
int money;
public User(String name, int money) {
this.name = name;
this.money = money;
}
}
//模拟取钱
class Draw extends Thread{
User user; //账户
int DrawMoney; //取的钱数
int HandMoney;//现在余额
public Draw(User user,int DrawMoney,String name){
super(name); //=Thread(name)
this.user=user;
this.DrawMoney=DrawMoney;
}
//取钱
@Override
public void run() {
synchronized (user){
//账户余额-取的钱数<=0,钱不够
if(user.money-DrawMoney<=0){
System.out.println(Thread.currentThread().getName()+"想取"+DrawMoney);
System.out.println(user.name+"余额不足,无法取款!");
return; //停止,退出run方法
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//现在的余额=账户余额-取出的钱数
user.money=user.money-DrawMoney;
//手上的钱=原来持有的钱+取出的钱
HandMoney = HandMoney +DrawMoney;
System.out.println(Thread.currentThread().getName()+"取出"+DrawMoney+"元成功!");
System.out.println(user.name+"余额:"+user.money); //可能取钱的人不是银行卡持有者
//Draw 继承 Thread,Thread.currentThread().getName()=this.getName();
System.out.println(this.getName()+"持有:"+HandMoney);
}
}
}
ArrayList
package com.test.syn;
// 多个线程看到的是同一片内存,造成复用
import java.util.ArrayList;
import java.util.List;
public class UnsafeArrayList {
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(); //lamda
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
tip:没有sleep线程运行过快导致锁不住
死锁
多个线程独自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有两个以上对象的锁,就可能发生死锁。(你需要我的资源,我需要你的资源,双方都想对方先给)
package com.test.Lock;
//多个线程互相拿着对方需要的资源,造成僵持的局面
public class DeadLock {
public static void main(String[] args) {
Eat eat=new Eat(0,"JOJO");
Eat eat1=new Eat(1,"Ray");
eat.start();
eat1.start();
}
}
//水果
class Fruit{
}
//蔬菜
class Vegetable{
}
class Eat extends Thread{
//static保证需要的资源只有一份
static Fruit fruit=new Fruit();
static Vegetable vegetable=new Vegetable();
int choice; //选择
String name; //吃货
public Eat(int choice,String name){
this.choice=choice;
this.name=name;
}
@Override
public void run() {
try {
eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//互相拿着对方想吃的食物
private void eat() throws InterruptedException {
if(choice==0){
synchronized (fruit){
System.out.println(this.name+"获得水果的锁");
Thread.sleep(1000);
// //一秒钟后想吃蔬菜
// synchronized (vegetable){
// System.out.println(this.name+"获得蔬菜的锁");
// }
}
//先把水果的锁释放,再去拿现在需要的蔬菜,不能既拿着水果的锁,又拿着蔬菜的锁,鱼和熊掌不可兼得
synchronized (vegetable){
System.out.println(this.name+"获得蔬菜的锁");
}
}
else{
synchronized (vegetable){
System.out.println(this.name+"获得蔬菜的锁");
Thread.sleep(2000);
// //两秒钟后想吃水果
// synchronized (fruit){
// System.out.println(this.name+"获得水果的锁");
}
//先把蔬菜的锁释放,再去拿现在需要的水果,不能既拿着水果的锁,又拿着蔬菜的锁,鱼和熊掌不可兼得
synchronized (fruit){
System.out.println(this.name+"获得水果的锁");
}
}
}
}
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
只要想办法破坏其中任意一个或多个条件就可以避免死锁发生
Lock(锁)
-
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class A{
private final ReentrantLock lock=new ReentrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码
}
finally{
lock.unlock();
//如果同步代码有异常,要将unlock()写入finally语句块,必然执行
}
}
}
package com.test.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(ticket,"JOJO").start();
new Thread(ticket,"Ray").start();
new Thread(ticket,"X").start();
}
}
class Ticket implements Runnable{
int tickets=10;
//定义可重入锁ReentrantLock
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){
try {
//加锁
lock.lock();
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"拿到第"+tickets--+"张票");
}
else{
System.out.println("没有票了");
break;
}
}
finally {
//解锁
lock.unlock();
}
}
}
}
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁)
- synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更高的可扩展性(提供更多子类)
- 优先使用顺序
- Lock——>同步代码块(已经进入了方法体,分配了相应资源)——>同步方法(在方法体之外)
线程协作
线程通信
- 应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
- 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品后又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者消费完毕,需要生产新的产品以供消费
-
Java提供几个方法解决线程之间的通信问题
方法名 作用 wait() 表示线程一直等待,直到其他线程通知,会释放锁 wait(long timeout) 指定等待的毫秒数 notify() 唤醒一个处于等待状态的线程 notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的优先调度 Tips:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException
管程法(缓冲区)

package com.test.PtoCProblem;
//生产者消费者问题——>利用缓冲区解决
//管程法
//生产者 消费者 产品 缓冲区
public class Moniters {
public static void main(String[] args) {
SynContainer container=new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"号牛肉");
container.setBeefs(new Beef(i)); //把当前牛肉放入容器
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了"+container.getBeefs().id+"号牛肉");
}
}
}
//产品
class Beef {
int id; //产品编号
public Beef(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer {
//需要一个容器大小
Beef[] beefs=new Beef[10];
//容器计数器
int count=0;
//生产者放入产品
public synchronized void setBeefs(Beef beef){
//容器满了,等待消费者消费
while(count==beefs.length){ //if语句中醒来的线程 不会再一次进行判断了 而while会重新再判断
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没满,需要放入产品
beefs[count]=beef;
count++; //容器里的产品+1
//通知消费者取出产品
this.notifyAll();
}
//消费者取走产品
public synchronized Beef getBeefs(){
//判断能否取走
while(count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//能取走
count--; //因为生产者生产完会+1,最后的数组下标会多1,所以这里要拿到应该-1
Beef beef=beefs[count];
//通知生产者生产
this.notifyAll();
return beef; //返回取走的产品
}
}
信号灯法(标志位)
package com.test.PtoCProblem;
import com.sun.org.apache.bcel.internal.generic.NEW;
//生产者消费者问题——>标志位
//信号灯法
public class Signer {
public static void main(String[] args) {
Bv bv=new Bv();
new UP(bv).start();
new User(bv).start();
}
}
//生产者——>UP主
class UP extends Thread{
Bv bv;
public UP(Bv bv) {
this.bv = bv;
}
@Override
public void run() {
for (int i = 0; i < 100;i++) {
if(i%2==0){
this.bv.Upload("JOJO");
}
else {
this.bv.Upload("Ray");
}
}
}
}
//消费者——>用户
class User extends Thread{
Bv bv;
public User(Bv bv) {
this.bv = bv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.bv.view();
}
}
}
//产品——>视频
class Bv{
//UP主上传,用户等待 T
//用户观看,UP主等待 F
String name;
boolean flag=true;
//上传
public synchronized void Upload(String name){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("UP主上传"+name);
//通知用户观看
this.notifyAll();
this.name=name;
this.flag=!this.flag;
}
//观看
public synchronized void view(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用户观看"+name);
//通知UP主上传
this.notifyAll();
this.flag=!this.flag;
}
}
线程池
背景:
- 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:
-
提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁,实现重复利用
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间会终止
使用线程池
- JDK 5.0 起提供了线程池相关API:ExecutorServie和Executors
- ExecutorService:真正的线程池接口。常见子类:ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable - void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package com.test.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
public static void main(String[] args) {
//创建服务,建立线程池
ExecutorService service= Executors.newFixedThreadPool(10); //参数为线程池大小
//服务
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭服务
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

浙公网安备 33010602011771号