Java高并发 二 [多线程基础]
1 进程和线程
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
进程:就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念。进程是一个“执行中的程序”,通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。
线程:线程是进程内的执行单元。使用线程的原因是,进程的切换是非常重量级的操作,非常消耗资源。如果使用多进程,那么并发数相对来说不会很高。而线程是更细小的调度单元,更加轻量级,所以线程会较为广泛的用于并发设计。
2 线程的基本操作
2.1 线程状态图
新建状态(NEW):新建一个线程对象。
就绪状态(RUNNABLE):线程对象创建后,调用了其start()方法,该状态的线程既可有机会获取CUP的使用权。
运行状态(RUNNING):就绪状态的线程获取了CPU,执行对象的run()方法。
阻塞状态(BLOCKED):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
ps:调用sleep()方法不会释放锁。
2.2 新建线程
第一种方式
Thread t = new Thread(); //继承thread类,重写run()方法
t.start(); //执行t中的run()方法
第二种方式(推荐用这种方法,可以实现多个接口)
Thread t = new Thread(new Runnalbe1()); //Runnalbe1实现Runnable接口,重写run()方法
t.start(); //执行t中的run()方法
2.3 线程中断
线程中断有3种方法
public void Thread.interrupt() // 中断线程
public boolean Thread.isInterrupted() // 判断是否被中断
public static boolean Thread.interrupted() // 判断是否被中断,并清除当前中断状态
调用线程对象的interrupt方法并不一定会中断正在运行的线程,它只是一种机制,要求线程在合适的时机中断自己。每个线程都有一个boolean的中断属性,而interrupt只是将这个属性置为true,对于非阻塞的线程,只是改变了这个属性,并不会试线程停止。
public void run(){//线程t
while(true){
Thread.yield();
}
}
t.interrupt();//这样子不会使线程中断
public void run(){ //线程t
while(true)
{
if(Thread.currentThread().isInterrupted())
{
System.out.println("Interruted!");
break;
}
Thread.yield();
}
}
t.interrupt();//这样子才能使线程中断
2.4 线程等待
2.4.1、休眠sleep()
调用线程的sleep方法,会让运行中的线程休眠指定的时间,休眠结束后可回到就绪状态,并不一定会立即获得运行状态。sleep是Thread类的静态方法,sleep方法不会释放锁,休眠时其他线程无法访问同个对象。
2.4.2、等待wait()
调用线程的wait方法,也会让运行中的线程等待执行的时间,这个线程进入一个和该对象相关的等待池中。wait是由Object类提供的,wait方法会释放锁,其他线程可以访问。
wait需要notify或者notifyAll或者等待时间来唤醒当前线程池中的线程。
2.4.3、join和yeild
yeild:线程让步,将当前线程从运行状态变为就绪状态,还是有机会再次获得cpu使用权的。
join:当前线程等待调用join方法的线程结束,如果调用线程没结束,当前线程一直处于阻塞。
join的本质:
while(isAlive())
{
wait(0);
}
2.5 基本的线程同步操作
synchronized有三种加锁方式。
1、指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
public class A{
private int i;
public void test(){
synchronized(this){
System.out.println(Thread.currentThread().getName() + "---" + i++);
try {
Thread.sleep(500);
}catch (InterruptedException e){
}
}
}
}
final A a = new A();
final A b = new A();
Thread ta = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i< 3 ; i ++){
a.test();
}
}
});
Thread tb = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i< 3 ; i ++){
b.test();
}
}
});
ta.start();
tb.start();
结果:同步锁的是传入的对象,这里可以发现不会发生锁竞争。
Thread-0---0
Thread-1---0
Thread-0---1
Thread-1---1
Thread-0---2
Thread-1---2
2、直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
public class A{
private int i;
//作用于实例方法
public synchronized void test(){
System.out.println(Thread.currentThread().getName() + "---" + i++);
try {
Thread.sleep(500);
}catch (InterruptedException e){
}
}
}
}
final A a = new A();
final A b = new A();
Thread ta = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i< 3 ; i ++){
a.test();
}
}
});
Thread tb = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i< 3 ; i ++){
b.test();
}
}
});
ta.start();
tb.start();
结果:同步锁的是实例对象,这里可以发现不会发生锁竞争,因为是不同的实例对象。
Thread-0---0
Thread-1---0
Thread-0---1
Thread-1---1
Thread-0---2
Thread-1---2
3、直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
public class A{
private static int i;
//作用于静态方法
public static synchronized void test(){
System.out.println(Thread.currentThread().getName() + "---" + i++);
try {
Thread.sleep(500);
}catch (InterruptedException e){
}
}
}
}
final A a = new A();
final A b = new A();
Thread ta = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i< 3 ; i ++){
a.test();
}
}
});
Thread tb = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i< 3 ; i ++){
b.test();
}
}
});
ta.start();
tb.start();
结果:同步锁的是当前类,这里可以发现会发生锁竞争,因为是同一个类。
Thread-0---0
Thread-0---1
Thread-0---2
Thread-1---3
Thread-1---4
Thread-1---5
Reference:
1. https://my.oschina.net/hosee/blog/599000