普通视图

Received before yesterday

Java线程的状态

2024年11月3日 00:00

JDK 1.5 前线程状态

线程状态中文名称描述
New新建刚创建的线程,还未启动。
Runnable可运行线程可以运行,可能在等待 CPU 调度。
Blocked阻塞线程被阻塞,正在等待锁的释放。
Dead终止线程执行完成或异常终止,已进入结束状态。

jdk1.5之前

JDK 1.5 后线程状态

java.lang.Thread.State

public enum State {    /**     * Thread state for a thread which has not yet started.     */    NEW,    /**     * Thread state for a runnable thread.  A thread in the runnable     * state is executing in the Java virtual machine but it may     * be waiting for other resources from the operating system     * such as processor.     */    RUNNABLE,    /**     * Thread state for a thread blocked waiting for a monitor lock.     * A thread in the blocked state is waiting for a monitor lock     * to enter a synchronized block/method or     * reenter a synchronized block/method after calling     * {@link Object#wait() Object.wait}.     */    BLOCKED,    /**     * Thread state for a waiting thread.     * A thread is in the waiting state due to calling one of the     * following methods:     * <ul>     *   <li>{@link Object#wait() Object.wait} with no timeout</li>     *   <li>{@link #join() Thread.join} with no timeout</li>     *   <li>{@link LockSupport#park() LockSupport.park}</li>     * </ul>     *     * <p>A thread in the waiting state is waiting for another thread to     * perform a particular action.     *     * For example, a thread that has called <tt>Object.wait()</tt>     * on an object is waiting for another thread to call     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on     * that object. A thread that has called <tt>Thread.join()</tt>     * is waiting for a specified thread to terminate.     */    WAITING,    /**     * Thread state for a waiting thread with a specified waiting time.     * A thread is in the timed waiting state due to calling one of     * the following methods with a specified positive waiting time:     * <ul>     *   <li>{@link #sleep Thread.sleep}</li>     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>     *   <li>{@link #join(long) Thread.join} with timeout</li>     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>     * </ul>     */    TIMED_WAITING,    /**     * Thread state for a terminated thread.     * The thread has completed execution.     */    TERMINATED;}
线程状态中文名称描述
New新建刚创建的线程,还未启动。
Runnable可运行线程可以运行,可能在等待 CPU 调度。
Blocked阻塞线程尝试获取锁失败,被阻塞,等待锁释放。
Waiting等待线程进入等待状态,等待其他线程显式唤醒,通常由 Object.wait() 引起。
Timed Waiting计时等待线程等待指定时间后自动唤醒,由 Thread.sleep()wait(time) 引起。
Terminated终止线程执行完成或异常终止,已进入结束状态。

jdk1.5之后

volatile作用分析

2022年5月1日 09:14

Java 内存模型 (JMM) 中的一个核心问题是线程对共享变量的可见性。在多线程环境中,每个线程都有自己的工作内存(即 CPU 缓存)。当一个线程修改了某个变量,其他线程并不能立即看到这个修改,因为每个线程可能都在使用自己工作内存中的缓存值。

volatile 的可见性机制:

  • 强制刷新主内存:当一个线程对 volatile 修饰的变量进行写操作时,修改后的值会立即被刷新到主内存中,而不是缓存在该线程的工作内存中。

  • 强制读取主内存:当一个线程对 volatile 修饰的变量进行读操作时,会直接从主内存中读取最新值,而不会从线程的工作内存中读取缓存的值。

  • 示例代码

public class TestVolatile {        static volatile boolean flag = false;        public static void main(String[] args) {                new Thread(() -> {            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            flag = true;                        System.out.println("子线程修改flag的值为:  " + flag);                    }).start();                        // while(true) 调用底层代码,效率极高,不会从主存中再次获取被其他线程修改过的数据        while (true) {            if (flag) {                System.out.println("flag is true 主线程结束循环!");                break;            }        }    }}

因此,volatile 保证了变量的可见性,即当一个线程修改了 volatile 变量后,其他线程能够立即看到最新的值。

volatile 与 synchronized 的对比

特性volatilesynchronized
可见性保证可见性保证可见性
原子性不保证原子性保证原子性
重排序禁止指令重排序保证顺序执行
性能开销较低(无锁机制)较高(加锁/解锁开销)
使用场景适用于简单状态标志位或单次读写适用于复杂的临界区保护

Java的线程和常见方法

2022年5月1日 00:00

在学习和使用Java的多线程前,需要了解一些关于计算机中进程,线程的基础知识。

1.线程,进程和管程

1.1 线程(Thread)

