《Java Concurrency in Practice》学习笔记:
Q:Thread.sleep()方法什么时候触发InterruptedException?
A:线程执行start()方法启动后,当执行sleep()方法的时候,线程又执行了interrupt()方法,会触发InterruptedException()
public class MultiThreadTest extends Thread {
public static void main(String[] args) throws InterruptedException {
System.out.println("main thread start.");
Thread t = new MultiThreadTest();
t.start();
t.join(2000);
t.interrupt(); // 这里将出发线程InterruptedException
System.out.println("main thread end.");
}
@Override
public void run() {
System.out.println("sub thread start.");
try {
Thread.sleep(99999999);
} catch (InterruptedException e) {
// 截获InterruptedException
System.err.println("sub thread 被中断了");
return;
}
System.out.println("sub thread end.");
}
}
// 输出结果
main thread start.
sub thread start.
sub thread 被中断了
main thread end.
Q:什么是守护线程?
A:Java有两种Thread:“守护线程(Daemon thread)”与“用户线程”。当虚拟机中将在用户线程结束后退出,如果没有用户线程,守护线程将随虚拟机一同退出。通过调用线程的setDaemon(true)方法设置线程为守护线程。
servlet原来是线程不安全的
下面的servlet用于计算两个输入值的乘机,打开两个浏览器,分别快速输入地址:
http://localhost:8888/xxxx/a=2&b=3
http://localhost:8888/xxxx/a=1&b=55
结果会发现两个浏览器输出的值是一样的,都是首先打开网址的结果。
public class TestServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
int result;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Integer a = Integer.parseInt(req.getParameter("a"));
Integer b = Integer.parseInt(req.getParameter("b"));
if(a!=null && b != null){
result = a * b;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resp.getWriter().print(result);
}
}
问题:servlet容器复用了servelt实例,当多个线程同时访问这个实例的时候,如果servelt包含实例变量(int result)会发生值被覆盖的情况。
解决
- 干掉实例变量(本例为result),让servlet变成无状态
- 使用synchronized关键字,同步方法或者代码块,这样就只有一个线程可以访问同步代码块的内容
synchronized (this) {
if (a != null && b != null) {
result = a * b;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resp.getWriter().print(result);
}
方法和代码块同步
关键字synchronized放在非static方法,则进入该方法需要对象锁;同理进入synchronized (obj){...}的代码块,也需要obj的对象锁
举例说明:有一个BankAccount对象,两个线程访问它,一个是存款线程(deposit),一个存款线程(withdraw)
public class BankAccount {
// ......
private int balance;
public void deposit(int amount) {
balance = balance + amount;
}
public void withdraw(int amount) {
balance = balance - amount;
}
}
假设初始余额为1000,存款和取款线程各执行10万次,发现结果不确定,可能是几万,或者负数。
原因:多线程并发修改一个对象的实例属性时,值会相互影响(覆盖)。
解决:使用synchronized 同步临界代码块(critical section)或者同步方法:
public void deposit(int amount) {
synchronized (this) {
balance = balance + amount;
}
}
public void withdraw(int amount) {
synchronized (this) {
balance = balance - amount;
}
}
无状态的对象总是线程安全的.
线程安全的类封装了所有需要同步的代码,所以客户端不需要再进行同步代码