zl程序教程

您现在的位置是:首页 >  后端

当前栏目

JavaSE进阶:多线程(二)

2023-09-11 14:16:29 时间

目录

3.线程状态

1.线程方法

2.停止线程

3.线程休眠

1.模拟网络延时

2.倒计时

4.线程礼让(放弃、谦让、避让)

5.线程加入(线程强制执行)

6.线程状态观测

7.线程优先级

8.守护线程

4.线程同步

1.线程同步

2.线程安全

3.三大不安全案例

1.三人抢票

2.两人一起取钱

3.不安全的集合

4.同步方法

同步方法实现抢票

5.同步代码块

1.同步代码块实现模拟取款

2.同步块实现不安全的集合

6.死锁

7.Lock锁

1.Lock实现倒计时案例

2.Lock实现添加字符串

3.Lock实现抢票案例

4.与synchronized相比

5.读写锁


3.线程状态

六大状态

  • 创建状态
  • 就绪状态(运行)
  • 阻塞状态
  • 等待状态
  • 运行状态
  • 死亡状态

线程的6中状态
线程状态描述
NEW(新建)

线程刚被创建,但是并未启动。

Runnable(可运行)

线程已经调用了start()等待CPU调度

Blocked(锁阻塞)

线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态

Waiting(无限等待)

一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒

Timed Waiting(计时等待)

超时参数的常用方法有Thread.sleep Object.wait

Teminated(被终止)

因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

1.线程方法

线程方法
方法说明
setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程
void interrupt()中断线程,别用这个方式
boolean isAlive()测试线程是否处于活动状态

2.停止线程

  • 不推荐使用JDK提供的stop()、
    destroy()方法。[已废弃]
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量 
    当flag=false,则终止线程运行。

建议使用步骤

  1. 建议线程正常停止-->利用次数,不建议死循环
  2. 建议使用标识位-->设置一个标识位
  3. 不要使用stop或destroy等过时或JDK不建议使用的方法
package java_se.java_jinjie.duoxiancheng.demo03;
//测试stop
//1.建议线程正常停止-->利用次数,不建议死循环
//2.建议使用标识位-->设置一个标识位
//3.不要使用stop或destroy等过时或JDK不建议使用的方法
public class TestStop implements Runnable {
    //1.设置一个标识位
    private boolean flag=true;

    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("run...Thread"+i++);
        }
    }

    //2.设置一个公开的方法停止线程,转换标识位
    public  void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
            //主线程到90的时候停止子线程
            if (i==90){
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

3.线程休眠

  • sleep(毫秒数)指定当前线程停止的实践
  • sleep()存在异常InteruptedException
  • sleep()实践到达后线程进入就绪状态
  • sleep()可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
  • 可以放大问题的发生性

1.模拟网络延时

package java_se.java_jinjie.duoxiancheng.demo03;

//模拟网络延时:放大问题的发生性
public class TestSleep  implements Runnable{
    private  int ticketNums=10;

    @Override
    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) {
        TestSleep text = new TestSleep();
        new Thread(text,"小明").start();
        new Thread(text,"老师").start();
        new Thread(text,"黄牛").start();
    }
}

2.倒计时

package java_se.java_jinjie.duoxiancheng.demo03;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep2 {
    public static void main(String[] args) {
//        //倒计时
//        try {
//            tenDown();
//        }catch (InterruptedException e){
//            e.printStackTrace();
//        }
        //获取系统当前时间
        Date date = new Date(System.currentTimeMillis());
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date = new Date(System.currentTimeMillis());//更新当前时间
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }


    public static void tenDown()throws InterruptedException{
        int num=10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
}

4.线程礼让(放弃、谦让、避让)

礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功,看CPU心情

package java_se.java_jinjie.duoxiancheng.demo03;
//测试礼让线程
//礼让不一定成功,看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

5.线程加入(线程强制执行)

  • Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
  • 可以想象成插队
package java_se.java_jinjie.duoxiancheng.demo03;
//测试join方法:想象成插队
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程vip来了"+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 < 20; i++) {
            if (i==12){
                thread.join();//插队
            }
            System.out.println("main"+i);
        }
    }
}

6.线程状态观测

Thread.State
线程状态。线程可以处于以下状态之一

  • NEW
    未启动的线程处于此状态。
  • RUNNABLE
    在Java虛拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    正在等待另-个线程执行特定动作的线程处于此状态。
  • TIMED WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    已退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

package java_se.java_jinjie.duoxiancheng.demo03;

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(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("");
        });


        //观测状态
        Thread.State state = thread.getState();
        System.out.println(state);//new

        //观测启动后
        thread.start();//启动线程
        state=thread.getState();
        System.out.println(state);//run

        while (state!=Thread.State.TERMINATED){//TERMINATED线程终止|只要线程不终止,就一直输出状态
            Thread.sleep(10);
            state=thread.getState();//更新线程
            System.out.println(state);//输出状态
        }
    }
}

