java多线程

155 阅读11分钟

自己学习记录,如有错误请提出进行修改,谢谢!

一线程创建方式

1、Thread方式

@Test
public void test1(){
    Thread thread = new Thread("t1"){
        @Override
        public void run() {
            System.out.println("线程运行逻辑方法区域");
            System.out.println("线程名称:"+Thread.currentThread().getName());
        }
    };
    //启动线程
    thread.start();

}
控制台输出结果:
线程运行逻辑方法区域
线程名称:t1

1.1、start()方法与run()方法的区别

start()方法是启动线程的方法,线程执行start()方法后,线程开始启动,然后执行run()方法。run()方法是线程的执行主体,线程中的所有逻辑都在run()方法中执行。

2、Runable方式

@Test
public void test2(){
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("线程执行逻辑主体");
            System.out.println("线程名称:"+Thread.currentThread().getName());
        }
    };

    Thread t2 = new Thread(runnable, "t2");
    //启动线程
    t2.start();
}
控制台输出结果:
线程执行逻辑主体
线程名称:t2

2.1、Thread与Runable的关系

根据源码可知,Thread实现了Runable,Thread和Runable方式创建线程执行的方法是相同的。那么这两种创建线程的方式有什么区别呢?个人感觉是Runable方式将线程执行的逻辑与线程的创建分开。意思是说线程的执行逻辑代码和线程的创建可以不在同一个类中。

3、Callable方式,该方式可以返回线程的运行结果

@Test
public void test3(){
    Callable<String> callable = new Callable<String>() {
        @Override
        public String call() throws Exception {
            //线程返回值
            System.out.println(Thread.currentThread().getName()+"线程执行逻辑");
            return "线程运行结果";
        }
    };
    FutureTask futureTask = new FutureTask(callable);
    //创建线程
    Thread thread = new Thread(futureTask,"t3");
    thread.start();
    try {
        System.out.println("获取线程运行结果:"+futureTask.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
控制台输出结果:
t3线程执行逻辑
获取线程运行结果:线程运行结果

二线程池创建线程

Executors工程提供的四种线程池

1、newCachedThreadPool线程池

newCachedThreadPool线程池:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收则新建线程(线程最大并发数并不可控制)。 适用场景:适用于并发执行大量短期的小任务,或者是负载较轻的服务器。

构造方法源码 :参数 核心线程:0,最大线程数:Integer.MAX_VALUE=2147483647,
                    救急线程存活时间60,单位秒,(核心线程为0,意思是所有的线程都是救急线程了,线程空闲60秒后就回收了)
                    SynchronousQueue队列没有容量一手交钱,一手交货。一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
@Test
public void test4(){
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<String> result = executorService.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("线程执行主体");
            return "线程返回结果";
        }
    });
    try {
        System.out.println("获取线程结果="+result.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
控制台输出结果:
线程执行主体
获取线程结果=线程返回结果

2、newFixedThreadPool线程池

newFixedThreadPool线程池:创建一个固定大小的线程池,可控制线程最大并发数,并发量超出核心线程数后,超出的线程将被存放到队列中等待。 适用场景:用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

源码分析:nThreads参数将作为核心线程数,最大线程数,没有救急线程,
           队列为无界队列
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
@Test
public void test5(){
    //测试核心线程数设置为1
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("线程执行主体execute方法无返回值");
        }
    });
}

3、newScheduledThreadPool线程池

newScheduledThreadPool线程池:创建一个定时线程池,支持定时及周期性任务。 适用场景:适用于多个后台线程执行周期任务,同时需要限制线程数量的场景。

源码:核心线程数:corePoolSize,最大线程数为Integer.MAX_VALUE=2147483647,
        无救急线程,队列DelayedWorkQueue优先级队列
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

测试中发现,使用的是测试@Test主线程不能停止,否则定时线程没打印数据,只用main方法测试则不会出现这个问题

 @Test
    public void test6(){
        System.out.println(new Date() +Thread.currentThread().getName()+"创建线程");
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date()+Thread.currentThread().getName()+"线程执行主体");
            }
        }, 3, TimeUnit.SECONDS);参数: Runnable,延迟时间,延迟时间单位
        try {
            //使主线程保持活着
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 //下面方式也是可以的       
//        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
//        ScheduledFuture<String> scheduledFuture = executorService.schedule(new Callable<String>() {
//            @Override
//            public String call() throws Exception {
//                return new Date()+"call";
//            }
//        }, 3, TimeUnit.SECONDS);
//        try {
//            System.out.println(scheduledFuture.get());
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }

    }
