本篇是系列第二篇,主要介绍下java中线程操作常用的函数和隔离区数据保护方法,join()、wait()、notify()、synchronized、volatile。
1.join()
当使用join的时候,表示该线程插入,当前线程等待该线程执行完毕
当线程执行完毕后,被等待的线程会在退出前调用notifyAll通知所有等待线程继续执行。下面我们做个实验来看下join的具体效果,这样比较好理解。
public class JoinThreadMain {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new TestThread());
thread1.start();
System.out.println("count = " + count);
}
private static int count = 0;
public static class TestThread implements Runnable{
@Override
public void run() {
for(int i=0; i<10000; i++){
count++;
}
}
}
}
从代码看定义了一个static的count变量用于计数,在main函数主线程中调用线程start方法后,接着输出count值,因为start后线程,主线程会直接输出当前的count值,当前count值可能还未执行完线程的循环,所以输出的值一般都是小于10000的,例如下面之行后输出的是63。
count = 63
如果我们要实现子线程完成后再其他线程再输出,就可以使用join方法将该线程临时插入当前线程去执行,直到执行完毕再继续执行当前线程,修改后的代码如下:
public class JoinThreadMain {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new TestThread());
thread1.start();
thread1.join();
System.out.println("count = " + count);
}
private static int count = 0;
public static class TestThread implements Runnable{
@Override
public void run() {
for(int i=0; i<10000; i++){
count++;
}
}
}
}
现在的输出结果就是我们期望的10000了。
count = 10000
2.wait()、notify()
jdk中两个非常重要的接口线程方法,wait和notity,这两个方法不在Thread类而是在Object类。当一个对象实例调用wait方法后,当前线程就会在这个对象上等待,当thread1调用了obj.wait(),那么thread1就会进入等待队列,当obj.notify被调用,系统会从队列中随机选择一个线程继续执行。obj.wait方法不可以随便调用必须包含在对应的synchronzied语句中,无论是wait还是notify都需要先获得目标对象的监视器。
public class WaitNotifyMain {
private final static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Thread1());
Thread thread2 = new Thread(new Thread2());
thread1.start();
thread2.start();
}
public static class Thread1 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread1 start !");
try {
System.out.println(System.currentTimeMillis() + ":thread1 wait for object !");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread1 end!");
}
}
}
public static class Thread2 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread2 start ! notify one thread");
object.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread2 end!");
}
}
}
}
3.synchronized
synchronized是最常用的隔离区保护的关键字,线程中方法如果使用synchronized则表示该方法同时只能有一个线程访问,这就能够保证隔离区数据的线程安全,这也是保证隔离区数据安全最常用的方法,下面定义的add方法不管并发多大计算的结果都不会改变。
public class SyncThread implements Runnable {
private synchronized void add(){
SynchronizedMain.count2++;
}
@Override
public void run() {
for(int i=0; i<100000; i++){
add();
}
}
}
4.Volatile
当用volatile去申明变量时,就等于告诉虚拟机,这个变量极有可能会被某些程序或者线程修改,当值修改,所有线程都能看到该值变化。但是执行会发现count还是不等于100000,因为当有线程并发操作count的时候,虽然count修改后通知到各个线程,但是并发读取到值的线程还是会存在修改后覆盖的情况,所以肯定是小于100000,所以volatile不适合用在大并发的场景。
public class VolatileMain {
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for(int i=0; i<10; i++){
threads[i] = new Thread(new MyVolatileThread());
threads[i].start();
}
for(int i=0; i<10; i++){
threads[i].join();
}
System.out.println(count);
}
public static class MyVolatileThread implements Runnable{
@Override
public void run() {
for(int i=0; i<10000; i++){
count++;
}
}
}
}
网友评论