一、线程简单操作
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