  • 定义:线程是操作系统中能够独立运行的最小单位,是进程的一个执行分支。一个进程可以包含多个线程,它们共享同一进程的资源(如内存和文件句柄)。
  • 特点
    • 线程之间的创建和销毁开销较小。
    • 线程间共享内存,通信较为高效,但也容易引发竞争条件和数据不一致问题。

1.2 进程(Process)

  • 定义:进程是程序在计算机上运行的实例,它拥有自己的内存空间和资源。进程之间是相互独立的,通常通过进程间通信(IPC)进行数据交换。
  • 特点
    • 进程有自己的地址空间,线程间不共享内存。
    • 进程的创建和销毁开销较大,但提供更好的隔离性和稳定性。

1.3 管程(Monitor)

  • 定义:管程是一种高层次的同步机制,用于控制对共享资源的访问。它将共享资源的访问和管理封装在一个对象中,并提供互斥访问。
  • 特点
    • 管程通常包括一个互斥锁和一些条件变量。
    • 通过管程,可以避免线程间的竞争条件,简化线程同步的复杂性。

2.串行、并行和并发

2.1 串行(Serial)

  • 定义:派发多个任务,所有任务都按照顺序先后执行。
  • 特点:顺序执行,执行总时长几乎等于每个任务执行的时间相加。

2.2 并行(Parallelism)

  • 定义:并行是指派发多个任务在同一时刻同时执行直到全部完成。通常是在多核处理器上,多个任务可以同时在不同的核心上运行。
  • 特点:同一时间分别执行,每个任务执行都不被打断,执行总时长约等于耗时最长的那个任务需要的时间。

2.3 并发(Concurrency)

