多线程

返回当前线程的名称:Thread.currentThread().getName() 线程的名称是由:Thread-编号定义的。编号从 0 开始。 线程要运行的代码都统一存放在了 run 方法中。

线程要运行必须要通过类中指定的方法开启。start 方法。(启动后,就多了一条执行路径) start 方法:1)、启动了线程;2)、让 jvm 调用了 run 方法。

Thread 类中 run()和 start()方法的区别:

start():用 start 方法来启动线程,真正实现了多线程运行,这时无需等待 run 方法体代码 执行完毕而直接继续执行下面的代码。通过调用 Thread 类的 start()方法来启动一个线程,这时 此线程处于就绪(可运行)状态,并没有运行,一旦得到 cpu 时间片,就开始执行 run()方法,这 里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run 方法运行结束,此线程随即终 止。
run():run()方法只是类的一个普通方法而已,如果直接调用 Run 方法,程序中依然只有主线 程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待 run 方法体执行完毕 后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:start()方法最本质的功能是从 CPU 中申请另一个线程空间来执行 run()方法中的代码, 它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的 run()方法,当然也会执行,但那是 在当前线程中执行,run()方法执行完成后继续执行下面的代码. 而调用 start()方法后,run()方法的代码会和当前线程并发(单 CPU)或并行 (多 CPU)执行。所以请 记住一句话:调用线程对象的 run 方法不会产生一个新的线程,虽然可以达到相同的执行结果,但 执行过程和执行效率不同

创建线程的第一种方式:继承 Thread ,由子类复写 run 方法。

步骤: 1,定义类继承 Thread 类;
2,目的是复写 run 方法,将要让线程运行的代码都存储到 run 方法中;
3,通过创建 Thread 类的子类对象,创建线程对象;
4,调用线程的 start 方法,开启线程,并执行 run 方法。

线程状态:

被创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备 cpu 的执行资格,没有 cpu 的执行权;
消亡:stop()

java.png

创建线程的第二种方式:实现一个接口 Runnable。

步骤: 1,定义类实现 Runnable 接口。
2,覆盖接口中的 run 方法(用于封装线程要运行的代码)。
3,通过 Thread 类创建线程对象;
4,将实现了 Runnable 接口的子类对象作为实际参数传递给 Thread 类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的 run 方法所属的对象。
5,调用 Thread 对象的 start 方法。开启线程,并运行 Runnable 接口子类中的 run 方法。

Ticket t = new Ticket(); 
/*
直接创建 Ticket 对象,并不是创建线程对象。
因为创建对象只能通过 new Thread 类,或者 new Thread 类的子类才可以。
所以最终想要创建线程。既然没有了 Thread 类的子类,就只能用 Thread 类。
20 / 31
*/
Thread t1 = new Thread(t); //创建线程。 
/*
只要将 t 作为 Thread 类的构造函数的实际参数传入即可完成线程对象和 t 之间的关联
为什么要将 t 传给 Thread 类的构造函数呢?其实就是为了明确线程要运行的代码 run 方
法。
*/
t1.start(); 
 
为什么要有 Runnable 接口的出现?

1:通过继承 Thread 类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类 已经有了自己的父类,就不可以继承 Thread 类,因为 java 单继承的局限性。 可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢? 只有对该类进行额外的功能扩展,java 就提供了一个接口 Runnable。这个接口中定义了 run 方法,其实 run 方法的定义就是为了存储多线程要运行的代码。 所以,通常创建线程都用第二种方式。
因为实现 Runnable 接口可以避免单继承的局限性。

2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义 到接口中。为其他类进行功能扩展提供了前提。 所以 Thread 类在描述线程时,内部定义的 run 方法,也来自于 Runnable 接口。 实现 Runnable 接口可以避免单继承的局限性。而且,继承 Thread,是可以对 Thread 类中的 方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现 Runnable 接口更方便一些。所以 Runnable 接口将线程要执行的任务封装成了对象。

