本文目录一览:
Java中线程怎么同步
- 使用线程类自带的join方法,将子线程加入到主线程,在子线程执行完之后,再执行主线程逻辑。
例如:
public static void joinDemo() throws InterruptedException { System.out.println("=========Test with join====="); JoinWorker worker1 = new JoinWorker("worker1"); JoinWorker worker2 = new JoinWorker("worker2"); worker1.start(); worker2.start(); worker1.join(); worker2.join(); doSuperWork(); }
- 使用JDK的并发包中的CountDownLatch类,每个线程调用其
countDown
方法使计数器减1,主线程调用await
方法阻塞等待,直到计数器为0时继续执行。 定义子线程:
主线程中调用static class CountDownLatchWorker extends Thread { String workerName; CountDownLatch latch; public CountDownLatchWorker(String workerName, CountDownLatch latch) { this.workerName = workerName; this.latch = latch; } public void run() { System.out.println("Sub Worker " + workerName + " do work begin at " + sdf.format(new Date())); new ThreadWaitDemo().doSomeWork(); System.out.println("Sub Worker " + workerName + " do work complete at " + sdf.format(new Date())); latch.countDown(); } }
await
方法阻塞等待,直到所有线程完成:public static void countDownLatchDemo() throws InterruptedException { System.out.println("=========Test with CountDownLatch====="); CountDownLatch latch = new CountDownLatch(2); CountDownLatchWorker worker1 = new CountDownLatchWorker("worker1", latch); CountDownLatchWorker worker2 = new CountDownLatchWorker("worker2", latch); worker1.start(); worker2.start(); // 主线程阻塞等待 latch.await(); doSuperWork(); }
- 使用JDK并发包CyclicBarrier,类似于
CountDownLatch
,不同的是await()
方法每被调用一次,计数便会减少1,并阻塞当前线程。当计数减至0时,阻塞解除,所有在此CyclicBarrier
上阻塞的线程开始运行。 示例:public static void cyclicBarrierDemo() throws InterruptedException, BrokenBarrierException { System.out.println("=========Test with CyclicBarrier====="); CyclicBarrier cb = new CyclicBarrier(2, new Runnable() { public void run() { new ThreadWaitDemo().doSuperWork(); } }); ExecutorService executor = Executors.newFixedThreadPool(2); CyclicBarrierWorker worker1 = new CyclicBarrierWorker("worker1", cb); CyclicBarrierWorker worker2 = new CyclicBarrierWorker("worker2", cb); executor.execute(worker1); executor.execute(worker2); executor.shutdown(); }
- 使用JDK并发包中的Executors框架,通过
ExecutorService
的invokeAll
方法批量执行多个线程,在invokeAll
方法结束之后,再执行主线程其他业务逻辑。 示例:public static void callableDemo() throws InterruptedException { System.out.println("=========Test with Callable====="); List<Callable<Integer>> callList = new ArrayList<>(); ExecutorService exec = Executors.newFixedThreadPool(2); callList.add(new Callable<Integer>() { public Integer call() throws Exception { System.out.println("Sub Worker worker1 do work begin at " + sdf.format(new Date())); new ThreadWaitDemo().doSomeWork(); System.out.println("Sub Worker worker1 do work complete at " + sdf.format(new Date())); return 0; } }); callList.add(new Callable<Integer>() { public Integer call() throws Exception { System.out.println("Sub Worker worker2 do work begin at " + sdf.format(new Date())); new ThreadWaitDemo().doSomeWork(); System.out.println("Sub Worker worker2 do work complete at " + sdf.format(new Date())); return 0; } }); exec.invokeAll(callList); exec.shutdown(); doSuperWork(); }
- 轮询线程状态:主线程创建一个线程列表,将每个子线程保存到列表中,然后定期轮询列表中子线程状态,当所有线程都完成之后,再执行主线程逻辑。
简单写出线程同步的方法(java)
/**
* Java线程:线程的同步
*
* @author leizhimin 2009-11-4 11:23:32
*/
public class Test {
public static void main(String[] args) {
User u = new User("张三", 100);
MyThread t1 = new MyThread("线程A", u, 20);
MyThread t2 = new MyThread("线程B", u, -60);
MyThread t3 = new MyThread("线程C", u, -80);
MyThread t4 = new MyThread("线程D", u, -30);
MyThread t5 = new MyThread("线程E", u, 32);
MyThread t6 = new MyThread("线程F", u, 21);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyThread extends Thread {
private User u;
private int y = 0;
MyThread(String name, User u, int y) {
super(name);
this.u = u;
this.y = y;
}
public void run() {
u.oper(y);
}
}
class User {
private String code;
private int cash;
User(String code, int cash) {
this.code = code;
this.cash = cash;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 业务方法
* @param x 添加x万元
*/
public synchronized void oper(int x) {
try {
Thread.sleep(10L);
this.cash += x;
System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash);
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "User{" +
"code='" + code + '\'' +
", cash=" + cash + '}';
}
}
输出结果:
线程A运行结束,增加“20”,当前用户账户余额为:120
线程F运行结束,增加"21",当前用户账户余额为:141
线程E运行结束,增加"32",当前用户账户余额为:173
线程C运行结束,增加"-80",当前用户账户余额为:93
线程B运行结束,增加"-60",当前用户账户余额为:33
线程D运行结束,增加"-30",当前用户账户余额为:3
Process finished with exit code 0
反面教材(不同步的情况):
去掉oper(int x)
方法的synchronized
修饰符,运行结果如下:
线程A运行结束,增加"20",当前用户账户余额为:61
线程D运行结束,增加"-30",当前用户账户余额为:63
线程B运行结束,增加"-60",当前用户账户余额为:3
线程F运行结束,增加"21",当前用户账户余额为:61
线程E运行结束,增加"32",当前用户账户余额为:93
线程C运行结束,增加"-80",当前用户账户余额为:61
Process finished with exit code 0
很显然,上面的结果是错误的,导致错误的原因是多个线程并发访问了竞争资源u,并对u的属性做了改动。可见同步的重要性。
注意: 通过前文可知,线程退出同步方法时将释放掉方法所属对象的锁,但还应该注意的是,同步方法中还可以使用特定的方法对线程进行调度。这些方法来自于java.lang.Object
类。
void notify()
:唤醒在此对象监视器上等待的单个线程。void notifyAll()
:唤醒在此对象监视器上等待的所有线程。void wait()
:导致当前的线程等待,直到其他线程调用此对象的notify()
方法或notifyAll()
方法。void wait(long timeout)
:导致当前的线程等待,直到其他线程调用此对象的notify()
方法或notifyAll()
方法,或者超过指定的时间量。void wait(long timeout, int nanos)
:导致当前的线程等待,直到其他线程调用此对象的notify()
方法或notifyAll()
方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
Java如何实现多线程同步?
- 解决方案-1:设置3把锁,然后把锁们应用到所有线程中(涉及到
synchronized
、wait
、notify
等,嫌麻烦,略)。 - 解决方案-2:设置3个全局共享的信号标记(信号灯)+ 3子线程分别占用标记1、2、3 + 主线程轮询/等待(简洁明快,推荐)。
static boolean t1_done = false;
static boolean t2_done = false;
static boolean t3_done = false;
// t1------run() { ............ ; t1_done = true; }
// t2、3: 同理,略
main() {
// 启动t1;
// 启动t2;
// 启动t3;
// 轮询 or 等待
while (true)
if (t1_done && t2_done && t3_done) break;
else Thread.yield();
// 或 Thread.sleep(xxxx) ----若子线程运行超过100ms以上,应予考虑
// 轮询结束,主线程继续工作
}
have fun
Java线程同步的方法
等待唤醒机制
wait()
:让线程等待。将线程存储到一个线程池中。notify()
:唤醒被等待的线程。通常都唤醒线程池中的第一个。让被唤醒的线程处于临时阻塞状态。notifyAll()
:唤醒所有的等待线程。将线程池中的所有线程都唤醒,让它们从冻结状态转到临时阻塞状态。 这三个方法用于操作线程,可是定义在了Object
类中,为什么呢? 因为,这三个方法在使用时,都需要定义在同步中,要明确这些方法所操作的线程所属于锁。 简单说,在A锁被wait
的线程,只能被A锁的notify
方法唤醒。 所以必须要表示wait
、notify
方法所属的锁对象,而锁对象可以是任意的对象。 可以被任意的对象调用的方法肯定定义在Object
类中。 注意: 等待唤醒机制,通常都用在同步中,因为需要锁的支持。而且必须要明确wait
、notify
所作用的锁对象。
JDK1.5后的锁
在JDK1.5版本之后,出现了一些新的特性,将原来的线程进行了改良。
在java.util.concurrent.locks
包中提供了一个接口Lock
,替代了synchronized
。
synchronized
:使用的是锁操作是隐式的。Lock
接口:使用的锁操作是显式的。 由两个方法来完成:lock()
:获取锁。unlock()
:释放锁。 还有一个对象Condition
。 该对象的出现替代了Object
中的wait
、notify
、notifyAll
这些操作监视器的方法。 替代后的方式:await
、signal
、signalAll
。
java中线程同步的几种方法
线程同步主要有以下几种方法(示例中是实现计数的功能):
- 同步方法:即使用
synchronized
关键字修饰方法。public synchronized void add(int c) { ... }
- 同步代码块:即有
synchronized
关键字修饰的语句块。public void addAndGet(int c) { synchronized (this) { count += c; } }
- 使用特殊域变量(volatile)实现线程同步:该方法不能保证绝对的同步。
private volatile int count = 0;
- 使用锁实现线程同步:
private Lock lock = new ReentrantLock(); public void add(int c) { lock.lock(); // 上锁 try { count += c; } finally { lock.unlock(); // 解锁 } }
- 使用原子变量实现线程同步:在
java.util.concurrent.atomic
包中提供了创建原子类型变量的工具类。private AtomicInteger count = new AtomicInteger(1); public void add(int c) { count.addAndGet(c); }
- 使用局部变量实现线程同步:如果使用
ThreadLocal
管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。ThreadLocal
类的常用方法:new ThreadLocal<T>()
:创建一个线程本地变量。get()
:返回此线程局部变量的当前线程副本中的值。initialValue()
:返回此线程局部变量的当前线程的"初始值"。set(T value)
:将此线程局部变量的当前线程副本中的值设置为value
。 示例代码:
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 1; } }; public void add(int c) { count.set(count.get() + c); }
- 使用阻塞队列实现:例如
LinkedBlockingQueue
,具体使用可百度LinkedBlockingQueue
的用法或查看Java文档。