7.线程优先级

  • java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程;线程调度器按照优先级决定该调度哪个线程来执行
  • 线程的优先级用数字来表示,范围从1~10
    • Thread.MIN_PRIORITY = 1        最小优先级
    • Thread.MAX_PRIORITY= 10      最大优先级
    • Thread.NORM_PRIORITY = 5    默认优先级
  • 使用getPriority()和setPriority()来获取或改变优先级

建议:优先级的设定建议在start()调度前

优先级低只是意味着被CPU的调度概率低,并不是优先级低就不会被调用了,这都是看CPU的调度

package java_se.java_jinjie.duoxiancheng.demo03;

public class TestPriority {
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);

        //先设置优先级
        t1.start();
        t2.setPriority(1);
        t2.start();
        t3.setPriority(4);
        t3.start();
        t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
        t4.start();
    }
}

class MyPriority implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

    }
}

8.守护线程

  • 线程分为用户线程(前台线程)和守护线程(后台线程)
  • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如后台记录操作日志,监控内存,垃圾回收
  • 线程对象. setDaemon (true) ;设置为守护线程
package java_se.java_jinjie.duoxiancheng.demo03;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread thread = new Thread(god);
        thread.setDaemon(true);//默认是false表示用户线程,正常的线程都是用户线程
        thread.start();//上帝守护线程
        new Thread(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 < 36500; i++) {
            System.out.println("你一生都开心的或者");
        }
        System.out.println("==goodbye world==");
    }
}

4.线程同步

1.线程同步

  • 处理多线程问题时,多个线程访问同一个对象(并发),并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程在使用
  • 线程同步,实际上就是线程不能并发了,线程必须排队执行
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制syncronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一下问题
    • 一个线程持有锁会导致其他所有需要此锁的进程挂起
    • 在多线程竞争的情况下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

2.线程安全

已知JDK中线程安全的类:

  • StringBuffer
  • Vector
  • Hashtable
  • 以上类中的公开方法,均为synchoni zed修饰的同步方法。

3.三大不安全案例

1.三人抢票

package java_se.java_jinjie.duoxiancheng.demo04;
//不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"苦逼的我").start();
        new Thread(station,"牛逼的你们").start();
        new Thread(station,"可恶的黄牛").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;//外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private void buy()throws InterruptedException{
        //判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        }

        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);

    }
}

2.两人一起取钱

package java_se.java_jinjie.duoxiancheng.demo04;
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"你的妻子");

        you.start();
        girlFriend.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()  因为继承Thread所以两个getName相等
        System.out.println(this.getName()+"手里的钱为:"+nowMoney);
    }
}

3.不安全的集合

package java_se.java_jinjie.duoxiancheng.demo04;

import java.util.ArrayList;
//不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        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());
    }
}

4.同步方法

  • 同步方法: public synchronized void method(int args) {}
  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块.
  • synchronized方法控制对“对象”的访问, 每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
  • 缺陷:若将一个大的方法申明为synchronized将会影响效率

底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

同步方法实现抢票

package java_se.java_jinjie.duoxiancheng.demo04.demo2;
//不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"苦逼的我").start();
        new Thread(station,"牛逼的你们").start();
        new Thread(station,"可恶的黄牛").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;//外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //synchronized 同步方法,锁的是this
    private synchronized void buy()throws InterruptedException{
        //判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        }

        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);

    }
}

5.同步代码块

  • 同步块: synchronized(obj){}
  • 任意对象/同步锁对象 : 在同步中这个对象称为对象锁,简称锁,官方的稳定称为 对象监视器

  • 锁对象只要对于当前同时执行的线程来说是同一个对象即可。即这个数据必须是多线程共享的数据。

  • obj称之为同步监视器(同步锁对象)
    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个对象本身,或者是class,不能使用new的对象,因为这样会每一个对象都拿到一个锁,都用自己的锁进入了线程,那锁将变得没有意义(就相当于没有锁)
    • 线程退出同步代码块,会释放对应的锁
  • 同步监视器的执行过程
    • 第一个线程访问,锁定同步监视器,执行其中的代码
    • 第二个线程访问,发现同步监视器被锁定,无法访问
    • 第一个线程访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
  • 同步方法在方法上添加synchronized关键子,锁的是对象本身

作用:把出现线程安全问题的核心代码给上锁

原理每次只能一个线程进入执行完毕后自动解锁,其他线程才可以进来执行

1.同步代码块实现模拟取款

package java_se.java_jinjie.duoxiancheng.demo04.demo2;
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"你的妻子");

        you.start();
        girlFriend.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;
    }
    //synchronized 默认锁的是this.
    @Override
    public void run() {
        //锁的对象就是变化的量,需要增删改的对象
        synchronized (account){
            //判断有没有钱
            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()  因为继承Thread所以两个getName相等
            System.out.println(this.getName()+"手里的钱为:"+nowMoney);
        }

    }
}

2.同步块实现不安全的集合

package java_se.java_jinjie.duoxiancheng.demo04.demo2;

import java.util.ArrayList;
//不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

