线程状态
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
Runnable状态是线程获得cpu资源的唯一入口,绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;所以获得synchronized之后是先到就绪状态再到执行状态
如何使用多线程
第一个是自己定义一个类MyThread在继承Thread,再重写run方法
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
// TODO Something
}
}
第二个是实现Runnable接口,并重写该接口的run()方法
class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
// TODO something
}
}
再创建一个线程对象,启动线程到就绪状态
1. MyThread myThread = new MyThread();
2. myThread.start();
1. MyRunnable myRunnable = new MyRunnable();
2. myRunnable.strat();
或者在主函数直接创建Thread对象实现重写run()在启动线程
new Thread(new Runnable(){
public void run() {
// TODO something
}
}).start();
使用多线程就不得不了解线程同步的概念
什么是线程同步?
线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。A,B相互协调完成任务
为何要使用线程同步机制(synchronized)?
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 多个线程读或者写相同的数据等情况时可能会导致数据不一致。为了解决这些问题,引入了synchronized同步方法
使用synchronized实现同步方法背后的原理:
即有synchronized关键字修饰的方法,这个方法肯定是某一个类里面的,要用对象调用
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
所以要使用公共资源变量去操作数据时一定要先拿到内置锁,否则只能等待其他线程执行完释放锁才能去获取锁
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
synchronized可以有三种加锁的方式
在成员方法代码块上加锁
public void method() {
synchronized(this) { // this 指的是这个对象
// TODO 代码块
}
}
成员方法上加锁
public synchronized void method() {
// TODO 代码块
}
静态方法加锁
public class SynchronizedTest {
// 取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
public static synchronized void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public static synchronized void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
final SynchronizedTest test2 = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1(); // 直接调用
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method2();
}
}).start();
}
}
使用synchronized的缺陷
出自该片: https://www.cnblogs.com/dolphin0520/p/3923167.html
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢(lock是一个接口)?
我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
Lock的用法
看源码
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
常用的是lock() 和 unlock()
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待.
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:
Lock lock = new ReentrantLock(); // ReentrantLock(重入锁) 以可以响应中断的方式加锁
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //要手动释放锁
}