//面试 
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnable run");
}
})
{
public void run(){
System.out.println("subthread run");
}
}.start(); //结果:subthread run

synchronized 关键字(一)

  一、当两个并发线程访问同一个对象 object 中的这个 synchronized(this)同步代码块时,一 个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行 该代码块。
  二、然而,当一个线程访问 object 的一个 synchronized(this)同步代码块时,另一个线程仍 然可以访问该 object 中的非 synchronized(this)同步代码块。   三、尤其关键的是,当一个线程访问 object 的一个 synchronized(this)同步代码块时,其他 线程对 object 中所有其它 synchronized(this)同步代码块的访问将被阻塞。   四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问 object 的一个 synchronized(this)同步代码块时,它就获得了这个 object 的对象锁。结果,其它线程对该 object 对象所有同步代码部分的访问都被暂时阻塞。   五、以上规则对其它对象锁同样适用.

package ths;
public class Thread1 implements Runnable { 
 public void run() { 
 synchronized(this) { 
 for (int i = 0; i < 5; i++) { 
 System.out.println(Thread.currentThread().getName()+"synchronized 
loop " + i); 
 } 
 } 
 } 
}

synchronized 关键字(二)

synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。

1.synchronized 方法:通过在方法声明中加入 synchronized 关键字来声明 synchronized 方法。 如:
  public synchronized void accessVal(int newVal);   synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方 法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁, 直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机 制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处 于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访 问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
  在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明 为 synchronized ,以控制其对类的静态成员变量的访问。
  synchronized 方法的缺陷:若将一个大的方法声明为 synchronized 将会大大影响效率,典型 地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行, 因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类 成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问 题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

2.synchronized 块:通过 synchronized 关键字来声明 synchronized 块。语法如下:

    synchronized(syncObject) { 
    //允许访问控制的代码 
    }

  synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可 以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上 锁的对象,故灵活性较高。
  对 synchronized(this)的一些理解
  一、当两个并发线程访问同一个对象 object 中的这个 synchronized(this)同步代码块时,一 个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行 该代码块。
  二、然而,当一个线程访问 object 的一个 synchronized(this)同步代码块时,另一个线程仍 然可以访问该 object 中的非 synchronized(this)同步代码块。
  三、尤其关键的是,当一个线程访问 object 的一个 synchronized(this)同步代码块时,其他 线程对 object 中所有其它 synchronized(this)同步代码块的访问将被阻塞。
  四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问 object 的一个 synchronized(this)同步代码块时,它就获得了这个 object 的对象锁。结果,其它线程对该 object 对象所有同步代码部分的访问都被暂时阻塞。
  五、以上规则对其它对象锁同样适用。

解决安全问题的原理:

只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执 行就可以解决这个问题。
如何保障共享数据的线程安全呢?
java 中提供了一个解决方式:就是同步代码块。

格式:
synchronized(对象) { // 任意对象都可以。这个对象就是共享数据。 
 需要被同步的代码; 
} 

同步:★★★★★

好处:解决了线程安全问题。Synchronized

弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

同步的第二种表现形式: //对共享资源的方法定义同步 同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。

同步函数是用的哪个锁呢? //synchronized(this)用以定义需要进行同步的某一部分代码块 通过验证,函数都有自己所属的对象 this,所以同步函数所使用的锁就是 this 锁。This.方法名

当同步函数被 static 修饰时,这时的同步用的是哪个锁呢?

静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载 进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class

同步代码块和同步函数的区别?

同步代码块使用的锁可以是任意对象。 同步函数使用的锁是 this,静态同步函数的锁是该类的字节码文件对象。

在一个类中只有一个同步的话,可以使用同步函数。如果有多同步,必须使用同步代码块, 来确定不同的锁。所以同步代码块相对灵活一些。

★考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步, 解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。

//懒汉式:延迟加载方式。 当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。所以容易出现 线程安全问题。为了解决,加入同步机制,解决安全问题。但是却带来了效率降低。
为了效率问题,通过双重判断的形式解决。


class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ //锁是谁?字节码文件对象;
if(s == null){ 
 synchronized(Single.class){ 
 if(s == null) 
 s = new Single(); 
 } 
 } 
return s;
}
}

