Java多线程学习总结之---多线程基础
1、线程相关概念
1)、线程与进程的区别
线程是操作系统调度的最小单元,也叫轻量级进程,而进程是操作系统中的应用程序,在进程中可以创建多个线程。
2)、上下文切换
我们知道现代处理器都是多核的,几核处理器只能同时处理几个线程,多线程执行程序看起来是同时进行,实际上是CPU在多个线程之间快速切换执行,这中间就涉及到上下问切换,所谓的上下文切换就是指一个线程T被分配的时间片用完了之后,线程的信息被保存起来,CPU执行另外的线程,再到CPU读取线程T的信息并继续执行T的过程。
2、线程实现方式
1)、继承Thread类
由于类的单继承性,使用这种方式实现就无法再去继承其他的线程,有局限,耦合度高。
public class MyThread extends Thread{ @Override public void run() { super.run(); System.out.println("我继承了Thread类..."); } }
2)、实现Runnable接口
大多数都用这种方式实现,很灵活,重写run()方法即可,run() 方法没有返回值。
3)、实现Callable接口
若是想获取到线程的执行结果,那就用这种方式,它和实现Runnable接口的区别是要重写call()方法,call()方式是有返回值的,返回的Object是任务的执行结果,可以用Future接口的实现类FutureTask来接收,并调用get()方法获取到执行结果。另外call()方法可抛出异常,而run()方法是不能抛出异常的
public class Test { public static void main(String[] args) throws Exception { Thread thread1 = new MyThread(); thread1.start(); Thread thread2 = new Thread(new MyRunnable()); thread2.start(); Callable callable = new MyCallable(); FutureTask<String> future = new FutureTask<>(callable); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(future); System.out.println(future.get()); executorService.shutdown(); } static class MyRunnable implements Runnable{ @Override public void run() { System.out.println("我实现了Runnable接口..."); } } static class MyCallable implements Callable{ @Override public String call() throws Exception { return "我实现了Callable接口..."; } } }
执行结果:
我继承了Thread类...
我实现了Runnable接口...
我实现了Callable接口...
3、线程状态
根据jdk中Thread类的State内部类,线程有6种状态,下次面试官问你线程有几种状态,你可以很有底气的回答:6种,如下左图,右图是线程状态之间的转换。
这里注意:线程在等待进入synchronzed方法或者synchronized块时的线程状态时BLOCKED,而在等待进入lock锁时的状态是WAITING或者TIME_WAITING,因为lock是用LockSupport实现的(源码还没研究)。
测试如下:
public class MyService { public static synchronized void serviceMethod1(){
try{
System.out.println(Thread.currentThread().getName+"进入了业务方法");
Thread.sleep(millis: 1000);
} catch (Execption e){
e.printStackTrace();
}
}
public static void serviceMethod2(){
ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); System.out.println(Thread.currentThread().getName()+"进入了业务方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock.unlock();
}
}
public class MyThread1 extends Thread{ @Override public void run() { MyService.serviceMethod1(); } public class MyThread2 extends Thread{ @Override public void run() { MyService.serviceMethod1(); } } public class MyThread3 extends Thread{ @Override public void run() { MyService.serviceMethod2(); } } public class MyThread4 extends Thread{ @Override public void run() { MyService.serviceMethod2(); } } public class ThreadStatusTest { public static void main(String[] args){ test1(); test2(); } public static void test1(){ MyThread1 t1 = new MyThread1(); t1.setName("a"); t1.start(); MyThread2 t2 = new MyThread2(); t2.setName("b"); t2.start(); System.out.println("t2的状态:"+t2.getState()); } public static void test2(){ MyThread3 t3 = new MyThread3(); t3.setName("a"); t3.start(); MyThread4 t4 = new MyThread4(); t4.setName("b"); t4.start(); System.out.println("t4的状态:"+t4.getState()); } }
test1() 运行结果:
a进入了业务方法
t2的状态:RUNNABLE
b进入了业务方法
test2() 运行结果:
a进入了业务方法
t4的状态:RUNNABLE
b进入了业务方法
4、常用方法
1)、start() 启动线程的一种方式,线程调用start()方法后由NEW状态变为RUNNABLE状态中的READY状态,等待CPU分配时间片,它是Thread类的方法;
2)、 run() 所需实现的逻辑写在run方法里,线程获得时间片后由READY状态变为RUNNING,并自动调用run()方法,当然run()方法也可以直接调用,那它就是普通方法,和线程无关;
3)、sleep(...) 线程由RUNNABLE状态变为TIMED_WAITING状态,调用此方法会抛出InterruptedException异常,线程自己拥有系统资源,并等待时间到了,自己醒来,这是和wait()方法的主要区别;
4)、wait() notify() notifyAll() 这三个都是Object类的方法,它们是配合使用的,调用wait()方法的线程状态由RUNNABLE中的RUNNING变为WAITING状态,并且此对象是不占有系统资源的,当调用notify()或notifyAll() 方法后线程又进入RUNNABLE中的READY状态,等待获取CPU时间片;
5)、join(...) 等待线程对象销毁,线程状态由RUNNABLE变为WAITING或者TIMED_WAITING;
6)、interrupt() 中断线程,线程状态变为TERMINATED;
7)、yield() ,线程状态由RUNNING变为READY,即由运行中变为就绪状态,向处理器表示自己愿意放弃当前CPU资源(让出自己的执行时间),但放弃时间不确定,有可能刚刚放弃,马上又获得CPU时间片,所有此方法并不能保证其它线程一定执行,调用此方法的线程一定不执行,而是看CPU是否分配了时间片,并且它只会让优先级不低于当前线程的线程执行,优先级比它低的是没有机会执行的。
5、线程的优先级
线程优先级为1-10(由低到高),默认优先级是5,优先级高的线程分配的时间片的数量要低于优先级低的,我们可以调用Thread.setPriority(int) 方法来为线程设置优先级,在设置线程优先级时应注意,争对频繁阻塞的,如休眠、IO、数据库等任务的线程应设置较高的优先级,对于偏重计算的,如需要较多的CPU时间或者偏运算的线程则应设置较低的优先级,确保处理器不会被独占。
6、线程间的通信
1)、volatile synchronized
这两个关键字可以实现线程间的通讯,我们知道每个线程都有自己的工作内存,并且它们还有共享内存,线程对一个变量修改时会先从共享内存中读取这个变量到自己私有的工作内存中,若是一个普通变量则修改后刷新到主内存中的时机时随机的,若是volatile变量(可见性和有序性),这时另一个线程来读这个变量,则它会被立即刷新到主内存中去,让后面读取的线程能看到变化,这就实现了两个线程之间的通信。
synchronized实现线程之间的同步,B线程必须等到A线程释放锁才能获得相应的资源,这是线程之间的一种通信方式。
2)、等待通知机制 wait() notify() notifyAll()
一个过程从一个线程开始,在另一个线程结束,前者是生产者,后者是消费者,生成者完成生产,通知消费者去消费,完成二者之间的通信。
等待通知的相关方法有
wait():调用该方法的线程进入WAITING状态,只有被其他线程通知或者被中断才会返回,调用该方法后,会释放对象的锁;
wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回;
wait(long,int):超时时间为long毫秒+int纳秒;
notify():通知一个在对象等待的线程,使其从wait()方法返回,而返回的前提时该线程获取了对象的锁;
notifyAll():通知所有等待在该对象上的线程。
以上方法都来自java.lang.Object类中,所以只要是对象就可以调用它们。
wait()、notify()和notifyAll()调用时需要注意:
a、使用wait()、notify()和notifyAll()时需要先对调用对象加锁;
b、调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列;
c、notify()或者notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或者notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回;
d、notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法将等待队列中所有的线程全部移到同步队列,被移动的线程状
态由WAITING变为BLOCKED;
e、从wait()方法返回的前提是获得了调用对象的锁。
A线程调用wait() 方法会释放持有的对象监视器,进入等待状态,等B线程执行完了后,调用notify()或者notifyAll() 方法唤醒A线程
3)、管道输入、输出流
管道流专门用于线程之间的通信,和普通字符字节流的区别是它们操作的是内存而不是硬盘。
主要有四种实现:字节流:PipedOutputStream、PipedInputStream
字符流:PipedWriter、PipedReader
4)、join(...)
当前存在线程A、B,若A执行了join()方法,意思就是:当前线程A等待B线程执行完成之后,才继续执行,即完成了A、B间的通信。
5)、ThreadLocal
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。可以通过set(T)方法来设置一个值,在当前线程下通过get()方法获取原先获取的值。
下面摘抄《Java并发编程的艺术》中的一段代码:
public class Profiler { private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){ protected Long initialValue(){ return System.currentTimeMillis(); } }; public static final void begin(){ TIME_THREADLOCAL.set(System.currentTimeMillis()); } public static final long end(){ return System.currentTimeMillis() - TIME_THREADLOCAL.get(); } public static void main(String[] args) throws InterruptedException { Profiler.begin(); TimeUnit.SECONDS.sleep(1); System.out.println("Cost: "+Profiler.end() +" mills"); } }
运行结果:
Cost: 1005 mills
参考资料:《Java并发编程的艺术》
最后,如有写的不对或不好的地方,请指出,谢谢!