控制台输出结果:注意时间相隔了3Tue Mar 22 14:19:39 CST 2022main创建线程
Tue Mar 22 14:19:42 CST 2022pool-1-thread-1线程执行主体

scheduleAtFixedRate方法,以固定速率执行

System.out.println(new Date() +Thread.currentThread().getName()+"创建线程");
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @SneakyThrows
    @Override
    public void run() {
        Thread.sleep(4000);//程序执行了4秒
        System.out.println(new Date()+Thread.currentThread().getName()+"线程执行主体");
    }
}, 2,3, TimeUnit.SECONDS);参数:延迟2秒执行,每3秒执行一次,线程运行时间大于3秒的话,就会以线成运行时间进行循环
控制台输出结果:
Tue Mar 22 14:39:31 CST 2022main创建线程
Tue Mar 22 14:39:37 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:41 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:45 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:49 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:53 CST 2022pool-1-thread-1线程执行主体

scheduleWithFixedDelay方法,以固定延迟时间执行

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    @SneakyThrows
    @Override
    public void run() {
        Thread.sleep(4000);
        System.out.println(new Date()+Thread.currentThread().getName()+"线程执行主体");
    }
}, 2,3, TimeUnit.SECONDS);参数:初始延迟两秒执行,以后以3秒延迟执行
控制台打印结果:
Tue Mar 22 14:36:04 CST 2022main创建线程
Tue Mar 22 14:36:10 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:36:17 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:36:24 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:36:31 CST 2022pool-1-thread-1线程执行主体

4、newSingleThreadExecutor线程池

newSingleThreadExecutor线程池:创建一个单线程的线程池,它会用唯一的工作线程来执行任务。保证所有任务按照指定顺序执行。 适用场景:用于串行执行任务的场景,每个任务按顺序执行,不需要并发执行。

@Test
public void test7(){
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("线程执行主体");
        }
    });
}
控制台打印结果:
线程执行主体

三线程的控制方法

1、sleep方法

public static void main(String[] args) {
    Thread t1 = new Thread("t1") {
        @SneakyThrows
        @Override
        public void run() {
            //睡2秒
            sleep(2000);
            System.out.println(new Date()+this.getName()+"睡醒两秒执行");
        }
    };
    //启动t1线程
    t1.start();
    System.out.println(new Date()+Thread.currentThread().getName()+"主线程执行");
}
控制台打印结果:
Wed Mar 23 10:53:10 CST 2022main主线程执行
Wed Mar 23 10:53:12 CST 2022t1睡醒两秒执行

方法解释:

  1. 在线程内使用sleep方法,会使当前线程进入阻塞状态。
  2. 睡眠时间过后,如果cpu紧张,当前线程未必会得到时间片执行代码
  3. 睡眠期间,线程不会释放锁。

2、yield方法

当前线程调用yield方法,会使当前线程从运行状态变成就绪状态,让出cpu的使用,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。

3、join方法

static int a=2;
public static void main(String[] args) throws InterruptedException {
    Thread t1= new Thread() {
        @SneakyThrows
        @Override
        public void run() {
            //防止先执行了t1线程
            sleep(2000);
            a=5;
        }
    };
    t1.start();
    System.out.println(new Date()+"线程ti还没来得及改a的值:"+a);
    //等待ti执行结束
    t1.join();
    System.out.println(new Date()+"线程tiy已经修改a的值:"+a);
}
控制台打印结果:
Wed Mar 23 11:35:12 CST 2022线程ti还没来得及改a的值:2
Wed Mar 23 11:35:14 CST 2022线程tiy已经修改a的值:5

方法解释:

  1. t1线程调用join方法,表示等待t1线程执行结束再执行主线程。
  2. join方法可以设置等待时间,等待超时将不再等待。

4、interrupt方法

打断正常运行的线程

