第二章 线程基本概念(二)

一、线程简单操作

1. 创建线程的方式

1)继承Thread,重写run()。

public class MyThread extends Thread {
    public void run() {
        // 线程逻辑
    }
}
Thread t = new MyThread();
t.start();

2)实现Runnable接口,重写run(),将接口实现类作为Thread的入参。

public class MyRunnable implements Runnable {
    public void run() {
        // 线程逻辑
    }
}
Thread t = new Thread(new MyRunnable());
t.start();

2. 不被推荐使用的stop()方法

 如果此时有两个线程分别是读线程和写线程,写线程正写到一半,被stop()停止执行,此时加在写线程上的锁被释放,读线程获取锁后将会读到脏数据。

public class StopThreadUnsafe {

    public static User u = new User();
    public static class User {
        private int id;
        private String name;

        public User() {
            id = 0;
            name = "0";
        }
        /**getter、setter*/
    }

    public static class ChangeObjectThread extends Thread {
        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    int v = (int)(System.currentTimeMillis() / 1000);
                    u.setId(v);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(v));
                    // System.out.println(u);
                }
                Thread.yield();
            }
        }
    }

    public static class ReadObjectThread extends Thread {
        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    if(u.getId() != Integer.parseInt(u.getName())) {
                        System.out.println(u.toString());
                    }
                }
                Thread.yield();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new ReadObjectThread().start();
        while(true) {
            Thread t = new ChangeObjectThread();
            t.start();
            Thread.sleep(150);
            t.stop();
        }
    }
}

可以通过改进上面的代码,在写线程中,可以在进入同步代码块之前停止线程,这样写线程就没有机会写坏对象。

public static class ChangeObjectThread extends Thread {
	volatile boolean stopme = false;
	public void stopMe() {
		stopme = true;
	}
	@Override
	public void run() {
		while(true) {
			if(stopme) {
				System.out.println("exit by stop me");
				break;
			}
			synchronized (u) {
				int v = (int)(System.currentTimeMillis() / 1000);
				u.setId(v);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				u.setName(String.valueOf(v));
				// System.out.println(u);
			}
			Thread.yield();
		}
	}
}

3. 线程中断

线程中断不会让线程立即退出执行,而是给线程发送了一个通知,告知目标线程有人希望你退出。至于目标线程收到通知后如何处理,则完全由目标线程决定。

三个重要的线程中断方法
public void Thread.interrupt(); //中断线程,将会给目标线程发送中断通知
public boolean Thread.isInterrupted(); // 判断线程是否被中断
public static boolean Thread.interrupted(); // 判断线程是否被中断,并清除当前中断标识

下面的代码虽然调用了线程的中断方法,但是只是给目标线程发送了中断通知,具体的线程该如何处理,取决去目标线程。

public class InterruputThread {
    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("想让我退出,得听我的");
                      break; // 终止该线程靠的是这个break
                  }
                  Thread.yield();
              }
          }
        };
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

中断可以帮助我们在出现类似wait()或者sleep()方法时通过抛出InterruptedException异常,我们可以在catch语句中处理该线程。

public class InterruputSleepThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
          @Override
          public void run() {
              while(true) {
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      System.out.println("我正在休眠呢,居然被吵醒了");
                      // 此时本可以直接退出,但是假如后面有需要保持数据一致性和完整性的逻辑
                      // 但是进了这个catch语句,则会清除中断标识位,这里我们重新设置下
                      this.interrupt();
                  }
                  // 保持数据的一致性和完整性代码
                  System.out.println("==保持数据的一致性和完整性代码==");
                  // 此时可以圆满退出了,通过判断标识位
                  if(Thread.currentThread().isInterrupted()) {
                      System.out.println("被中断了");
                      break;
                  }
                  Thread.yield(); // 让出线程执行权
              }
          }
        };
        t.start();
        Thread.sleep(500);
        t.interrupt();
    }
}

4. 等待(wait)和通知(notify)

这两个方法是Object类中的方法,用于线程间协作用的。线程A调用了obj.wait()后,线程A将停止运行,直到其他线程调用了obj.notify()或obj.notifyAll()。要说明的是这个obj上等待队列上可能不仅仅有A线程,而notify()方法只能唤醒其中的一个,而notifyAll()才能唤醒全部的等待线程。要注意的是在调用wait()和notify()方法时需要被包含在synchronized语句中。

public class SimpleWN {
    // 对象锁
    public static Object obj = new Object();
    public static class T1 extends Thread {

        @Override
        public void run() {
            synchronized (obj) {
                // T1获得了对象锁
                System.out.println(System.currentTimeMillis() + ": T1 start!");
                try {
                    System.out.println(System.currentTimeMillis() + ": T1 for wait");
                    obj.wait();// T1在执行了wait方法后,会释放对象锁
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ": T1 stop!");
            }
        }
    }

    public static class T2 extends Thread {
        @Override
        public void run() {
            synchronized (obj) {
                // T2获得T1释放的对象锁
                System.out.println(System.currentTimeMillis() + ": T2 start!");
                obj.notify(); // 通知obj上的T1线程

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ": T2 end!"); // 执行这行结束,会释放T1的对象锁
            }
        }
    }
    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();


    }
}

5. 不被推荐使用的挂起(suspend)和继续执行(resume)线程

不推荐使用suspend()方法是因为调用后,不会释放锁。这将会导致一大片需要这个锁的线程阻塞。虽然resume()可以将将挂起的线程重新激活,但是万一执行的顺序不对,比如在suspend()方法之前执行呢。如下面代码:

public class BadSuspend {
    public static Object u = new Object();
    private static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }
        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                try {
                    Thread.sleep(1000); // 处理必要逻辑所花费时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 线程挂起
                Thread.currentThread().suspend();
                System.out.println(getName() + " end!");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        t1.start();
        t1.resume();
        t1.join();
    }
}

可以重写一个可靠的suspend()和resume()方法,达到相同的效果。

public class GoodSuspend {
   
    public static Object u = new Object();

    public static class ChangeObjectThread extends Thread {
        volatile boolean suspendme = false;

        public void suspendMe() {
            suspendme = true;
        }

        public void resumeMe() {
            suspendme = false;
            synchronized (u) {
                u.notify();
            }
        }

        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    System.out.println("我正在happy的执行呢");
                    if (suspendme) {
                        System.out.println("我被挂起来了");
                        try {
                            u.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ChangeObjectThread t = new ChangeObjectThread();
        t.start();
        Thread.sleep(1000);
        t.suspendMe();
        Thread.sleep(1000);
        t.resumeMe();
    }
}

6. 等到调用join()的线程执行结束和线程谦让方法yeild()

join()方法有个重载的有参方法,不像join()方法会一直等待,在等待指定的时间后线程还没执行完,则它的父线程会继续往下执行。

public class JoinMain {
    private volatile static int i = 0;

    public static class AddThread extends Thread {
        @Override
        public void run() {
            for(i=0; i<1000000; i++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

值得注意的是,join()方法底层是调用的wait()方法, 而锁是调用wait()方法的线程。因此不要在线程实例上使用类似wait()方法或者notify()方法,否则可能影响join()工作。

yield()方法比较好理解,就是正在执行的线程让出执行权,重新回到就绪状态,等待下次分配CPU执行权。

下一章 线程基本概念(三):https://blog.csdn.net/qq_35165775/article/details/107117440

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章