多线程二
1、线程的生命周期
1.1、线程的五种状态
- JDK中用Thread.State类定义了线程的几种状态
新建:当一个Thraed类或其子类对象被声明并创建时,新生的线程对象处于新建状态
就绪:新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是还没有分配到CPU。
运行:就绪态的进程被调度并获得CPU,遍进入运行状态,run()方法定义了线程的操作和功能
阻塞:在特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作被提前强制性的终止或出现异常导致结束
1.2、五种状态的切换关系
2、线程的同步(重点)
为了解决线程的安全问题
2.1 问题的提出——多窗口售票问题
重票
错票
分析:
* 1、买票过程中,出现重票、错票--->出现了线程安全问题
* 2、问题出现的原因:当耨个线程操作车票的过程中,尚未完成操作时,其他线程也参与进来,操作车票
* 3、解决:当一个线程在操作ticket的时候其他线程不能参与,直到当前线程的操作结束.最终期刊即使当前线程出现了阻塞也不能被改变
- 多个线程执行的不确定性引起执行结果的不确定性
- 多个线程堆共享数据的操作
2.2 java中通过线程同步机制解决线程安全问题
2.2.1 方式一:同步代码块 synchronized()
2.2.1.1 同步代码块解决实现Runnable接口类的线程安全问题
synchronized(同步监视器--同步锁对象){
//需要被同步的代码,操作共享的核心资源
}
说明:1、操作共享数据的代码即为需要同步的代码
2、同步监视器(锁),任何一个类的对象都可以充当锁
要求:多个线程必须共用**同一把锁**
修改实现类代码:
class Window1 implements Runnable{
private int ticket = 100;
//1、新建任意类的对象
Object obj = new Object();
@Override
public void run() {
while (true) {
//2、给操作共享数据的代码加锁
synchronized (obj) {
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "窗口售出一张票,当前余票" + (ticket));
} else break;
}
}
}
}
补充:这里的同步监视器可以是任何类的对象且唯一,说明这里的参数也可以是当前的对象本身,即此处的参数可以为(this)
synchronized(this){//synchronized (obj) {
if (ticket > 0) {
ticket--;
System.out.println(getName() + "窗口售出一张票,当前余票" + (ticket));
} else break;
}
2.2.1.2 同步代码块解决实现Thread类的线程安全问题
子类代码:
class Window extends Thread{
private static int ticket = 100;
//1、为了保证“锁”时同一个,座椅子类需要static修饰
private static Object obj = new Object();
Window(){}
Window(String name){
super(name);
}
@Override
public void run() {
while (true) {
//2、加锁
synchronized (obj) {
if (ticket > 0) {
ticket--;
System.out.println(getName() + "窗口售出一张票,当前余票" + (ticket));
} else break;
}
}
}
}
补充:这里的同步监视器可以是(当前类类名.class)
synchronized (Window.class) {//类也是对象,且类只会加载一次
if (ticket > 0) {
ticket--;
System.out.println(getName() + "窗口售出一张票,当前余票" + (ticket));
} else break;
}
2.2.2 方式二:同步方法 (method)
如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法同步
2.2.2.1 同步方法解决实现Runnable接口类的线程安全问题
实现类:
class Thread2 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){//此时的同步监视器this
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "窗口售出一张票,当前余票" + (ticket));
}
}
}
2.2.2.2 同步方法解决实现Thread类的线程安全问题
class Window1 extends Thread{
private static int ticket = 100;
Window1(){}
Window1(String name){
super(name);
}
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){//同步监视器 Window1.class
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "窗口售出一张票,当前余票" + (ticket));
}
}
}
2.2.3 同步方法的总结
* 同步方法仍涉及到同步监视器,只是不需要显示地声明
* 非static的同步方法,同步监视器是this
* static的同步方法,监视器是当前类本身(Xxxx.class)
2.2.4 同步的方式的优缺点
优点:解决了线程安全问题
不足:操作同步代码时,其他线程无区别等待。相当于一个单线程,效率低
2.2.5 同步方法的两个问题——
2.2.5.1 改写单例模式(懒汉式)为线程安全
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;
}
}
2.2.5.2 死锁问题
出现死锁的原因
- 不同的线程分别占据对方需要的同步资源不放弃,都在等待对方放弃自己需要的资源,就形成了线程额死锁
- 出现死锁后不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态没无法继续
死锁代码举例
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);;
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append("C");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("D");
s2.append("4");
System.out.println(s1);;
System.out.println(s2);
}
}
}
}).start();
}
}
解决死锁的办法
* 专门的算法
* 尽量减少同步资源的定义
* 尽量避免嵌套同步
2.3解决线程安全问题——方式三lock锁
- 为了清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活方便。
- Lock实现提供比synchronized方法和语句可以获得更广泛的锁定操作
- Lock不能直接实例化,这里采用他的实现类ReentrantLock来构建Lock锁对象
public class LockTest {
public static void main(String[] args) {
Window w1 = new Window();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t3.start();
t2.start();
t1.start();
}
}
class Window implements Runnable{
private int ticket = 100;
//实例化ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//调用lock()方法
lock.lock();
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":售票,票号为:"+ticket);
ticket--;
}else break;
}finally {
//3.调用unlock()解锁方法
lock.unlock();
}
}
}
}
2.4 synchronized 与 lock的异同
2.4.1 同
都可以解决线程安全问题
2.4.2 异
synchronized:执行完相应的同步代码后,自动释放同步监视器
Lock需要手动启动同步lock(),结束时也需要手动实现(unlock())
优先使用顺序
Lock >> 同步代码块 >> 同步方法