static int b=1;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread() {
        @SneakyThrows
        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                System.out.println(new Date()+this.getName()+"ti线程正常执行"+b);
            }
        }
    };
    t1.start();
    t1.interrupt();
    System.out.println(new Date()+"t1打断状态"+t1.isInterrupted()+b);
}
控制台打印结果:
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行2
Wed Mar 23 15:05:52 CST 2022t1打断状态true2
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行3
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行4
....省略
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行10000
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行10001

通过控制台打印信息发现,interrupt方法打断正常运行的线程,只是给该线程一个打断标记,但是该线程还是会继续执行下去。

打断睡眠的线程

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread() {
        @SneakyThrows
        @Override
        public void run() {
            sleep(3000);
            System.out.println(new Date()+this.getName()+"睡醒3秒执行");
        }
    };
    t1.start();
    Thread.sleep(1000);
    t1.interrupt();
    System.out.println(new Date()+"t1打断状态"+t1.isInterrupted());
}
控制台打印信息:
Exception in thread "Thread-0" java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.thread.test.CreatThread$8.run(CreatThread.java:205)
Wed Mar 23 14:56:11 CST 2022t1打断状态false

打断睡眠中的线程,打断标记会被清空

打断join的线程

static int b=1;
static int c=1;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread() {
        @SneakyThrows
        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                b++;
                System.out.println(new Date()+this.getName()+"ti线程正常执行"+b);
            }
        }
    };
    Thread t2 = new Thread() {
        @SneakyThrows
        @Override
        public void run() {
            t1.join();
            for(int i=0;i<10000;i++){
                c++;
                System.out.println(new Date()+this.getName()+"ti线程正常执行"+c);
            }
        }
    };
    t1.start();
    t2.start();
    //t2此刻正在join阻塞中
    t2.interrupt();
    System.out.println(new Date()+"t1打断状态"+t1.isInterrupted()+b);
    System.out.println(new Date()+"t2打断状态"+t2.isInterrupted()+c);
}
控制台打印信息:
Exception in thread "Thread-1" java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1252)
	at java.lang.Thread.join(Thread.java:1326)
	at com.example.thread.test.CreatThread$9.run(CreatThread.java:217)
Wed Mar 23 15:14:14 CST 2022t1打断状态false2
Wed Mar 23 15:14:14 CST 2022t2打断状态false1
Wed Mar 23 15:14:14 CST 2022Thread-0ti线程正常执行2
Wed Mar 23 15:14:14 CST 2022Thread-0ti线程正常执行3

在t2中执行了t1.join方法,t2线程会等待t1执行完再执行,t2正在阻塞中,主线程在这期间,打断t2,会出现报错,t2的打断标记被清空。

方法总结:

  1. 打断正常运行的线程,会给线程加上打断标记,但是线程会继续执行下去
  2. 打断sleep,wait,join线程,会出现报错,并清空打断标记。

5、wait方法、notify方法、notifyAll方法

wait方法和notify方法不是线程Thread的方法,而是Object的方法。

  1. 必须配合锁使用,只有获得锁后才能是有这两个方法。
  2. wait方法的作用是使获得锁的线程进入WaitSet(休息室)变成等待状态,也即是线程阻塞,此时是释放了锁,不占用cpu资源。sleep方法不释放锁。
  3. notify方法是从WaitSet(休息室)中随机唤醒一个线程,竞争锁,竞争cpu。
  4. notifyAll方法是从WaitSet(休息室)中唤醒所有线程,竞争锁,竞争cpu。
public class ThreadLock {
    volatile static boolean flag=false;
    static Object obj=new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (obj){
                    //竞争到锁
                    while (!flag){
                        //不满足条件,进入休息室
                        System.out.println("条件不允许,不想干活,休息会");
                        obj.wait();
                    }
                    //条件满足,执行程序
                    System.out.println("条件允许了,该干活了");
                }

            }
        }.start();
        new Thread(){
            @SneakyThrows
            @Override
            public void run() {
                sleep(500);
                synchronized (obj){
                    flag=true;
                    System.out.println("修改flag:"+flag);
                    System.out.println("唤醒线程:");
                    obj.notifyAll();
                }
            }
        }.start();
    }
}
控制台打印信息:
条件不允许,不想干活,休息会
修改flag:true
唤醒线程:
条件允许了,该干活了