  • 定义:派发多个任务在同一时间段内进行,不一定是同时执行的。任务可能在共享的时间片上交替运行。
  • 特点:同一时间交替的执行,任务有被其他任务抢走时间片后中断和抢占其他任务的时间片的可能,执行总时长可能小于以串行或并行来执行这些任务的总时长。

3.Java多线程的实现和常见方法

3.1 继承Thread类创建子线程

通过继承java.lang.Thread,重写其run()方法来创建自定义线程类,然后实例化该类并调用start()方法启动线程,jvm自动调用run()方法,run()方法运行的就是子线程。

start()方法执行后,线程不一定立即创建,因为线程是操作系统的资源,需要等待操作系统分配

1.可以通过重写带参(线程名)构造函数,或调用setName()设置线程名。

2.每个Thread只能执行一次run()方法否则会出现IllegalThreadStateException异常,如果我们自己直接运行线程的run()方法等同于对象调用方法,仍然是单线程。

3.通过Thread.currentThread()可以获取当前运行的线程。

class MyThread extends Thread {    public MyThread(String name) {        super(name);    }    @Override    public void run() {        System.out.println("Thread is running.");        System.out.println(Thread.currentThread().getName());    }}MyThread thread = new MyThread("t1");thread.start();
class MyThread extends Thread {    @Override    public void run() {        System.out.println("Thread is running.");        System.out.println(Thread.currentThread().getName());    }}MyThread thread = new MyThread();thread.setName("t1");thread.start();

通过匿名内部类写法

new Thread("t1") {   @Override   public void run() {      System.out.println("Thread is running.");      System.out.println(Thread.currentThread().getName());   }}.start();

3.2 实现Runnable接口创建子线程

通过实现java.lang.Runnable接口并重写其run()方法实现一个Target对象,然后将该Target传递给Thread,同时可以选择指定一个线程名,再调用Thread的start()方法启动线程,jvm自动调用run()方法,在run()方法开启子线程。

实现Runnable接口创建子线程,既可以避免单继承的局限,又能使得代码更加清晰,把子线程的任务与执行任务的Thread对象分开,可以用一个任务创建出多个线程同时执行。

class MyRunnable implements Runnable {    @Override    public void run() {        System.out.println("Thread is running.");    }}Thread thread = new Thread(new MyRunnable());thread.start();Thread thread2 = new Thread(new MyRunnable(), "t2");thread2.start();

无论哪种方式创建线程,都不能自己手动调用Thread的run()方法,必须调用start()start()方法最终调用C++实现的native方法start0(),由JVM调用run()方法,所以自己调用run()方法无法实现多线程。
java.lang.Thread

public synchronized void start() {  /**  * This method is not invoked for the main method thread or "system"  * group threads created/set up by the VM. Any new functionality added  * to this method in the future may have to also be added to the VM.  *  * A zero status value corresponds to state "NEW".  */  if (threadStatus != 0)     throw new IllegalThreadStateException();  /* Notify the group that this thread is about to be started  * so that it can be added to the group's list of threads  * and the group's unstarted count can be decremented. */  group.add(this);  boolean started = false;  try {     start0();     started = true;  } finally {     try {           if (!started) {              group.threadStartFailed(this);           }     } catch (Throwable ignore) {           /* do nothing. If start0 threw a Throwable then           it will be passed up the call stack */     }  }}private native void start0();

3.3 创建守护线程

JVM的线程分为用户线程和守护线程

用户线程:系统的工作线程,会完成这个程序需要完成的业务操作。

守护线程:服务线程,没有服务对象就没有必要继续运行下去了。

Java中,通过设置setDaemon(true)来实现一个守护线程,需要在调用start()方法之前设置,当主线程结束后,守护线程即使还有任务未完成,JVM进程也会退出。如果子线程没有设置为守护线程,即使主线程完成,子线程仍然继续执行未完成的任务,JVM不会退出。

public static void main(String[] args) {   Thread daemon = new Thread(() -> {      System.out.println(Thread.currentThread().getName());      while (true) {      }   }, "daemon");   daemon.setDaemon(true);   daemon.start();   System.out.println(Thread.currentThread().getName());}

3.4 线程的暂停:sleep()

暂停执行(睡眠)多少毫秒

Thread.sleep(2000);

3.5 检查线程是否存活:isAlive()

通过Thread.sleep();使线程休眠,观测线程开启和结束后的状态。

public static void main(String[] args) {   Thread t1 = new Thread(() -> {      System.out.println(Thread.currentThread().getName());      try {         Thread.sleep(2000);      } catch (InterruptedException e) {         throw new RuntimeException(e);      }   }, "t1");   t1.start();   System.out.println(t1.isAlive()); //true   try {      Thread.sleep(4000);   } catch (InterruptedException e) {      throw new RuntimeException(e);   }   System.out.println(t1.isAlive()); //false}

3.6 让出CPU:yield()

静态方法,当前线程让出CPU,给同级或更高优先级线程机会

Thread.yield();

还有一些线程的方法涉及到线程间的通信,见:Java线程间的通信机制

Java线程间的通信机制

2022年5月1日 00:00

当需要多个线程共同完成一件任务,而且需要有规律的执行,那么多个线程之间需要一定的通信机制,可以协调他们的工作,以此实现多线程共同操作一份数据。

1.等待唤醒机制

这是一种线程间的协作机制,与争夺锁的竞争机制相对应,当一个线程满足某个条件时,就进入等待状态( wait/wait(m) ),等到其他线程执行完指定的代码后,再将其唤醒,或者可以指定时间,到时间了自动唤醒,有多个线程等待时,如果有需要,可以notifyAll()唤醒所有等待的线程,wait/notify就是一种线程间的协助机制。

wait()notify()notifyAll()都是java.lang.Object中的方法,这说明在Java语言的设计中,任何对象都能充当同步监视器(锁)

1.1 wait

作用:使当前线程等待,直到其他线程调用相同对象的notify()notifyAll()方法,或者线程被中断。
使用场景:当一个线程需要等待某个条件发生时,比如资源可用、状态改变等。
条件:调用wait()方法时,当前线程必须持有该对象的监视器锁(synchronized),否则会抛出 IllegalMonitorStateException 异常。

synchronized (lock) {    while (condition) {        lock.wait(); // 释放锁并等待    }    // 条件满足后的操作}

1.2 notify

作用:唤醒在该对象上等待的一个线程。如果有多个线程在等待,则随机选择一个线程唤醒。
使用场景:在某个条件被满足时,通知一个等待的线程继续执行。

synchronized (lock) {    // 修改条件    lock.notify(); // 唤醒一个等待线程}

1.3 notifyAll

作用:唤醒在该对象上等待的所有线程。
使用场景:当条件变化可能影响所有等待线程时,使用 notifyAll() 确保所有线程都有机会重新检查条件。

synchronized (lock) {    // 修改条件    lock.notifyAll(); // 唤醒所有等待线程}

1.4 例:两个线程交替累加

第1次执行:假设线程1先抢到锁,进去后会先唤醒了线程2,但是锁还是线程1持有的,线程2只能等待,线程1将变量i加1变成1,wait释放锁进入等待唤醒状态。

第2次执行:线程2拿到锁后同样先唤醒了线程1,但是现在锁是线程2持有,线程1无法执行,线程2将变量i加1变成2后,wait释放锁进入等待唤醒状态。

第3次执行:线程1拿到锁还是先唤醒线程2然后将变量i加1变成3,然后wait释放锁。

……

第99次执行:线程1拿到锁还是先唤醒线程2然后执行加1将变量i变成99,然后wait释放锁。

第100次执行:线程2进来先唤醒了线程1,然后将i加到100后wait释放锁,此时循环加到100就已经完成,但是线程的执行还没有结束,被唤醒的线程1继续执行,先唤醒了线程2然后进入if判断,但是这次已经不能再加了,所以线程1没有wait就退出了while循环,离开synchronized块后自动失去锁,线程2随后自然得到锁进入synchronized块,同样判断已经不能再加了后随即也跳出while循环,线程1和线程2就都在就绪状态自然结束了,随后JVM进程退出。

public class TestAdd {    static int i = 0;    public static void main(String[] args) {        Runnable runnable = new Runnable() {            @Override            public void run() {                while (true) {                    synchronized (this){                        this.notify();                        if (i < 100) {                            System.out.println(Thread.currentThread().getName() + "---" + ++i);                        }                        else {                            break;                        }                        try {                            this.wait();                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                }            }        };        new Thread(runnable).start();        new Thread(runnable).start();    }    }

1.5 wait退出时检查

lock.wait()阻塞了的线程,一旦被唤醒,或超时时,会从wait()方法下面的代码继续向下执行,即直接跳出if执行下面的代码,需要注意的是此时已经醒来的线程并不会再次判断if中的条件是否满足而是跳出if直接向下执行,这样就会遇到一个问题,线程阻塞期间,其他线程进行的一些操作可能造成条件改变,不能满足if中的条件了,如果不加以二次判断就继续执行,就可能导致程序出错。

synchronized (lock) {    if (条件不满足) {        lock.wait();    }    //继续执行后面操作}

Java给出的解决办法是:让wait总是出现在循环中,使用while去判断wait的条件而不是if,当使用while时,线程被唤醒后,不会继续跳出while块向下执行,而是会再判断一次while中的逻辑,如果条件不满足会继续wait,直到条件满足跳出while。

synchronized (lock) {    while (条件不满足) {        lock.wait();    }    //继续执行后面操作}

例1:如果使用if,wait中的线程被唤醒时,不会再次判断if中的条件

public class SimpleWakeupDemo {    private volatile static boolean flag = true;     private static final Object lock = new Object();    public static boolean condition() {        System.out.println(Thread.currentThread().getName() + "判断flag = " + flag);        return flag;    }    public static void main(String[] args) {        Thread t1 = new Thread() {            @Override            public void run() {                synchronized (lock) {                    if (condition()) {                        try {                            System.out.println(Thread.currentThread().getName() +" before wait");                            lock.wait();                            System.out.println(Thread.currentThread().getName() +" after wait");                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                    System.out.println(Thread.currentThread().getName() + "打印flag = " + flag);                }            }        };        Thread t2 = new Thread() {            @Override            public void run() {                synchronized (lock) {                    try {                        Thread.sleep(900);                        lock.notifyAll();                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                }            }        };        t1.start();        t2.start();    }}
Thread-0判断flag = trueThread-0 before waitThread-0 after waitThread-0打印flag = true

如果改成while,可以看到wait结束后,会再次判断条件是否成立

while (condition()) {    try {        System.out.println(Thread.currentThread().getName() +" before wait");        lock.wait();        System.out.println(Thread.currentThread().getName() +" after wait");    } catch (InterruptedException e) {        throw new RuntimeException(e);    }}
Thread-0判断flag = trueThread-0 before waitThread-0 after waitThread-0判断flag = trueThread-0 before wait

例2:生产消费模型:两个线程操作同一变量,一个判断变量为0就加1变成1,另一个判断变量为1就减1变成0

public class SimpleWakeupDemo {    private volatile static int product = 0;     private static final Object lock = new Object();    public static void main(String[] args) {        Runnable consumer = new Runnable() {            @Override            public void run() {                synchronized (lock) {                    while (product < 1) {                        try {                            System.out.println(Thread.currentThread().getName() + "不足");                            System.out.println("before wait");                            lock.wait();                            System.out.println("after wait");                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                    product --;                    System.out.println(Thread.currentThread().getName() + " = " + product);                    lock.notifyAll();                }            }        };        Runnable productor = new Runnable() {            @Override            public void run() {                synchronized (lock) {                    try {                        Thread.sleep(900);                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                    while (!(product < 1)) {                        try {                            System.out.println(Thread.currentThread().getName() + "已满");                            System.out.println("before wait");                            lock.wait();                            System.out.println("after wait");                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                    product ++;                    System.out.println(Thread.currentThread().getName() + " = " + product);                    lock.notifyAll();                }            }        };        for (int i = 0; i < 10; i++) {            new Thread(productor, "productor-"+i).start();            new Thread(consumer, "consumer-"+i).start();        }    }}

当使用while时,操作后的数总是0或1

productor-0 = 1productor-3已满before waitconsumer-8 = 0productor-9 = 1productor-8已满before waitconsumer-1 = 0productor-7 = 1productor-6已满before waitconsumer-6 = 0consumer-5不足before waitproductor-1 = 1consumer-4 = 0productor-4 = 1consumer-3 = 0productor-2 = 1consumer-9 = 0consumer-2不足before waitconsumer-7不足before waitproductor-5 = 1consumer-0 = 0after waitconsumer-7不足before waitafter waitconsumer-2不足before waitafter waitconsumer-5不足before waitafter waitproductor-6 = 1after waitproductor-8已满before waitafter waitproductor-3已满before waitafter waitconsumer-5 = 0after waitconsumer-2不足before waitafter waitconsumer-7不足before waitafter waitproductor-3 = 1after waitproductor-8已满before waitafter waitconsumer-7 = 0after waitconsumer-2不足before waitafter waitproductor-8 = 1after waitconsumer-2 = 0

如果换成if,就会错误的出现其他的数字

productor-0 = 1productor-2已满before waitproductor-9已满before waitconsumer-8 = 0productor-7 = 1productor-8已满before waitconsumer-7 = 0consumer-6不足before waitconsumer-4不足before waitproductor-6 = 1consumer-5 = 0productor-5 = 1productor-4已满before waitconsumer-3 = 0productor-1 = 1productor-3已满before waitconsumer-2 = 0consumer-9不足before waitconsumer-1不足before waitconsumer-0不足before waitafter waitproductor-3 = 1after waitproductor-4 = 2after waitconsumer-4 = 1after waitconsumer-6 = 0after waitproductor-8 = 1after waitproductor-9 = 2after waitproductor-2 = 3after waitconsumer-0 = 2after waitconsumer-1 = 1after waitconsumer-9 = 0

1.6 小总结

线程互相交替执行的过程可以简记为:1判断,2干活,3通知

synchronized (Object) {    while (....) { //判断        try {            Object.wait();        } catch (InterruptedException e) {            throw new RuntimeException(e);        }    }    //干活    //.........    Object.notifyAll(); //通知}   

2.线程连接:join方法

主执行中插入其他线程m1,主线程立刻被阻塞,直到插入的线程m1执行完成。

public static void main(String s[]) {    Thread m1 = new Thread() {        @Override        public void run() {            for (int i = 0; i < 100; i++) {                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(this.getName() +" = " + i);            }        }    };    m1.setName("m1");    m1.start();    for (int i = 0; i < 100; i++) {        /**i加到20的时候,插入子线程,子线程执行完了(消亡),主线程再继续*/        if (i == 20) {            try {                m1.join();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName() +" = " + i);    }}

3.volatile

volatile是一种轻量级的线程通信机制,具体见:volatile作用分析

Java线程安全和同步机制

2022年5月1日 00:00

当多个线程同时访问同一资源(变量,文件,记录),如果只有读操作,则不会有线程安全问题,如果有读和写操作,则会产生线程安全问题,必须保证共享数据同一时刻只能有同一个线程操作。Java采取的办法是synchronized同步代码块或同步方法。同步代码块或同步方法解决了线程安全问题,但是操作共享数据时,线程时串行执行的,意味着效率较低。

1.多线程安全问题

经典卖票案例:

两个线程一块卖票,没有加同步代码块,程序运行结果不正确,存在超卖重卖

public class Ticket {    public static void main(String[] args) {        TicketTask t1 = new TicketTask();        TicketTask t2 = new TicketTask();        t1.start();        t2.start();    }    static class TicketTask extends Thread {        static int ticket = 200;        @Override        public void run() {            while (true) {                if (ticket > 0) {                    System.out.println(Thread.currentThread().getName() +" " + ticket);                    ticket--;                }                else {                    break;                }            }        }    }}

2 同步代码块和同步方法解决线程安全问题

2.1 同步代码块

需要被同步的代码,即为操作共享数据的代码。共享数据,即为多个线程都需要操作的数据。同步监视器可以由任何类的对象担任,但是多个线程必须共用同一个同步监视器。

同步代码块的语法

synchronized (同步监视器/) {  //需要被同步的代码}

加入同步代码块,程序运行结果正确,当有线程操作共享数据,其他线程需要等待。lock作为同步监视器,锁住代码块中的操作,谁获得同步监视器,谁运行同步代码块中的代码。

public class Ticket {    public static void main(String[] args) {        TicketTask t1 = new TicketTask();        TicketTask t2 = new TicketTask();        t1.start();        t2.start();    }    static class TicketTask extends Thread {        static int ticket = 200;        static final Object lock = new Object();        @Override        public void run() {            while (true) {                synchronized (lock) {                    if (ticket > 0) {                        System.out.println(Thread.currentThread().getName() +" " + ticket);                        ticket--;                    }                    else {                        break;                    }                }            }        }    }}

2.2 同步方法

如果需要同步执行的代码恰好在一个方法中,可以使用同步方法保证线程安全,在方法声明上使用 synchronized 关键字,此时锁为对象实例(this)。同一时刻,只有一个线程能够执行该实例的方法。

public synchronized void test() {}

3 同步代码块和同步方法的使用

3.1 Runnable创建线程时使用同步代码块

使用synchronized使得实例方法加锁变为同步方法,因为Runnable对象只有一个,所以锁可以直接使用当前调用者this

public class TestRunnable {    public static void main(String[] args) {        Target target = new Target();        for (int i = 0; i < 10; i++) {            new Thread(target, "T"+i).start();        }    }}class Target implements Runnable {    private Integer i = 1000;    @Override    public void run() {        while (true) {            try {                Thread.sleep(200);            } catch (InterruptedException e) {                e.printStackTrace();            }            synchronized (this) {                if (i > 0) {                    i--;                    System.out.println(Thread.currentThread().getName() + "->" + i);                } else {                    break;                }            }        }    }}

3.2 Thread类创建线程时,使用同步代码块

Thread类创建线程时,使用同步代码块加锁,因为Thread对象是多个,所以需要静态的监视器对象object,如果还用this就出现了多个锁

public class TestThread {    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            new MyThread("T"+i).start();        }    }}class MyThread extends Thread {    private static Integer i = 1000;    //同步监视器,锁    private static Object object = new Object();    public MyThread(String name) {        super(name);    }    @Override    public void run() {        while(true) {             synchronized(object) {                if (i > 0) {                    i--;                    System.out.println(Thread.currentThread().getName() +"->" + i);                } else {                    break;                }            }        }    }}

3.3 Runnable创建线程时,使用同步方法加锁

public class TestRunnable {    public static void main(String[] args) {        Runnable runnable = new Runnable() {            private int num = 1000;            @Override            public void run() {                while (true) {                    this.show();                }            }             public synchronized void show() {                try {                    Thread.sleep(20);                } catch (InterruptedException e) {                    e.printStackTrace();                }                if (num > 0) {                    num--;                    System.out.println(Thread.currentThread().getName() + "->" + num);                }            }        };        for (int i = 0; i < 10; i++) {            new Thread(runnable, "T" + i).start();        }    }}

3.4 Thread创建线程时,使用同步方法加锁

public class TestThread {    public static void main(String[] args) {        TicketTest ticketTest = new TicketTest();        for (int i = 0; i < 10; i++) {            new Thread("T"+i) {                @Override                public void run() {                    while (true) {                        ticketTest.test();                    }                }            }.start();        }    }    static class TicketTest  {        private int num = 1000;        private synchronized void test() {            if (num > 0) {                num--;                System.out.println(Thread.currentThread().getName() + "->" + num);            }        }    }}

3.5 静态方法加锁和使用.class对象做锁

在静态方法上使用 synchronized,锁住的是类的.class对象,每个类的class对象只有一个,所以同时只能有一个线程进入方法。

public static synchronized void staticMethod() {    // 方法体}

同步块上使用.class对象做锁,因为每个类.class对象只有一个,故也能用于保证线程安全

synchronized (A.class) {}

4 死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

写程序时,要避免出现死锁。

示例代码1

死锁原因的分析:这个例子是个比较明显的死锁,线程t1t2几乎同时启动,在一秒钟的等待时间里,t1获得了锁lock1t2获得了锁lock2,一秒钟后t1又想去获得lock2,但是现在lock2t2持有,需要一直等直到t2释放lock2,与此同时,t2也想去获得lock1,但是lock1现在被t1持有,需要一直等待,直到t1释放lock1,两个线程都在争抢在对方持有的锁,且都在等待对方先释放各自持有的锁,不然就一直等待,线程都一直处在阻塞状态无法继续运行,造成死锁。

public class TestDeadLock {    public static void main(String[] args) {        Object lock1 = new Object();        Object lock2 = new Object();        Thread t1 = new Thread() {            @Override            public void run() {                synchronized (lock1) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        Thread.currentThread().interrupt();                    }                    synchronized (lock2) {                        System.out.println(lock1);                        System.out.println(lock2);                    }                }            }        };        Thread t2 = new Thread(){            @Override            public void run() {                synchronized (lock2) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        Thread.currentThread().interrupt();                    }                    synchronized (lock1) {                        System.out.println(lock1);                        System.out.println(lock2);                    }                }            }        };        t1.start();        t2.start();    }}

示例代码2

这是一个不是非常明显的死锁的例子,线程thread1thread2几乎同时开始执行,thread1执行a.fun(b)时,由于A类的fun方法是个同步方法,故锁是当前调用者this对象,即a,调用fun方法,thread1便持有了锁a,与此同时,thread2同理的持有了锁b,这些都在一秒钟前完成了,1秒钟后,thread1执行blast同步方法,同理需要先获得锁b,但是锁b目前被thread2持有,同时thread2也开始执行alast方法,需要先持有锁a,但是锁athread1持有,双方都在等待对方先释放自己需要的锁,否则就一直阻塞无法继续运行,造成死锁。

public class TestDeadLock2  {    public static void main(String[] args) {        A a = new A();        B b = new B();        Thread thread1 = new Thread() {            @Override            public void run() {                a.fun(b);            }        };        Thread thread2 = new Thread() {            @Override            public void run() {                b.fun(a);            }        };        thread1.start();        thread2.start();    }    public static class A extends Thread {        public synchronized void fun(B b) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            b.last();        }        public synchronized void last() {        }    }    public static class B extends Thread {        public synchronized void fun(A a) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            a.last();        }        public synchronized void last() {        }    }}
❌