6.死锁

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源释放才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有"两个以上的对象锁"时,就可能发生死锁现象

死锁产生的条件

  • 互斥条件: 一个资源每次只能被一个进程使用
  • 请求保持条件: 一个进程因请求资源而阻塞时.对以获得的资源保持不放
  • 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
package java_se.java_jinjie.duoxiancheng.demo04;

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "灰姑凉");
        Makeup g2 = new Makeup(1, "白学公主");

        g1.start();
        g2.start();
    }
}

//口红
class Lipstick {

}

//镜子
class Mirror {

}

class Makeup extends Thread {
    //需要的资源只有一份,用static来保证只有一份
    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.getName() + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror) {
                System.out.println(this.getName() + "一秒钟后想获得镜子");
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.getName() + "获得镜子的锁");
                Thread.sleep(1000);
            }
            synchronized (lipstick) {
                System.out.println(this.getName() + "一秒钟后想获得口红");
            }
        }
    }
}

7.Lock锁

  • 从JDK1.5开始,java提供了更为强大的线程同步机制——通过显示定义同步锁对象来实现同步,同步锁使用lock对象来充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • 重入锁:ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中.比较常用的是ReentrantLock,可以显示加锁,释放锁
Lock锁常用方法
方法说明
void lock()

获取锁,如锁被占用,则等待

voolean tryLock尝试获取锁(陈宫返回true,失败返回false),不堵塞
void unlock()释放锁

1.Lock实现倒计时案例

package java_se.java_jinjie.duoxiancheng.demo04;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
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){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else {
                    break;
                }
            } finally {
                //解锁
                lock.unlock();
            }
        }
    }
}

2.Lock实现添加字符串

package java_se.java_jinjie.duoxiancheng.demo04.demo2;

import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyList {
    private Lock lock=new ReentrantLock();
    private String[] str={"a","b","","",""};
    private int count=2;

    public void add(String value){
        try {
            lock.lock();
            str[count]= value;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println(Thread.currentThread().getName()+"添加了"+value);
        }finally {
            lock.unlock();
        }
    }
    public String[] getStr(){
        return str;
    }
}

class TestMyList{
    public static void main(String[] args) throws Exception{
        MyList list = new MyList();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                list.add("hello");
            }
        };
        Runnable runnable2 = new Runnable(){
            @Override
            public void run() {
                list.add("world");
            }
        };
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(list.getStr()));
    }
}

3.Lock实现抢票案例

package java_se.java_jinjie.duoxiancheng.demo04.demo2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    private int ticket=100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticket<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                ticket--;
            }finally {
                lock.unlock();
            }
        }
    }
}
class TestTicket{
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        ExecutorService es = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 4; i++) {
            es.submit(ticket);
        }
        es.shutdown();
    }
}

4.与synchronized相比

  • Lock是显示锁(手动开启和关闭,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序
  • Lock功能更强大、性能更优越

Lock > 同步代码块 > 同步方法

5.读写锁

  • ReentrantReadWriteLock:读写锁
    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
    • 支持多次分配读锁,使多个读操作可以并发执行。
    • ReentrantReadWriteLock.ReadLock:读锁
    • ReentrantReadWriteLock.WriteLock:写锁
    • ReentrantLock:互斥锁
  • 互斥规则:
    • 写-写。互斥,阻塞。
    • 读-写:互斥,读阻塞写、写阻塞读。
    • 读-读:不互斥、不阻塞。
    • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

package java_se.java_jinjie.duoxiancheng.demo04.demo2;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteDemo {
    //创建读写锁
    private ReentrantReadWriteLock rrl=new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock=rrl.readLock();//获取读锁
    private ReentrantReadWriteLock.WriteLock writeLock=rrl.writeLock();//获取写锁
    //互斥锁
    private ReentrantLock lock =new ReentrantLock();
    private String value;
    //读取
    public String getValue(){
        //使用读锁上锁
        try {
            lock.lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取:"+this.value);
            return this.value;
        }finally {
            lock.unlock();
        }
    }
    //写入
    public void setValue(String value){
        try {
            lock.lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("写入:"+value);
            this.value=value;
        }finally {
            lock.unlock();
        }
    }
}

class TestReadWriteDemo{
    public static void main(String[] args) {
        ReadWriteDemo rwd = new ReadWriteDemo();
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(10);
        //分配18读取任务

        Runnable read = new Runnable(){
            @Override
            public void run() {
                rwd.getValue();
            }
        };
        Runnable write = new Runnable(){
            @Override
            public void run() {
                rwd.setValue("张三"+new Random().nextInt(100));
            }
        };
        long start=System.currentTimeMillis();
        //分配2写入任务
        for (int i = 0; i < 2; i++) {
            es.submit(write);
        }
        //分配18读取任务
        for (int i = 0; i < 8; i++) {
            es.submit(read);
        }
        es.shutdown();//关闭线程池
        while (es.isTerminated()){//空转

        }
        long end=System.currentTimeMillis();
        System.out.println("用时"+(end-start));

    }
}