2.1 线程必须要知道的事
进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程:是轻量级进程,是程序执行的最小单位。
一个线程的生命周期
2.2初始线程
2.2.1 新建线程
Thread t1 = new Thread();
t1.start();
线程Thread,有一个方法run()方法。start()方法就会新建一个线程并让这个线程执行run()方法。
Thread t1 = new Thread();
t1.run();
以上代码不能新建一个线程,而是在当前线程中调用run()方法,只是一个普通的方法调用。
Thread t1 = new Thread(){
@Override
public void run(){
System.out.println("hello ,I am t1");
}
};
t1.start();
上述代码使用了内部匿名类,重写了run()方法;也可以使用Runnable接口来实现同样的操作。
public interface Runnable(){
public abstract void run();
}
Thread类中的构造方法:
public Thread(Runnable target)
它传入一个Runnable接口的实例,在start()方法调用时,新的线程就会执行Runnable.run()方法,默认的Thread.run()方法如下:
public void run(){
if(target != null){
target.run();
}
}
具体实现如下:
public class CreateThread implements Runnable{
public static void main(String args[]){
Thread t1 = new Thread(new CreateThread());
t1.start();
}
@Override
public void run(){
System.out.println("i am run");
}
}
2.2.2 终止线程
不要随便使用stop()方法来终止一个线程。
原因:stop()方法会直接终止线程,并立即释放这个线程所持有的锁,而锁恰恰是用来维护对象的一致性的。
例如:写线程写入数据写到一半,强行stop(),那么对象就会被写坏,但由于锁已经释放,其他读线程就会读出错误的数据。
我们可以使线程在合适的地方退出
public class ChangeObjectThread extends Thread {
volatile boolean stopme = false;
public void stopme(){
stopme = true;
}
public void run(){
while(true){
//判断线程是否退出,在此阶段退出
if(stopme){
System.out.println("exit by stop me");
break;
}
//修改对象数据
synchronized (u){
u.setId();
u.setName();
}
Thread.yield();
}
}
}
上述代码标记了一个变量stopme,用于指示线程是否需要退出。使用这种退出方式,线程就没有机会在写坏数据了,因为它总是在一个合适的时间终止进程。
2.2.3 线程中断
线程中断不会使线程立即退出,而是给线程发一个通知,告知目标线程,至于何时退出,则由线程自己选择一个合适的时间。
3个线程中断有关的方法
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断线程是否中断
public static boolean Thread.interrupted() //判断是否被中断,并判断当前中断状态
中断示例:
public class Test {
public static void main(String args[]) throws InterruptedException {
Thread t1 = new Thread(){
public void run(){
while(true){
//判断线程是否中断
if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted!");
break;
}
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
2.2.4 等待(wait)和通知(notify)
wait()方法和notify方法是在Object类中的,意味这任何类都可以调用这两个方法。
这两个方法的声明如下:
public final void wait() throws InterruptedException
public final native void notify()
在线程中,如果调用了obj.wait()方法,线程就会停止执行,进入一个等待队列,直到其他线程调用了obj.notify()方法,从等待队列中随机选择一个线程唤醒。
Object.wait()必须包含在对应的synchronized语句中,无论是wait()还是notify()都必须获得目标对象的监视器。
一个小实例:
package chapter2;
public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread{
public void run(){
{
synchronized (object){
System.out.println(System.currentTimeMillis()+":T1 start!");
try {
System.out.println(System.currentTimeMillis()+":T1 wait for object");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+":T1 end!");
}
}
}
}
public static class T2 extends Thread{
public void run(){
synchronized (object){
System.out.println(System.currentTimeMillis()+":T2 start,notify one thread");
object.notify();
System.out.println(System.currentTimeMillis()+":T2 end");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]){
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
2.2.5 挂起(suspend)和继续执行(resume)
现已被标注为废弃方法,不推荐使用。
原因:suspend方法在导致线程暂停的同时,不会释放任何锁资源,使得其他需要访问此资源的线程都无法正常运行。
2.2.6 等待线程结束(join)和谦让(yeild)
一个线程的输入可能非常依赖于另一个或多个线程的输出,这时就要用到join()。
join的两个方法:
public final void join() throws InterruptedException
public final synchronized void join(long mills) throws InterruptedException
其中第一个方法表示无限等待,会一直阻塞当前线程,直到目标线程执行完毕。
第二个方法给出了一个最大等待时间,如果超出时间,那么线程将不再等待。
一个小实例:
package chapter2;
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread{
@Override
public void run() {
for(i = 0;i < 100000000; i++);
}
}
public static void main(String args[]) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
在执行at.join后,i总是等于100000000,说明主线程等待at线程执行完成后,再继续执行。
2.3 volatile关键字
volatile声明一个变量,就等于告诉了虚拟机,这个变量极有可能会被某些程序或线程修改。
根据编译器的优化规则,如果不使用volatile关键字,那么变量修改后,其他线程可能并不会通知到。
2.4 线程组
在一个系统中,可以将相同功能的线程放置在同一个线程组中。
一个小实例:
package chapter2;
public class ThreadGroupName implements Runnable{
public static void main(String args[]){
ThreadGroup tg = new ThreadGroup("PrintGroup");
Thread t1 = new Thread (tg,new ThreadGroupName(),"T1");
Thread t2 = new Thread (tg,new ThreadGroupName(),"T2");
t1.start();
t2.start();
//获得当前组活动线程总数
System.out.println(tg.activeCount());
tg.list();
}
@Override
public void run() {
String groupAndName = Thread.currentThread().getThreadGroup().getName()+"-"+Thread.currentThread().getName();
while(true){
System.out.println("I am "+groupAndName);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.5 守护线程(Daemon)
守护线程提供一些系统性的服务,比如垃圾回收器线程和JIT线程。当用户线程全部结束,只有守护线程时,应用程序应该结束,此时java虚拟机将会退出。
一个小实例:
package chapter2;
public class DaemonDemo {
public static class DaemonT extends Thread{
@Override
public void run() {
while(true){
System.out.println("I am alive");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]) throws InterruptedException {
Thread t = new DaemonT();
t.setDaemon(true);//设置t为守护线程
t.start();
Thread.sleep(3000);
}
}
当主线程mian休眠3秒后退出时,t也退出了。
2.6 线程优先级
在java中,使用1到10表示线程优先级。一般可以使用三个内置静态变量表示
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
数字越大,优先级越高
通过setPriority()方法来设定线程的优先级。
2.7 synchronized关键字
关键字synchronized的作用是实现线程间的同步。它的工作是对同步代码的加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全。
synchronized的各种用法:
指定对象加锁:进入同步代码前要获得给定对象的锁。
直接作用于实例方法:进入同步代码前获得当前实例的锁。
直接作用于静态方法:进入同步代码前要获得当前类的锁。