当多个线程访问一个对象时,有可能会发生污读,即读取到未及时更新的数据,这个时候就须要线程同步。html
线程同步:java
即当有一个线程在对内存进行操做时,其余线程都不能够对这个内存地址进行操做,直到该线程完成操做, 其余线程才能对该内存地址进行操做,而其余线程又处于等待状态,实现线程同步的方法有不少,临界区对象就是其中一种。git
在通常状况下,建立一个线程是不能提升程序的执行效率的,因此要建立多个线程。可是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,因为CPU时间调度上的问题,写入数据会被屡次的覆盖,因此就要使线程同步。算法
同步就是协同步调,按预约的前后次序进行运行。如:你说完,我再说。编程
“同”字从字面上容易理解为一块儿动做安全
其实不是,“同”字应是指协同、协助、互相配合。多线程
如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到必定程度时要依靠B的某个结果,因而停下来,示意B运行;B依言执行,再将结果给A;A再继续操做。并发
所谓同步,就是在发出一个功能调用时,在没有获得结果以前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。可是通常而言,咱们在说同步、异步的时候,特指那些须要其余部件协做或者须要必定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息以前,这个函数不返回。当对方处理完毕之后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。异步
在多线程编程里面,一些敏感数据不容许被多个线程同时访问,此时就使用同步访问技术,保证数据在任什么时候刻,最多有一个线程访问,以保证数据的完整性。ide
因为同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程得到对象的排它锁,独占资源,其余线程必须等待,使用后释放锁便可能存在如下问题:
举个例子,一个售票口有10张票,当100我的同时去买时,每一个人都获取到了有100张票的数据,因此每一个人买了一张,致使最后剩下-90张票,线程不一样步就会致使这种结果。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有如下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其做用的范围是大括号{}括起来的代码,做用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其做用的范围是整个方法,做用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其做用的范围是整个静态方法,做用的对象是这个类的全部对象;
4. 修改一个类,其做用的范围是synchronized后面括号括起来的部分,做用主的对象是这个类的全部对象。
咱们写一个例子,使用线程不安全的List来看看效果
public class MyThread{ public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
能够看到,循环1000次,只存进去998个,重复执行,这个大小还会变化,因此是线程不安全的。
可使用synchronized把list加锁,就能保证每次都能插入进去。
public class MyThread{ public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
这样就可以保证线程安全。
也可使用JUC(java.util.concurrent
)包下的线程安全的列表CopyOnWriteArrayList,代码以下
import java.util.concurrent.CopyOnWriteArrayList; public class MyThread{ public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
使用CopyOnWriteArrayList就能够不须要synchronized关键字实现线程安全
查看源代码能够发现,CopyOnWriteArrayList实现了List<E>接口
而后再add方法中使用了synchronized来加锁,和咱们上面的操做方法一致
//CopyOnWriteArrayList中的add()方法 public boolean add(E e) { synchronized (lock) { Object[] es = getArray(); int len = es.length; es = Arrays.copyOf(es, len + 1); es[len] = e; setArray(es); return true; } }
所谓死锁,是指多个进程在运行过程当中因争夺资源而形成的一种僵局,当进程处于这种僵持状态时,若无外力做用,它们都将没法再向前推动。
死锁的条件
只要破坏后三个条件之一就能够避免死锁,可使用银行家算法等方法。
先写一个不使用锁的例子
import java.util.concurrent.locks.ReentrantLock; public class MyThread implements Runnable { public static void main(String[] args) { MyThread thread = new MyThread(); Thread thread1 = new Thread(thread); Thread thread2 = new Thread(thread); Thread thread3 = new Thread(thread); thread1.start(); thread2.start(); thread3.start(); } public static int tickets = 10; @Override public void run() { while (true) { if (tickets > 0) { System.out.println(tickets--); } else { break; } } } }
执行后发现顺序彻底是乱的
使用ReentrantLock(可重入锁)来把相关代码加锁,便可实现按顺序调用
import java.util.concurrent.locks.ReentrantLock; public class MyThread implements Runnable { public static void main(String[] args) { MyThread thread = new MyThread(); Thread thread1 = new Thread(thread); Thread thread2 = new Thread(thread); Thread thread3 = new Thread(thread); thread1.start(); thread2.start(); thread3.start(); } public static int tickets = 10; final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { try { lock.lock(); if (tickets > 0) { System.out.println(tickets--); } else { break; } } finally { lock.unlock(); } } } }
这样也能够实现线程同步。
Java提供的线程通讯方法
方法名 | 做用 |
---|---|
wait() | 表示线程一直等待,直到其余线程通知,与sleep不一样,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上全部调用wait()方法的线程,优先级别高的线程优先调度 |
均是0bject类的方法都,只能在同步方法或者同步代码块中使用,不然会抛出llegalMonitorStateException
首先定义一个生产者类
//生产者 class Producer extends Thread { SynContainer container; public Producer(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生产第" + i + "个"); container.push(new Product(i)); } } }
生产者不断往缓冲区添加产品,而后定义一个消费者类
//消费者 class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费第" + container.pop().id + "个"); try { Thread.sleep(500); } catch (InterruptedException ignored) { } } } }
消费者不断在缓冲区去除产品,这里添加一个sleep来模拟真实效果
最后定义缓冲区
//缓冲区 class SynContainer { //容器大小 Product[] products = new Product[10]; //计数器 int count = 0; //生产者放入产品 public synchronized void push(Product product) { //若是满了,通知消费者,生产者等待,不然放入产品 if (count == products.length) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } products[count++] = product; this.notifyAll(); } //消费者消费产品 public synchronized Product pop() { if (count == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); return products[--count]; } }
缓冲区的两个方法都是使用synchronized修饰,保证可以执行完整,而后根据容器大小来判断是否让生产者以及消费者线程等待
当容器中没有产品时,通知消费者等待,生产者线程开始,当产品满时,通知生产者等待,消费者线程开始。
最后补上产品类
//产品 class Product { //产品编号 int id; public Product(int id) { this.id = id; } }
类定义和上面相似,只不过在产品类中添加了一个信号量来区分是否有产品,不须要一个缓冲区
//生产者 class Producer extends Thread { Product product; public Producer(Product product) { this.product = product; } //生产 @Override public void run() { for (int i = 0; i < 10; i++) { this.product.push("产品" + i); } } } //消费者 class Consumer extends Thread { Product product; public Consumer(Product product) { this.product = product; } //消费 @Override public void run() { for (int i = 0; i < 10; i++) { this.product.pop(); } } } //产品 class Product { String product; boolean flag = true; //生产 public synchronized void push(String product) { if (!flag) { try { this.wait(); } catch (InterruptedException ignored) { } } System.out.println("生产了" + product); //通知消费 this.notifyAll(); this.product = product; this.flag = !this.flag; } //消费 public synchronized void pop() { if (flag) { try { this.wait(); } catch (InterruptedException ignored) { } } System.out.println("消费了" + this.product); //通知生产者 this.notifyAll(); this.flag = !this.flag; } }
这样也能够解决生产者和消费者问题
常常建立和销毁、使用量特别大的资源,好比并发状况下的线程,对性能影响很大。
思路:提早建立好多个线程,放入线程池中,使用时直接获取,使用完放回池中。能够避免频繁建立销毁、实现重复利用。相似生活中的公共交通工具。
JDK 5.0起提供了线程池相关API: ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
代码演示
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { //建立线程池 ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭链接 service.shutdown(); } } class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
这样就能够实现经过线程池来管理线程
Java多线程(上)http://www.javashuo.com/article/p-cjqayqvb-wr.html