线程的生命周期
java语言使用Thread类及其子类的对象来表示线程, 在它的一个完整的生命周期中通常要经历五种状态:新建、就绪、运行、阻塞、死亡。
线程的同步
多个线程同时进行就会出现线程安全的问题
例题:模拟火车站售票程序,开启三个窗口售票。(具体代码见添加链接描述)
解决办法
Synchronized的使用方法
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程公共操作的变量
同步监视器:俗称:锁。任何一个类的对象都可以充当锁。
要求:多个线程必须要共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器
/**
* 例子:创建3个窗口买票,总票100张
* 使用同步代码块解决继承Thread类的方式的线程安全问题
*/
class Window extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
// synchronized (this){//错误方法
//正确的
// synchronized (obj) {
synchronized (Window.class){ //Window.class只会加载一次
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(getName() + ":卖票,票号为: " + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
/**
* 使用同步方法解决实现Runnable接口的线程安全问题
*/
class Window2 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
public synchronized void show(){//同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 window = new Window2();
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
Thread thread3 = new Thread(window);
thread1.setName("窗口一");
thread2.setName("窗口二");
thread3.setName("窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
使用同步机制解决单例模式懒汉式的线程安全问题
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
return instance;
}
//方式二:效率稍高
if (instance == null){
synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
方式三:Lock(锁)从JDK 5.0开始
结构:
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码;
}
finally{
lock.unlock();
}
}
}
优先使用顺序:
Lock ----》同步代码块(已经进入了方法体,分配了相应资源)----》 同步方法
(在方法体之外)
面试题:synchronize 与 lock的异同
同:二者都可以解决线程安全的问题
异:synchronize机制在执行完相同的同步代码以后,自动释放同步监视器
lock需要手动的启动同步,同时结束同步也需要手动的实现(unlock())
面试题:如何解决线程安全问题?有几种方式
利用同步机制解决线程安全问题,以上3种。
wait() 与 notify() 和 notifyAll()
- ①wait()一旦执行此方法,当前线程就进去阻塞状态
- ②notify()会唤醒被wait的一个线程
- ③notifyAll()唤醒所有被wait的线程
说明:
- 1.此三个方法必须使用在同步代码块或同步方法中
- 2.此三个方法的调用者必须是同步代码块或同步方法中的同步监视器否则会出现异常
- 3.此三个方法都定义在object类中
- 面试题:sleep()和wait()方法的异同
同:都可以使得当前的线程进入阻塞状态
异:1.两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait().
2.调用的要求不同:sleep()可以再任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
3.关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。