1 线程同步
多个线程操作同一个资源 synchronized
-
并发:同一个对象被多个线程同时执行
-
某个线程想修改对象,需要线程同步
-
形成条件:队列+锁(解决线程不安全问题)
存在问题
-
一个线程持有锁会导致其他所有需要此锁的线程挂起
-
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度问题,引起性能问题
-
如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
(1)不安全的买票问题
//不安全的买票问题 public class UnsafeBuyTicket { } class BuyTicket implements Runnable{ private int tickNums=10;//票 boolean flag=true; //停止线程标记位 @Override public void run() { //买票 while (flag){ buy(); } } private void buy(){ if(tickNums<=0){ flag=false; return; } //买票 System.out.println(Thread.currentThread().getName()+"买到"+tickNums--); } public static void main(String[] args) { BuyTicket station=new BuyTicket(); new Thread(station,"A").start(); new Thread(station,"B").start(); new Thread(station,"C").start(); } }
(2)不安全的银行取钱问题
//不安全的银行取钱问题 //两个人去银行取钱 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(1000,"Bank"); Drawing drawing = new Drawing(account,500); new Thread(drawing,"A").start(); // new Thread(drawing,"C").start(); new Thread(drawing,"B").start(); } } //账户 class Account{ int money; //余额 String name; //卡名 public Account(int money, String name) { this.money = money; this.name = name; } public Account(){ } } //银行:模拟取款 class Drawing implements Runnable{ Account account; //账户 int draw; //取钱金额 int nowMoney; //现在手里有多少钱 public Drawing(Account account,int draw){ this.account=account; this.draw=draw; } @Override public void run() { //1.判断账户了的金额 if (account.money-draw<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //2.卡里余额 account.money=account.money-draw; //3.拿出来的钱 nowMoney=nowMoney+draw; System.out.println(account.name+"余额:"+account.money); System.out.println(Thread.currentThread().getName()+"手里的钱:"+nowMoney); } }
(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 < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(list.size()); //输出集合的大小 } }
同步方法
(1)synchronized关键字
-
synchronized同步方法
private synchronized void buy()
-
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized 将会影响效率
-
-
synchronized同步块
-
synchronized(Obj){ }
-
Obj称之为同步监视器
- Obj可以是任何对象
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this
-
同步监视器执行过程
(1)第一个线程访问,锁定同步监视器,执行其代码
(2)第二个线程访问,发现同步监视器被锁定,无法访问
(3)第一个线程访问完毕,解锁同步监视器
(4)第二个线程访问,发现同步监视器没有锁,然后锁定并访问
-
(1)不安全的买票问题
//安全的买票问题
//锁的是增删改查的对象
public class UnsafeBuyTicket {
}
class BuyTicket implements Runnable{
private int tickNums=10;//票
boolean flag=true; //停止线程标记位
@Override
public void run() {
//买票
while (flag){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
//synchronized 同步方法 锁的是this
private synchronized void buy(){
if(tickNums<=0){
flag=false;
return;
}
//买票
System.out.println(Thread.currentThread().getName()+"买到"+tickNums--);
}
public static void main(String[] args) {
BuyTicket station=new BuyTicket();
new Thread(station,"A").start();
new Thread(station,"B").start();
new Thread(station,"C").start();
}
}
(2)安全的银行取钱问题
//安全的银行取钱问题
//两个人去银行取钱
//锁的是共同的操作对象 synchronized方法只能锁当前对象
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(1000,"Bank");
Drawing A = new Drawing(account,500,"A");
Drawing B = new Drawing(account,500,"B");
Drawing C = new Drawing(account,500,"C");
A.start();
B.start();
C.start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public Account(){
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account; //账户
int draw; //取钱金额
int nowMoney; //现在手里有多少钱
public Drawing(Account account,int draw,String name){
super(name);
this.account=account;
this.draw=draw;
}
//synchronized默认锁的是this Drawing,但是增删改查的对象是account
@Override
public void run() {
synchronized (account){
//1.判断账户了的金额
if (account.money-draw<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
//sleep可以放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.卡里余额
account.money=account.money-draw;
//3.拿出来的钱
nowMoney+=draw;
System.out.println(account.name+"余额:"+account.money);
System.out.println(Thread.currentThread().getName()+"手里的钱:"+nowMoney);
}
}
}
(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 < 1000; i++) {
new Thread(()->{ //Lamda表达式
synchronized(list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(2000);
System.out.println(list.size()); //输出集合的大小
}
}
Lamda表达式请查看关于多线程的静态代理和Lamda表达式
-
JUC并发编程
CopyOnWriteArrayList
//测试JUC安全类型的集合 import java.util.concurrent.CopyOnWriteArrayList; public class TestJUC { public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
2 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源。导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
package lin.deadlock;
//死锁:多个线程互相抱着对方需要的资源,然后僵持
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0,"girl1");
Makeup girl2 = new Makeup(1,"girl2");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
//需要一个镜子一个口红 static保证只有1
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice; //选择
String girlName; //使用的人
Makeup(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(1000);
//获得镜子的锁
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
}
3 lock锁
- 可重入锁
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
- synchronized与Lock区别
4 线程协作
- 生产者消费者模式 (它是一个问题)
(1)解决方式1:管程法
并发协作模型"生产者/消费者模式"–>管程法
-
生产者:负责生产数据的模块
-
消费者:负责处理数据的模块
-
缓冲区:消费者不能直接使用生产者的数据
生产者将生产好的数据放入缓存区,消费者从缓存区拿出数据
//测试生产者消费者模型 利用缓存区解决:管程法 //生产者 消费者 产品 缓存区 public class TestPC { public static void main(String[] args) { SynContainer container=new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //10.生产者 class Productor extends Thread{ //11.定义一个缓存区 SynContainer container; //12.定义初始化构造方法 public Productor(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生产第"+i+"个Hamburg"); try { container.push(new Hamburg(i)); } catch (InterruptedException e) { e.printStackTrace(); } } } } //10.消费者 class Consumer extends Thread{ //11.定义一个缓存区 SynContainer container; //12.定义初始化构造方法 public Consumer(SynContainer container) { this.container = container; } //13.消费 @Override public void run() { for (int i = 0; i < 100; i++) { try { System.out.println("消费了第"+container.pop().id+"个Hamburg"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Hamburg{ int id; //产品编号 public Hamburg(int id) { this.id = id; } } //1.缓存区 class SynContainer{ //2.需要容器大小 Hamburg[] hamburgs=new Hamburg[10]; //3.容器计数器 int count=0; //4.生产者放入产品 public synchronized void push(Hamburg hamburg) throws InterruptedException { //5.如果容器满了,就需要等待消费者消费 if (count==hamburgs.length-1){ //8.生产等待,消费者消费 this.wait(); } //6.如果没有满,就丢入产品 hamburgs[count]=hamburg; count++; //7.可以通知消费者消费 this.notifyAll(); } //4.消费者消费产品 public synchronized Hamburg pop() throws InterruptedException { //5.判断是否消费 if(count==0){ //8.等待生产者生产,消费者等待 this.wait(); } //6.如果不为0,则消费者消费 count--; Hamburg hamburg=hamburgs[count]; //7.通知生产者生产 this.notifyAll(); return hamburg; } }
(2)解决方式2:信号灯法
//测试生产者消费者问题: 信号灯法,标志位
//
public class TestPC2 {
public static void main(String[] args) {
Show show=new Show();
new Actor(show).start();
new Audience(show).start();
}
}
//生产者:演员
class Actor extends Thread{
Show show;
public Actor(Show show) {
this.show = show;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i%2==0){
this.show.play("奔跑吧兄弟");
}else {
this.show.play("极限挑战");
}
}
}
}
//消费者:观众
class Audience extends Thread{
Show show;
public Audience(Show show) {
this.show = show;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
show.watch();
}
}
}
//产品:节目
class Show{
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice; //1.表演的节目
boolean flag=true; //2.标志位
//3.演员表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("actor表演了"+voice);
//4.通知观众观看
this.notifyAll();
this.voice=voice;
this.flag=!this.flag;
}
//3.观众观看
public synchronized void watch(){
//4.判断是否有表演可看
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:"+voice);
//5.通知演员表演
this.notifyAll();
this.flag=!this.flag;
}
}
5 线程池
ExecutorService:线程池接口
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool参数为线程池大小
ExecutorService service= Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}