四 线程锁synchronized

1、synchronized个人理解:

  1. synchronized是对象锁,可重入锁,同步锁,互斥的,一个线程A持有对象锁,另一个线程B需等待A线程将锁释放才能获取该对象锁。
  2. 根据第一点,可以发现并发量打大的话,会有很多阻塞线程,影响执行效率。
  3. 锁住的区域称作临界区,保证被锁住的对象的原子性,临界区的代码是一个整体,不会因为线程切换打断执行。

2、synchronized使用位置

  1. 作用在普通方法上,锁住的是类的实例对象。
  2. 作用在静态方法上,锁住的是类。
  3. 作用在代码块synchronized(this)锁住的是实例对象。
  4. 作用在代码块synchronized(User.class)锁住的是类。
  5. 作用在实例对象上synchronized(Object)锁住的是该对象。 网上有线程八锁,加深理解。

3、synchronized应用例子

1、作用在普通方法上,验证互斥性

public class ThreadLock {

    String name="张三";
    public synchronized void saveA(String t,String newName)  {
        //对name进行修改

        try {
            name=newName;
            Thread.sleep(1000);
            System.out.println(new Date()+"="+t+"=="+name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    public synchronized void saveB(String t,String newName)  {
        //对name进行修改

        try {
            name=newName;
            Thread.sleep(1000);
            System.out.println(new Date()+"="+t+"=="+name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    public static void main(String[] args) {
        ThreadLock t = new ThreadLock();
        for (int i=0;i<10;i++){
            int finalI = i;
            new Thread("t1"){
                @Override
                public void run() {
                    t.saveA(finalI +"","张三");
                }
            }.start();

            new Thread("t2"){
                @Override
                public void run() {
                    t.saveB(finalI +"","李四");
                }
            }.start();
        }

    }
}
控制台打印信息:
Thu Mar 24 10:45:00 CST 2022=0==张三
Thu Mar 24 10:45:01 CST 2022=9==李四
Thu Mar 24 10:45:02 CST 2022=9==张三
Thu Mar 24 10:45:03 CST 2022=6==张三
Thu Mar 24 10:45:04 CST 2022=7==李四
Thu Mar 24 10:45:05 CST 2022=8==张三
Thu Mar 24 10:45:06 CST 2022=7==张三
Thu Mar 24 10:45:08 CST 2022=6==李四
Thu Mar 24 10:45:09 CST 2022=8==李四
Thu Mar 24 10:45:10 CST 2022=5==李四
Thu Mar 24 10:45:11 CST 2022=5==张三
Thu Mar 24 10:45:12 CST 2022=3==李四
Thu Mar 24 10:45:13 CST 2022=4==张三
Thu Mar 24 10:45:14 CST 2022=4==李四
Thu Mar 24 10:45:15 CST 2022=3==张三
Thu Mar 24 10:45:16 CST 2022=2==李四
Thu Mar 24 10:45:17 CST 2022=2==张三
Thu Mar 24 10:45:18 CST 2022=1==李四
Thu Mar 24 10:45:19 CST 2022=1==张三
Thu Mar 24 10:45:20 CST 2022=0==李四

代码及结果现象解释:

  1. saveA,saveB方法执行只需1秒。
  2. main方法中创建了1个对象,20个线程,10个调用saveA方法,10个调用saveB方法,20个线程争抢1个对象锁。
  3. 20个线程,按照以前的思维,觉得应该1秒就能执行完,但是根据打印结果显示是20秒执行完。
  4. 根据打印信息发现,20个线程是串行执行,印正了synchronized的互斥性,一个线程获得锁,其他线程必须等待线程释放了锁。
  5. 根据打印信息的顺序发现,线程执行顺序是随机的,谁先抢到锁谁先执行。

2、作用在普通方法上,验证原子性

public class ThreadLock {
    int a=0;
    public  void incr(){
        for (int i = 0; i <1000 ; i++) {
            a++;
        }
    }
    public  void decr(){
        for (int i = 0; i <1000 ; i++) {
            a--;
        }
    }
    public int getA(){
        return a;
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadLock t = new ThreadLock();
        Thread t1= new Thread() {
            @Override
            public void run() {
                t.incr();
            }
        };
        Thread t2=  new Thread() {
            @Override
            public void run() {
                t.decr();
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("最终结果a="+t.getA());

    }
}
控制台打印结果:
最终结果a=393

代码及结果解释:

  1. main方法创建2个线程,分别调用incr()和decr()方法,这个个方法对a进行加减操作。
  2. a++和a--不是原子操作。
  3. 按照以前的思维觉得结果a=0,但是a却是其他值。说明代码执行顺序出现了混乱。 顺序混乱解释:
  4. 以a++为例,a++可以拆分成a=a+1,假如此时a=5在线程执行了a+1=6的操作后还没来得及将a=5改a=6,这时该线程的时间片到了,进行了线程切换.
  5. 另一个线程调用a--方法,此时a还是5,a--执行完后,a=4。然后时间片用完进行切换。
  6. 前一个线程将6赋值给a,继续执行a++。
  7. 以次上述现象,a最终的值就会无法确定。 问题解决:在incr()和decr()方法上加上synchronized字段即可,以此验证了synchronized保证了临界区域的原子性。

3、作用在静态方法上

public class ThreadLock {
    public static synchronized void a() {
        sleep(1);
        System.out.println("1");

    }
    public synchronized void b() {
        System.out.println("2");
    }
   public static void main(String[] args) {
    ThreadLock t = new ThreadLock();
    new Thread(()->{ t.a(); }).start();
    new Thread(()->{ t.b(); }).start();
  } 
}
控制台打印结果:
2 1秒后打印1

结果分析:synchronized在静态方法a和普通方法b上,a和b锁住的对象不同,因此两个线程不存在锁竞争,u因此两个线程同时执行,a方法要睡1秒,因此输出的要慢一秒。因此要注意要竞争的锁对象是否是同一个。 4、作用在对象实例上

String a="张三";
public void a(){
    synchronized (a){
     
    }
}

五 volatile字段

volatile字段字段作用是保证可见性和禁止指令重排序

1、保证可见性

static boolean flag=true;
public static void main(String[] args) throws InterruptedException {
    Thread t= new Thread() {
        @Override
        public void run() {
           while (flag){

           }
        }
    };
    t.start();
    Thread.sleep(1000);
    flag=false;
}

根据运行结果发现线程t一直在运行没有停止。按照我们的想法是,主线程修改了flag的值,t线程应该会根据flag的值停止循环。但是实际情况并不是这样。

原理如下

jvm会对t线程的run方法代码进行重排,t线程第一次获取flag的值后,会将该值存到t线程的内存中,t线程每次是从自己线程内存中获取值。主线程修改flag值,该值并不会同步修改到t内存中的值,因此主线程修改的值对t线程来说并不保证可见性,也许可见,也许不可见。因此会出现程序无法停止的现象。为了解决这个问题,需要在flag字段添加volatile字段。由此可以验证可见性

2、禁止重排序

六 ReentrantLock可重入锁

1、加锁,释放锁

public class ThreadLock {
    static ReentrantLock lock= new ReentrantLock();
    public static  void a(){
        //加锁
        lock.lock();
        System.out.println("执行加锁操作");
        //执行逻辑代码
        try {
            System.out.println("执行逻辑代码");
            //故意制作错误,看看锁是不是释放了
            int b=1/0;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁,一定要有finally进行释放锁,方式程序运行出错锁无法释放
            lock.unlock();
            System.out.println("释放锁");
        }

    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(){
            @Override
            public void run() {
                a();
            }
        }.start();
    }
}
控制台打印信息:
执行加锁操作
执行逻辑代码
释放锁
java.lang.ArithmeticException: / by zero
	at com.example.thread.test.ThreadLock.a(ThreadLock.java:23)
	at com.example.thread.test.ThreadLock$1.run(ThreadLock.java:37)

2、可打断

static ReentrantLock lock= new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
    Thread t= new Thread(){
        @Override
        public void run() {
            try {
                if(lock.tryLock()){
                    System.out.println("尝试获得锁,并获得了锁");
                }else {
                    System.out.println("尝试获得锁,没有获得锁");
                }
                //等待锁时可打断
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("等待锁时被打断");
                return;
            }
            try {
                System.out.println("获得锁");
            } finally {
                lock.unlock();
            }
        }
    };
    //主线程获得锁
    lock.lock();
    System.out.println("主线程获得锁");
    t.start();
    try {
        Thread.sleep(1000);
        t.interrupt();
        System.out.println("打断t线程");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        lock.unlock();
    }

}
控制台打印结果:
主线程获得锁
尝试获得锁,没有获得锁
打断t线程
等待锁时被打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.example.thread.test.ThreadLock$1.run(ThreadLock.java:42)

if(lock.tryLock())改成while的话,会一直进行循环尝试

public static void main(String[] args) throws InterruptedException {
   Thread t= new Thread(){
       @Override
       public void run() {
           try {
               while(!lock.tryLock()){
                  //睡会,降低cpu占用率
                   sleep(1000);
                   System.out.println(new Date()+"尝试获得锁,没有获得锁");
               }
               lock.lockInterruptibly();
           } catch (InterruptedException e) {
               e.printStackTrace();
               System.out.println("等待锁时被打断");
               return;
           }
           try {
               System.out.println("获得锁");
           } finally {
               lock.unlock();
           }
       }
   };
   //主线程获得锁
   lock.lock();
   System.out.println("主线程获得锁");
   t.start();
   try {
       Thread.sleep(3000);
   } catch (InterruptedException e) {
       e.printStackTrace();
   }finally {
       lock.unlock();
   }

}
控制台打印信息:
主线程获得锁
Fri Mar 25 13:39:31 CST 2022尝试获得锁,没有获得锁
Fri Mar 25 13:39:32 CST 2022尝试获得锁,没有获得锁
Fri Mar 25 13:39:33 CST 2022尝试获得锁,没有获得锁
获得锁

3、条件变量

可以根据情况,唤醒某一个条件变量的线程

public class ThreadLock {
    static ReentrantLock lock= new ReentrantLock();
    static Condition aroom= lock.newCondition();
    static Condition broom= lock.newCondition();
    static  volatile boolean a=false;
    static  volatile boolean b=false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(){
            @SneakyThrows
            @Override
            public void run() {
                try {
                    System.out.println(new Date()+"a线程执行");
                    lock.lock();
                    while (!a){
                        System.out.println(new Date()+"进入aroom休息");
                        aroom.await();//会释放锁
                    }
                    System.out.println(new Date()+"条件a满足,可以干活了");
                } finally {
                    System.out.println(new Date()+"a释放锁");
                    lock.unlock();
                }
            }
        }.start();
        new Thread(){
            @SneakyThrows
            @Override
            public void run() {
                try {
                    System.out.println(new Date()+"b线程执行");
                    lock.lock();
                    while (!b){
                        System.out.println(new Date()+"进入aroom休息");
                        broom.await();//会释放锁
                    }
                    System.out.println(new Date()+"条件b满足,可以干活了");
                } finally {
                    System.out.println(new Date()+"b释放锁");
                    lock.unlock();
                }
            }
        }.start();

        try {
            Thread.sleep(1000);
            lock.lock();
            a=true;
            System.out.println(new Date()+"a状态改变:"+a);
            aroom.signal();
            Thread.sleep(1000);
            b=true;
            System.out.println(new Date()+"b状态改变:"+b);
            broom.signal();
        } finally {
            lock.unlock();
        }
    }
}
控制台打印信息:
Fri Mar 25 14:12:43 CST 2022a线程执行
Fri Mar 25 14:12:43 CST 2022b线程执行
Fri Mar 25 14:12:43 CST 2022进入aroom休息
Fri Mar 25 14:12:43 CST 2022进入aroom休息
Fri Mar 25 14:12:44 CST 2022a状态改变:true
Fri Mar 25 14:12:45 CST 2022b状态改变:true
Fri Mar 25 14:12:45 CST 2022条件a满足,可以干活了
Fri Mar 25 14:12:45 CST 2022a释放锁
Fri Mar 25 14:12:45 CST 2022条件b满足,可以干活了
Fri Mar 25 14:12:45 CST 2022b释放锁