创建线程的方式
一、继承Thread类创建线程类
需要在类中重写run()
public class myThread extends Thread
{
public void run(){
for(int i=0;i<100;i++){
System.out.println(getName()+" "+i);//线程实例的方法,前缀是this
}
}
public static void main(String[] args){
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if (i==20)
new myThread().start();//此时有主线程和子线程两个
}
}
}
二、实现Runnable接口创建线程类
public class demo{
public static void main(String[] args){
Thread t1=new Thread(new Runnable(){//也可以单独用一个类实现Runnable接口,然后把实现类的实例作为target
public void run(){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);//线程实例的方法
}
}
},"子线程");
t1.start();
}
}
常见方法
sleep()
- Thread的类,由Thread实例调用
- sleep让当前正在执行的线程暂停,进入阻塞态,时间结束进入就绪态
- sleep的方法声明抛出了interruptedExceptiin,调用者必须处理,捕获或抛出
- 不释放同步监视器,但会让cpu,让出cpu时不理会其他线程的优先级
yield()
- yield让当前正在执行的线程暂停,立马又进入就绪态,因此没有时间参数。
- 方法声明没有抛出异常
- 不释放同步监视器,但会让cpu,让出cpu时只会给同优先级或更好优先级
wait()
- Object的类,由同步监视器调用
- wait()让当前线程暂停,进入阻塞态,直到有人来唤醒它(由其他线程调用该同步监视器的notify方法),唤醒后进入就绪态
- wait方法会释放同步监视器
suspend()
当其他线程调用了该线程的suspend()方法要将该线程挂起,该线程不会释放同步监视器。
同步方法\同步代码块\同步锁
同步代码块
其中obj是同步监视器,线程开始执行同步代码块之前,必须先获得对该同步代码块的同步监视器的锁定。
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,线程会释放掉对该同步监视器的锁定。
同步方法
同步方法就是使用synchronized关键字修饰的方法,同步方法的同步监视器的this,即调用这个方法的对象
执行同步方法之前,必须获得对这个对象的实例的锁定,这个方法执行完后会释放对这个实例的锁定。
同步锁Lock
线程通信常用方法
synchronized关键字线程同步
下面是使用synchronized关键字来保证线程同步的方法
wait(),notify(),notifyAll()都是Object的方法!!!并不是Thread类的,都需要同步监视器来调用。
wait()
- 必须通过同步监视器来调用这个方法(同步代码块是括号里的对象,同步方法是调用者)
- 导致当前线程等待暂停,进入阻塞态,直到有其他人来唤醒它(调用该同步监视器的notify()方法或者notifyAll()方法即可唤醒它),唤醒后它才进入就绪态
- wait()会让当前线程释放对该同步监视器的锁定
notify()notifyAll()
- 必须通过同步监视器来调用这个方法(同步代码块是括号里的对象,同步方法是调用者)
- notify()唤醒在此同步监视器上等待的单个线程(只有当前线程放弃对该同步监视器的锁定后(使用wait()会放弃)执行被此方法唤醒的线程)
- notifyAll()唤醒在此同步监视器上等待的所有线程只有当前线程放弃对该同步监视器的锁定后才能执行被此方法唤醒的线程)
Lock线程同步/通信
下面是使用Lock对象来保证同步的方法——同步锁,此时没有所谓的同步监视器了,所以用到了Condition接口。
await(),signal(),signalAll()都是Condition的方法,都需要Condition来调用——他的作用类似同步监视器。通过lock.new Condition()可以获得一个Condition实例。
await()
- 必须通过Condition来调用这个方法
- 类式隐式同步监视器上的wait(),导致当前线程等待,直到其他线程调用该Condition的signal()或者signalAll()来唤醒此线程
signal()
- 必须通过Condition来调用这个方法
- 唤醒此Lock对象上等待的单个线程。(只有当线程放弃该Lock对象的锁定后才可以执行被此方法唤醒的线程)
上面图写错了,应该是唤醒其他堵塞在此lock对象的线程
方法总结
- 程序调用sleep,yield来暂停当前线程执行,当前线程不会释放同步监视器。
- 当前线程的suspend()被别的线程调用,该线程不会释放同步监视器。
- 程序执行了同步监视器对象的wait(),则当前线程暂停并释放同步监视器。
- wait()notify() notifyAll()都必须通过同步监视器来调用这个方法(同步代码块是括号里的对象,同步方法是调用者)
- await()signal() signalAll()都必须通过同步监视器来调用这个方法(同步代码块是括号里的对象,同步方法是调用者)
线程同步(同步监视器、同步方法)
- 线程同步,就是我在处理某件事情的时候,其他线程也来处理这件事,此时他们只能在外面等待,直到我处理完这件事情
同步监视器
- 可以用同步监视器来实现线程同步,使用同步监视器的通用方法就是同步代码块
- 当多个线程进入同步代码块之前,必须要获得同步监视器,任何时刻只能有一个线程能获得,未获得的只能在同步代码块外面等着,执行完同步代码块就会释放对同步监视器的锁定
- 任何对象都可以作为同步监视器,但是建议用可能被并发访问的共享资源作为同步监视器,synchronized(obj){ } 中obj就是同步监视器
同步方法
- 使用同步监视器还可以用同步方法,同步方法就是用synchronized修饰的方法,此时无需显式指定同步监视器,同步方法的同步监视器就是this,即调用这个方法的对象
区别
- 多个线程的执行体都在run或者call,对共享资源的操作都要在run才能被执行。同步代码块没有名字可以调用,所以全部写在run里;而同步方法则可以写在共享数据所在的类里,然后run方法去调用即可
- 同步方法这种在资源所在的类定义对该资源操作的对应方法,而不是直接在run里实现取钱逻辑更符合面向对象规则
线程通信
- 虽然无法准确控制线程轮换执行,但我们还是可以通过一些机制来保证线程协调执行。比如让AB交替执行,而不能连续执行A或B。
使用synchronized关键字来保证线程同步
为实现这种功能,可以借助Object类的wait(),notify(),notifyAll(),这三个方法必须由同步监视器来调用(同步代码块是括号里的对象,同步方法是调用者)
使用Lock对象来保证同步
- 如果程序不使用synchronized关键字来保证线程同步,而是直接用Lock对象来保证同步。那么就没有同步监视器了,也就不能用wait,notify
notifyall方法了(三个方法都必须由同步监视器调用),因此,java提供了一个Condition类来保持协调,Condition可以让得到Lock对象的线程释放Lock对象,也可以唤醒其它正处于等待的线程。 - Lock替代了同步代码块或同步方法,Condition替代了同步监视器的功能
- Condition提供了await()让得到Lock对象的线程释放Lock对象,提供了signal(),signalAll()唤醒其它正处于等待此Lock对象的线程。
- 通过调用Lock对象的newConditiom()方法即可获得lock实例的condition实例