线程的创建:
不带返回值:
方式一:
MyThread thread = new MyThread();
通过这种方式创建, MyThread 必须 extend Thread 类并且完成里面的 void run()方法
线程开启后,run方法将会被执行
方式二:
Thread thread = new Thread(task);
task 为 implement 了 Runnable 的类的对象,与方式一一样,类里面的void run()方法必须被完成
线程开启后,run方法会被执行
带返回值:
先建立一个implement Callable<T> 的类,完成里面的 T call()方法
先新建这个Callable对象(返回值为 Integer 为例):
class MyCallable implements Callable<Integer>{
@Override
int call(){
return 1;
}
}
Callable<Integer> callable=new MyCallable<Integer>();
然后再将这个callable对象传入task
FutureTask<Integer> task=new FutureTask<>(callable);
再将此task传入线程
Thread thread = new Thread(task);
这样线程开启后,当调用task.get();,程序会等待线程完成并且返回1
用户线程与守护线程
用户线程的运行是独立的,而守护线程在所有用户线程终止后就会自行终止,无论是否执行完任务
如何创建守护线程?
在构造Thread对象时,将第二个参数传入true可将线程设置为守护线程,否则,将会被设为用户线程
多线程下的数据安全问题
在使用多线程操作同一个数据时,会导致数据不安全
比如:
class ExposeProblem implements Runnable {
private int testNum = 100;
public void run() {
while(testNum > 0){
System.out.println(testNum--);
}
}
}
假设用这个类创建一个task对象,并用10个线程来运行这一个task
在这段代码中,按照代码逻辑,控制台本应该输出100到1,testNum最小值应该为0
但由于多线程,可能会发生这样的情况:
- testNum为1
- Thread1正在执行while条件判断,结果为true
- Thread0在循环内,并且正在执行testNum--
- Thread0输出testNum,为1
- testNum为0
- Thread1在循环内,并且正在执行testNum
- Thread1输出testNum,为0
- testNum为-1
这只是一种数据不安全的示例,在实际运行中,可能会发生更严重的情况,例如输出-1,-2
因此,在多线程中,数据保护非常重要
解决数据安全问题
常用的解决方法有两种
第一种: synchronized
以上面的代码为例,我们在run方法的签名加上synchronized
class ExposeProblem implements Runnable {
private int testNum = 100;
public synchronized void run() {
while(testNum > 0){
System.out.println(testNum--);
}
}
}
此时,当一个线程执行run方法,会把任务对象作为key,并且 hold 住这个 key,此时,别的线程便无法进入run方法,当这个线程执行结束,会自动释放key,其他线程便可以进入
第二种:Lock
还是以前面的代码为例:
class ExposeProblem implements Runnable {
private int testNum = 100;
private Lock lock = new ReentrantLock();
public synchronized void run() {
lock.lock();
while(testNum > 0){
System.out.println(testNum--);
}
lock.unlock();
}
}
用lock()方法来锁,unlock()方法来开锁
这样,当一个线程进入lock.lock();之后,会拿走锁的钥匙,别的进程就无法进入,unlock以后将钥匙归还,别的线程就可以进入,这也能保住testNum不被多条线程同时操作
死锁
使用锁来确保数据安全性,但是使用不当可能会造成死锁的情况,程序堵在一个地方无法运行
例如:
public static void main(String[] args) throws InterruptedException {
Student s = new Student();
Teacher t = new Teacher();
TestThread testThread = new TestThread(s, t);
testThread.start();
t.enter(s);
}
static class Student {
public synchronized void enter(Teacher t) throws InterruptedException {
System.out.println("学生拿着教室的钥匙尝试进入老师办公室");
Thread.sleep(200);
t.leave();
}
public synchronized void leave() {
System.out.println("教室外有钥匙");
System.out.println("老师离开了教室并且归还了钥匙");
}
}
static class Teacher {
public synchronized void enter(Student s) throws InterruptedException {
System.out.println("老师拿着办公室的钥匙尝试进入教室");
Thread.sleep(200);
s.leave();
}
public synchronized void leave() {
System.out.println("办公室外有钥匙");
System.out.println("学生离开了办公室并且归还了钥匙");
}
}
static class TestThread extends Thread {
Student s;
Teacher t;
public TestThread(Student s, Teacher t) {
this.s = s;
this.t = t;
}
@Override
public void run() {
try {
s.enter(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这段代码会造成死锁
老师拿着办公室的钥匙进入教室,但教室的钥匙被学生拿走了,所以老师在教室外面等
学生拿着教室的钥匙进入办公室,但办公室的钥匙被老师拿走了,所以学生在办公室外面等
他们永远也等不到钥匙,这就是死锁
Wait和Notify
每个对象都有wait和notify方法用来暂停线程和恢复其它线程,
例如,Thread1和Thread0都有obj对象,Thread1调用obj.wait(),会暂停,Thread0可以随时调用notify()或notifyAll()来恢复Thread1的线程
这个通常用在线程间通信