一、多线程的创建
方式一:继承Thread类 略
方式二:实现Runnable接口
class ThreadRun implements Runnable{
@overwrite
public void run(){
......
}
}
ThreadRun run1 = new ThreadRun();
new Thread(run1).start();
方式三:实现Callable接口
class ThreadCall implements Callable{
@overwrite
public Object call() throws Exception{
......
}
}
Future future = new FutureTask(new ThreadCall());
new Thread(future).start();
方式四:线程池创建
ExecutorService pool = Executors.newFixedThreadPool(5)
pool.execute(Runnable run);
二、线程同步
在并发程序环境下,当多个线程对同一个资源进行修改的时候若是不加以控制则会出现错误。比如此时存在共享变量i=0,当5个线程同时对其进行加1操作,最后i的结果很可能不是5.
在Java中,使用同一个锁的线程才会同步,在多线程环境下,我们应该关心锁的范围,避免锁的范围不够(该同步的线程不同步)、或范围过大(不该同步的线程跟着同步,效率太低).
2.1 synchronized
同步方法
public synchronized void add(){
i++;
}
同步代码块
public void add(){
synchronized(this){
i++;
}
}
注意:同步方法的锁就是调用该方法的对象,而同步代码块的锁可以自定义,可以用该类的Class充当锁,这样该对象的所有实例在执行该代码块的时候都会同步。
2.2 lock锁
lock是一个接口,它常用的具体实现类有:ReentrantLock、ReentrantReadWriteLock、StampedLock
我们通过一个生产者问题来理解他
\\该接口主要提供锁,使得生产者消费者使用的是同一把锁
public interface GoodsSyn {
static Lock lock = new ReentrantLock();
static Condition consumerCon = lock.newCondition();
static Condition producerCon = lock.newCondition();
}
\\商品
public class Goods {
int num = 0;
int max = 10;
public Goods() {
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
\\生产者
public class Producer implements GoodsSyn{
Goods goods;
public Producer() {
}
public Producer(Goods goods) {
this.goods = goods;
}
public void produce(){
GoodsSyn.lock.lock();
while(goods.num == goods.max){
try {
while(goods.num == goods.max) {
GoodsSyn.producerCon.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ "正在生产第"+ (++goods.num)+"个商品");
GoodsSyn.consumerCon.signal();
GoodsSyn.lock.unlock();
}
}
\\消费者
public class Consumer implements GoodsSyn{
Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
public void consume(){
GoodsSyn.lock.lock();
try {
while(goods.num == 0) {
System.out.println(Thread.currentThread().getName()+"被阻塞");
GoodsSyn.consumerCon.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在消费第"+ (goods.num--)+"个商品");
GoodsSyn.producerCon.signal();
GoodsSyn.lock.unlock();
}
}
二者区别:
- 同步代码块自动解锁,lock需要手动释放
- lock的效率要远大于同步代码块
- lock提供更多样的api
- 一个lock可以new多个condition,使得一个lock对应多个等待队列,而一个同步代码块的锁只能对应一个等待队列。
三、线程通信
首先我们应该记住,在java中,线程通信是在锁的范围内才能使用的
3.1 wait()、signa()
这一类的方法属于原生java提供给我们的线程通信的方法,只能在同步方法,或者同步代码块中使用,当一个线程执行wait()方法后,必须等待该锁的其它线程执行signal()、或signalAll()才能被唤醒。
注意:在同步代码块中,应该由同步监视器(锁)来执行wait()或signal()方法
3.2 condition
在我们上面的生产者消费者问题中,以及展示了condition是如何使用的了。
简而言之,condition更适合在开发中使用,因为condition更加灵活更加精准。一个lock锁可以对应多个condition,而一个condition对应一个等待队列,所以一个lock锁对应多个等待队列。