努力努力再努力
1 线程同步
多个线程操作同一个资源
并发:同一个对象被多个线程同时操作。
队列 和 锁:解决线程不安全问题
线程同步:
-
处理多线程问题,多个线程访问同一个对象,并且修改这个对象,就需要线程同步。线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
-
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排他锁,独享资源,其他线程必须等待,使用后释放锁即可,但同时存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题
- 优先级高的线程等待优先级低的线程释放锁,会导致优先级导致,引起性能问题
2 三大安全案例
-
买火车票
-
银行取钱
//银行取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(5000, "旅游基金");
Drawing me = new Drawing(account, 3000, "me");
Drawing baby = new Drawing(account, 3000, "baby");
me.start();
baby.start();
}
}
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
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<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
//sleep可以放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money=account.money-drawingMoney;//卡里的余额
nowMoney=nowMoney+drawingMoney;//手里的钱
System.out.println(account.name+"余额为:"+account.money);
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
- 线程不安全的集合
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args){
List<String> list= new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(() ->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());//输出不一定为1000
}
}
3 同步方法
-
synchronized方法 和 synchronized块
-
synchronized方法控制对 “对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
-
方法里面需要修改的内容才需要锁,锁太多浪费资源。
同步块:
- synchronized(Obj){}
- Obj称为同步监视器。可以是任何对象,但是推荐使用共享资源作为同步监视器,锁的对象最好是变化的量,需要增删改的对象;同步方法中无需指定同步监视器,此时的Obj就是this,对象本身,或者是class[反射中讲解]
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
4 CopyOnWriteArrayList
//测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list =new CopyOnWriteArrayList<String>();
for (int i = 0; i < 100000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(100);
System.out.println(list.size());
}
}
5 死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能发生“死锁”的问题。
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) throws InterruptedException {
Makeup g1 = new Makeup(true, "臭猪");
Makeup g2 = new Makeup(false, "傻琳");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
static Lipstick lipstick= new Lipstick();
static Mirror mirror = new Mirror();
boolean flag;
String name;
public Makeup(boolean flag, String name) {
this.flag = flag;
this.name = name;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException {
if(flag==true){
synchronized (lipstick){
System.out.println(this.name+"获得口红的锁");
Thread.sleep(1000);//一秒之后想获得镜子
synchronized (mirror){
System.out.println(this.name+"获得镜子的锁");
}
}
}else{
synchronized (mirror){
System.out.println(this.name+"获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){
System.out.println(this.name+"获得口红的锁");
}
}
}
}
}
调整成这样就ok了
public void makeup() throws InterruptedException {
if(flag==true){
synchronized (lipstick) {
System.out.println(this.name + "获得口红的锁");
Thread.sleep(1000);//一秒之后想获得镜子
}
synchronized (mirror){
System.out.println(this.name+"获得镜子的锁");
}
}else{
synchronized (mirror) {
System.out.println(this.name + "获得镜子的锁");
Thread.sleep(2000);
}
synchronized (lipstick){
System.out.println(this.name+"获得口红的锁");
}
}
}
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
只要破其中的任意一个或多个条件就可以避免死锁发生
6 Lock(锁)
可重入锁
- 从JDK5.0开始,Java提供了更强大的线程同步机制,通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。
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();
}
}
class TestLock2 implements Runnable{
int ticketNums=10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
lock.lock();//加锁
if (ticketNums<=0){
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ticketNums--);
}
} finally {
lock.unlock();//解锁,如果同步代码有异常,保证能释放锁
}
}
}
}
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方发锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序: Lock>同步代码块(已经进入了方法体,分配了相应的资源)>同步方法(在方法体之外)
7 线程协作
生产者消费者模式问题 :生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
- 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品,以供消费。
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(线程通信)
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出IllegalMonitorStateException
管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”。生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
//测试:生产者消费者模型-->利用缓冲区解决:管程法
//生产者、消费者、产品、缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
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 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.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens=new Chicken[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();
}
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
//消费完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
信号灯法
//测试:生产者消费者模型:-->信号灯法,标志位解决
public class TestPC2 {
public static void main(String[] args) {
Program program = new Program();
new Actor(program).start();
new Audience(program).start();
}
}
//生产者-->演员
class Actor extends Thread{
Program program;
public Actor(Program program) {
this.program = program;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i%2==0){
program.play("电视剧播放中");
}else{
program.play("电影播放中");
}
}
}
}
//消费者-->观众
class Audience extends Thread{
Program program;
public Audience(Program program) {
this.program = program;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
program.watch();
}
}
}
//产品-->节目
class Program{
//演员表演,观众等待 T
//观众观看,演员等待 F
String str;
boolean flag=true;
//表演
public synchronized void play(String str){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了"+str);
this.str=str;
this.flag=!this.flag;
//通知观众观看
this.notifyAll();
}
//观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了"+str);
//通知演员表演
this.flag=!this.flag;
this.notifyAll();
}
}
线程池
-
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
-
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
-
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理:corePoolSize:核心池的大小、maximumPoolSize:最大线程数、keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
JDK5.0起提供了线程池相关API:ExecutorService和Executors
-
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
//测试线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为:线程池大小
ExecutorService service= Executors.newFixedThreadPool(10);//多态
//设置线程池的属性,ThreadPoolExecutor是ExecutorService子类
//((ThreadPoolExecutor) service).setCorePoolSize(15);
//((ThreadPoolExecutor) service).setKeepAliveTime();
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new MyThreadPool1());//service.execute()适用于Runnable
service.submit(new MyThreadPool2());//service.submit() 适用于Callable
//Future future=service.submit(new MyThreadPool2()); 执行并获取返回值
//System.out.println(future.get());
//3.关闭线程池
service.shutdown();
}
}
class MyThreadPool1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
class MyThreadPool2 implements Callable{
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "hhh";
}
}
创建线程总结
public class ThreadNew {
public static void main(String[] args) {
new MyThread1().start();
new Thread(new MyThread2()).start();
//将Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask=new FutureTask(new MyThread3());
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());//返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.继承Thread类
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("MyThread1");
}
}
//2.实现Runnable接口
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("MyThread2");
}
}
//3.实现Callable接口,多种方法
class MyThread3 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("MyThread3");
return "hhh";
}
}