线程的状态:线程的所有状态都在Thread中的State枚举中定义:
public class Demo {
public static void main(String[] arg){
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("线程执行啦!");
}
};
t1.start();
}
}
我们通过匿名内部类的方式重载Thread的run方法,start()就会执行run()内的代码。这里要注意t1.run();和t1.start();的区别t1.run();只会在当前线程中串行执行run()中的代码。
Thread类中有一个非常重要的构造方法public Thread(Runnable target)
,单纯使用接口定义Thread,避免重载Thread.run(),实现与 1 同样的操作。
public class Demo implements Runnable{
public static void main(String[] arg){
Thread t1 = new Thread(new Demo());
t1.start();
}
@Override
public void run() {
System.out.println("线程执行啦!");
}
}
在JDK中,Threa提供stop()方法停止线程,但是这个方法被标记为废弃的方法,因为通过stop()方法终止线程,会立即释放线程所持有的所有锁,会导致对象的不一致性。如果我们需要停止一个线程,可以定义一个标记变量stopme,用于指示线程是否需要退出,示例如下:
public class MyThread extends Thread {
volatile boolean stopme = false;
public static class User{
private String name;
private String adress;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAdress() {
return adress;
}
public void setAdress(String adress) {
this.adress = adress;
}
}
User u = new User();
public void stopme() {
stopme =true;
}
@Override
public void run() {
while (true){
if(stopme){
System.out.println("退出线程");
break;
}
synchronized (u){
u.setName("simon");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setAdress("GZ");
}
Thread.yield();
}
}
}
使用这种方式退出,不会是对象u的状态出现错误。
在Java中为了完善线程的退出功能,提供一套自有的线程协作机制——线程中断,与线程中断有关的方法如下:
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断线程是否被中断
public static boolean Thread.interrupted() //判断线程是否被中断,并清除当前中断状态
Thread.interrupt()方法中断,线程不会立刻停下来,比如死循环体。可以通过isInterrupted()方法来判断是否跳出循环体
@Override
public void run() {
while (true){
if(Thread.currentThread().isInterrupted()){
break;
}
synchronized (u){
u.setName("simon");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setAdress("GZ");
}
Thread.yield();
}
}
为了支持多线程间的协作,JDK提供了两个非常重要的接口线程等待wait()通知notify()方法,这两个方法在Object类,任何对象都可以的调用这两个方法,它两是配套的,一个线程调用obj.wait()处于等待状态,需要其他线程调用obj.notify()来唤醒。
public final void wait() throw InterruptedException
public final native void notify() //唤醒等待队列中的任意一个
public final native void notifyAll() //唤醒等待队列所有
注意:
线程挂起suspend()和线程继续执行resume()这两个方法在JDK中被标记为废弃的方法,不推介使用。
1. suspend()会导致休眠线程的所有资源都不会被释放,直到执行 resume() 方法。(如果两个方法,后者执行于前者之前,则线程很难有机会被继续执行)
2. 对于被挂起的线程,其状态是Runnable,这会严重影响到我们对于系统当前状态的判断
public final void join() throws InterruptedException; //无限等待
public final void join(long millis) throws InterruptedException;//等待一定时间
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的(数据可见性)。
2. 禁止进行指令重排序(数据有序性)。
那么volatile可以保证原子性吗?看下面的例子:
public class Demo {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Demo demo = new Demo();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
demo.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(demo.inc);
}
}
大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,这样就会出现上面的问题。
如果线程数量很多,并且功能分配明确,就可以将相同功能的线程放在一个线程组,以书中代码为例:
public class ThreadGroup implements Runnable {
public static void main(String[] args) {
//创建一个叫"PrintGroup"的线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
//加入线程组
Thread t1 = new Thread(tg, new ThreadGroup(), "T1");
Thread t2 = new Thread(tg, new ThreadGroup(), "T2");
t1.start();
t2.start();
//由于线程是动态的,activeCount()获得活动线程总数的估算值
System.out.println("活动线程总数 = " + tg.activeCount());
//打印出线程组的线程信息
tg.list();
//这个方法与Thread.stop()方法遇到问题一样
tg.stop();
}
@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();
}
}
}
}
守护线程是一种特殊的线程,是系统的守护者,在后台完成一些特殊的服务。比如垃圾回收线程,JIT线程都可理解为守护线程。与守护线程相对的叫用户线程,当一个Java应用内只有守护线程时,Java虚拟机就会自然退出。
public class Demo implements Runnable{
public static void main(String[] arg){
Thread t1 = new Thread(new Demo());
//设置守护线程
t1.setDaemon(true);
//设置守护线程必须在线程start()之前设置,否则会得到一个IllegalThreadStateException,但程
//序和线程依然可以正常执行,只是线程被当作用户线程而已
t1.start();
t1.interrupt();
}
@Override
public void run() {
while (true){
System.out.println("I am alive!!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Java中线程可以有自己的优先级。由于线程的优先级调度和底层操作系统有密切关系,在各个平台表现不一,无法精准控制,低优先级的可能一直抢不到资源,处于饥饿状态,在要求严格的场合,需要在应用层自己解决线程调度的问题。
在Java中,使用 1 到 10 表示线程优先级。一般可以使用三个内置的静态标量表示:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
syschronized用法如下:
1. 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
2. 直接作用于实例对象:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
3. 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
public class Demo implements Runnable{
public static Integer i = 0;
static Demo demo = new Demo();
public static void main(String[] arg) throws InterruptedException {
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.print(i);
}
@Override
public void run() {
for(int j = 0;j < 10000;j++){
synchronized (i){
i++;
}
}
}
}
这样不能获得预期的结果,典型的加锁错误。i++的本质是创建一个新的Integer对象,并将引用赋值给i,因此每次锁的对象都不一样。