java中关于线程的一些API:
* 线程的启动
线程的启动有两种方式:继承Thread类,复写run方法;实现Runnable接口,实现run方法。
两种方式都可以,但切记继承Thread类时如果要启动线程需要调用的是start方法而不是run方法,如果调用的是run方法的话不会开启新线程,只会在当前线程中执行run方法中的代码,如下面的代码
public class ThreadStart {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
ThreadRun t = new ThreadRun();
t.run();
//t.start();
}
}
class ThreadRun extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
上面的代码,如果调用的是run方法,得到的线程名字都是main线程,如果调用的是start方法得到的就是两个线程名字
* 线程中断
关于线程中断有三种方式:
public void interrupt//中断
public static boolean interrupted()//判断是否中断并清除中断标志位
public boolean isInterrupted//判断是否中断
关于中断和Thread中的stop方法是有区别的,调用stop方法是立刻停止线程,这样可能会造成数据不一致的现象,而调用interrupt方法是给当前线程添加中断标志,但不一定会立即中断
第一个是实例方法,它通知目标线程中断,也就是设置中断标志位。中断标志位表示当前线程已经被中断了;第三个也是实例方法,它判断目标线程是否有被中断;第二个是静态方法,也是用来判断是否有被中断,但它会清除目标线程的中断标志位。
public class ThreadInterruptDemo1 {
public static void main(String[] args)throws InterruptedException {
Thread t = new Thread(){
@Override
public void run(){
while (true){
Thread.yield();
}
}
};
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
在上述代码中,虽然对t进行了中断,但是线程并没有进行中断,如果想要中断需要进一步处理代码:
public class ThreadInterruptDemo1 {
public static void main(String[] args)throws InterruptedException {
Thread t = new Thread(){
@Override
public void run(){
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("thread is interrupted");
break;
}
Thread.yield();
}
}
};
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
这样就会中断退出。如果在循环体中出现类似wait或者sleep等操作时,只能通过中断来识别
public class ThreadInterruptDemo1 {
public static void main(String[] args)throws InterruptedException {
Thread t = new Thread(){
@Override
public void run(){
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("thread is interrupted");
break;
}
try {
Thread.sleep(2000);
}catch (InterruptedException e){
System.out.println("Interrupt when sleep");
//设置中断标志
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
};
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
Thread.sleep()方法由于中断而抛出的异常,它会清除中断标志,如果不加处理,那么在下一次循环中就无法捕获到这个中断标志位。
* 等待(wait)和通知(notify)
wait和notify必须使用在synchronized代码块中,当一个线程调用了wait方法时,这个线程就会进入等待状态并且会释放锁,当另外一个线程调用notify时,这个线程才会被唤醒(假设此处只有两个线程,并且等待队列中只有这一个线程)。
注意事项:wait和sleep方法都是让线程进行等待,最重要的区别是wait方法会释放当前锁,而sleep不会;线程调用notify方法之后并不会立刻释放锁,只有当notify之后的代码执行完毕之后才会释放锁。
public class ThreadWaitAndNotify {
private static Object object = new Object();
public static class T1 extends Thread{
@Override
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{
@Override
public void run(){
synchronized (object){
System.out.println(System.currentTimeMillis()+" :T2 start!");
object.notify();
System.out.println(System.currentTimeMillis()+" :T2 end!");
try {
Thread.sleep(2000);
}catch (InterruptedException e){
}
}
}
}
public static void main(String[] args) {
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
运行结果如下:
1512288311968 :T1 start!
1512288311968 :T1 wait for object
1512288311969 :T2 start!
1512288311969 :T2 end!
1512288313970 :T1 end!
* 等待线程结束(join)和谦让(yield)
很多时候,一个的输入可能非常依赖另外一个或多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。JDK提供了两个方法:
public final synchronized void join(long millis)//最大等待时间,当超过这个时间主线程就会继续执行
public final void join()//无限期等待,直到目标线程执行完毕
yield:
public static native void yield()
这是一个静态方法,一旦执行,它会使当前线程让出CPU,但要注意,让出CPU并不表示当前线程不执行了。当前线程在让出CPU后,还会进行CPU资源的争夺。
* 守护线程(daemon)
当一个应用中只有守护线程时,表示这个java虚拟机就会自然退出
注意事项:设置守护线程时一定要设置在start方法之前,否则会报IllegalThreadStatException异常,报出这个异常并不会使线程停止,该线程还会继续执行。
* 线程优先级
在Thread类中有表示线程优先级的字段
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
数值越大,优先级越高。
* 线程安全(synchronized)
并行程序的开发是以线执行结果的争取性为保证的,所以线程安全是并行程序开发额根本和根基。以下这个程序就是典型的线程不安全
public class ThreadSynchronizeDemo1 implements Runnable {
static ThreadSynchronizeDemo1 instance = new ThreadSynchronizeDemo1();
static volatile int i = 0;
public void increase(){
i++;
}
@Override
public void run(){
for(int j = 0; j < 1000000; j++){
increase();
}
}
public static void main(String[] args)throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
上述的结果会小于2000000,线程t1和t2同时读取i为0,并各自计算得到i=1,并先后写入这个结果,因此,虽然i++被执行了2次,但是实际i的值只增加了1,想解决这个问题synchronized关键字是一个解决方案。
synchronized可以有多种用法:
[x] 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁
[x] 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
[x] 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁
这时对上述代码进行改造:
public synchronized void increase(){
i++;
}
在这个实例方法上加synchronized关键字进行同步
在上述代码尤其要特别注意:在创建线程时使用的是同一个对象实例,如果此时使用的两个不同的对象实例,在increase()方法上加synchronized关键字是不起作用的,如果想得到正确的结果而又是不同的对象实例,可以在increase()方法之前加上static关键字,让这个方法变为静态方法,这个synchronized关键字就会作用于这个类上。
* 并发下的list
JDK中的util包中的list是线程不安全的类,如下面的代码:
public class ThreadList {
static List
static class AddThread implements Runnable{
@Override
public void run(){
for(int i = 0; i <1000000; i++){
list.add(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new AddThread());
Thread t2 = new Thread(new AddThread());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(list.size());
}
}
上面的代码可能会出现三种情况:
第一种:程序正常结束
第二种:抛出异常:Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 15 这是因为ArrayList在扩容过程中,内部一致性被破坏,但由于没有锁的保护,另外一个线程访问到了不一致的内部状态,导致出现越界问题。
第三种:程序正常结束,但是结果不正确。