等待唤醒机制:涉及的方法:

  wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线 程池中。
  notify:唤醒线程池中某一个等待线程。
  notifyAll:唤醒的是线程池中的所有线程。

注意:

  1:这些方法都需要定义在同步中。
  2:因为这些方法必须要标示所属的锁。
  你要知道 A 锁上的线程被 wait 了,那这个线程就相当于处于 A 锁的线程池中,只能 A 锁的 notify 唤醒。
  3:这三个方法都定义在 Object 类中。为什么操作线程的方法定义在 Object 类中? 因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意 对象,那么能被任意对象调用的方法一定定义在 Object 类中。

wait 和 sleep 区别: 分析这两个方法:从执行权和锁上来分析:

  wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的 notify 或者 notifyAll 来唤醒。
  sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
  wait:线程会释放执行权,而且线程会释放锁。
  sleep:线程会释放执行权,但不是不释放锁。

线程的停止:通过 stop 方法就可以停止线程。但是这个方式过时了。

停止线程:原理就是:让线程运行的代码结束,也就是结束 run 方法。   怎么结束 run 方法?一般 run 方法里肯定定义循环。所以只要结束循环即可。
  第一种方式:定义循环的结束标记。
  第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过 Thread 类中 的 interrupt 方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到 标记,并结束。
---------< java.lang.Thread >----------
interrupt():中断线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。 当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用 join 方法。
当 A 线程执行到了 B 线程的 join 方式。A 线程处于冻结状态,释放了执行权,B 开始执行。A 什么时候执行呢?只有当 B 线程运行结束后,A 才从冻结状态恢复运行状态执行。

LOCK 的出现替代了同步:lock.lock();………lock.unlock();

Lock 接口:多线程在 JDK1.5 版本升级时,推出一个接口 Lock 接口。

解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。 到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释 放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这 些动作定义在了锁当中,并把锁定义成对象。

所以同步是隐示的锁操作,而 Lock 对象是显示的锁操作,它的出现就替代了同步。

在之前的版本中使用 Object 类中 wait、notify、notifyAll 的方式来完成的。那是因为同步中的 锁是任意对象,所以操作锁的等待唤醒的方法都定义在 Object 类中。

  而现在锁是指定对象 Lock。所以查找等待唤醒机制方式需要通过 Lock 接口来完成。而 Lock 接口 中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是 Condition,将 Object 中的三个方法进行单独的封装。并提供了功能一致的方法 await()、 signal()、signalAll()体现新版本对象的好处。

< java.util.concurrent.locks > Condition 接口:await()、signal()、signalAll();

class BoundedBuffer {
 final Lock lock = new ReentrantLock();
 final Condition notFull = lock.newCondition(); 
 final Condition notEmpty = lock.newCondition(); 
 final Object[] items = new Object[100];
 int putptr, takeptr, count;
 public void put(Object x) throws InterruptedException {
 lock.lock(); 
 try {
 while (count == items.length) 
 notFull.await();
 items[putptr] = x; 
 if (++putptr == items.length) putptr = 0;
 ++count;
 notEmpty.signal(); 
 } 
 finally { 
 lock.unlock(); 
 }
 }
 public Object take() throws InterruptedException {
 lock.lock(); 
 try {
 while (count == 0) 
 notEmpty.await();
 Object x = items[takeptr]; 
 if (++takeptr == items.length) takeptr = 0;
 --count;
 notFull.signal();
 return x;
 } 
finally { 
 lock.unlock(); 
 }
 } 
}
Last Updated 10/25/2025, 5:44:13 AM
ON THIS PAGE