目录
线程的同步方式(也叫同步锁)
- 方式一: synchronized 代码块
synchronized(资源对象){// 实现锁的对象资源
// 需要统一执行的原子资源
// 这段内容是不可分割的
}
- synchronized后面的资源对象,最好是线程需要竞争的唯一的
- 内部代码块中执行的内容是统一整体当结束代码块时候锁被释放掉
public class TestSynchronized{
public static void main(Stirng[] args){
ThreadOne one = new ThreadOne();
Thread th = new Thread(one);
Thread th1 = new Thread(one);
th.start();
th.start();
}
}
// 资源
class ThreadOne implements Runnable{
public void run(){
// 同步代码块上锁
synchronized(this){
for(int i=1;i < 100;i++){
System.out.print(Thread.currentThread().getName()+":"+i);
}
}
}
}
- 方式二: 同步方法
synchronized 返回值类型 方法名称(形参列表0){
// 当前对象(this)加锁
}
-
只有拥有对象互斥锁标记的线程,才能进入该对象的同步方法中
-
线程退出同步方法时,会释放相应的互斥锁标记
-
已知的java jdk内库中线程安全类有:StringBuffer、Vector、Hashtable、这几个类中的公开方法,均为synchronized修饰的方法
public class TestSynchronized{
public static void main(Stirng[] args){
ThreadOne one = new ThreadOne();
Thread th = new Thread(one);
Thread th1 = new Thread(one);
th.start();
th.start();
}
}
// 资源
class ThreadOne implements Runnable{
public void run(){
start();
}
// 方法的同步修饰
public synchronized void start(){
for(int i=1;i < 100;i++){
System.out.print(Thread.currentThread().getName()+":"+i);
}
}
}
同步规则
- 只有调用同步代码块的方法,或者同步方法时,才需要对象的锁标记
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用
加锁的场景
- 写(增、删、改)操作时候加锁
- 读操作时候,不加锁
死锁
- 当第一个线程拥有对象A的锁标记,并且等待B对象的锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁问题。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有饿锁标记,由此可能造成死锁问题。
常见的死锁具体问题
- 生产者与消费者问题
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区取走产品,也不允许生产者向一个满的缓冲区中放入产品
线程通信
线程通信时解决思索地有效方法
- 等待
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
- 通知
- public finall void notify()
- public final void notifyAll()
- 必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响
以下是个简单的案例
也就是李敢敢(丈夫)与赵岩岩(妻子) 银行卡子母卡同时取钱的案例
public class TestWaitNotify {
public static void main(String[] args) {
//临界资源,被共享的对象
//临界资源对象只有一把锁
Account acc =new Account("6002","1234",2000);
//两个线程对象 共享同一银行卡资源对象。
//给定任务后,第二个参数是对线程自定义命名
Thread husband = new Thread(new Husband(acc),"丈夫");
Thread wife = new Thread(new Wife(acc),"妻子");
// 启动线程
husband.start();
wife.start();
}
}
class A extends Thread{
}
// 模拟现实世界的子母卡
class Husband implements Runnable{
Account acc;
public Husband(Account acc) {
this.acc = acc;
}
public void run() {
this.acc.withdrawal("6002","1234",1200);//整体式原子操作
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
public void run() {
this.acc.withdrawal("6002","1234",1200);//整体式原子操作
}
}
class Account{
String cardNO;// 卡号
String password;// 密码
double balance; // 余额
public Account(String cardNO, String password, double balance) {
super();
this.cardNO = cardNO;
this.password = password;
this.balance = balance;
}
// 取款操作(整体是个原子操作,从插卡开始验证,到取款成功的一系列步骤,不可以缺少或者打乱)
public synchronized void withdrawal(String no,String pwd,double money) {
// 等待! --->阻塞状态
System.out.println(Thread.currentThread().getName()+"正在读卡...");
if(no.equals(this.cardNO) && pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功...");
if(money <= this.balance) {
try {
Thread.sleep(1000);//模拟现实世界ATM机器查钱
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.balance = this.balance - money;
System.out.println(Thread.currentThread().getName()+"取款成功!当前余额为:"+this.balance);
}else {
System.out.println(Thread.currentThread().getName()+"当前卡内余额不足!");
}
}else {
System.out.println(Thread.currentThread().getName()+"卡号或密码错误!");
}
}
}
高级多线程
线程池的原理
- 现有问题:
- 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出
- 频繁的创建及销毁线程会增加虚拟机的回收频率、资源开销、造成程序性能下降
所以就引入线程池来解决这个问题
- 线程池:
- 线程容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建和销毁
将任务提交给线程池,有线程池分配线程、运行任务,并在当前任务结束后服用线程
- java.util.concurrent 所有的线程池类的祖先
并发编程中很常用的实用工具类
- Executor: 线程池的顶级接口
- ExecutorService:线程池接口(常用的更丰富也是个接口),可通过submit()(提交任务代码)提交一个Runnable任务用于执行,并返回一个标识该任务的Future(结果)
- Executors工厂类:通过此类可以获得一个线程池
- 通过static ExecutorService new FixedThreadPool(int nThreads) 获取固定数量的线程池。参数:线程池中的线程的数量
- 通过newCachedThreadPool() 获得动态数量的线程池,如果不够则创建新的,没有上限
public class TestThreadPool {
public static void main(String[] args) {
// 利用线程池的工厂方法来生产对象 参数3代表创建三个线程
ExecutorService es = Executors.newFixedThreadPool(3) ;//手动限定线程池里的线程数量。
MyTask a = new MyTask();
//2.将任务提交到线程池,由线程池调度、执行
es.submit(a);
es.submit(a);
es.submit(a);
es.submit(a);
es.submit(a);
es.submit(a);
}
}
// 线程任务
class MyTask implements Runnable{
public void run() {
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class TestThreadPool {
public static void main(String[] args) {
// 利用线程池的工厂方法来生产对象 参数3代表创建三个线程
ExecutorService es = Executors.newCachedThreadPool();//自动扩充线程池中线程数量
MyTask a = new MyTask();
//2.将任务提交到线程池,由线程池调度、执行
es.submit(a);
es.submit(a);
es.submit(a);
es.submit(a);
es.submit(a);
es.submit(a);
}
}
// 线程任务
class MyTask implements Runnable{
public void run() {
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
newCachedThreadPool() 与 newFixedThreadPool(3) 的对比
- newCachedThreadPool()是动态创建线程,不用对程序进行线程创建数量的预估,而且用户体检感较好,但是最大的诟病是线程足够多时会内存溢出
- newFixedThreadPool()给与评估过后的线程数量,可能会在用户过多时候慢些,也可通过维护服务器时修改线程数量等方法增加扩充。
接口Callable
- 类似于Runnable,两者都是为了哪些实例可能被另一个线程执行的类设计的,但是Runnable没有返回结果
public interface Callable<V>{
public V call() throws Exception;
}
- JDK5加入的,与Runnable接口类似,实现之后代表一个线程任务
- Callable具有泛型返回值、可以声明异常
public class TestCallable {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService es = Executors.newFixedThreadPool(3);
MyTask1 task = new MyTask1();
// 将任务放入线程池
es.submit(task);
}
}
class MyTask1 implements Callable<Integer>{
public Integer call() throws Exception{
for (int i = 0; i < 100; i++) {
if(i == 30) {
Thread.sleep((int)(Math.random()*1000));
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
}
Future接口
- 异步接收ExecutorService.submit()所返回的状态结果,当中包含了call()返回值
- 方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
示例:两个线程,并发计算1 ~ 50、51 ~ 100的和,在进行汇总统计
public class TestFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
MyCall mc = new MyCall();
MyCall2 mc2 = new MyCall2();
//通过submit执行提交的任务,Future接受返回的结果
Future<Integer> result = es.submit(mc);
Future<Integer> result1 = es.submit(mc2);
//通过Future的get方法,获得线程执行完毕后的结果.
Integer value = result.get();
Integer value2 = result1.get();
System.out.println(value + value2);
}
}
//计算1~50的和
class MyCall implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Integer sum = 0;
for(int i = 1;i<=50;i++) {
sum = sum + i;
}
return sum;
}
}
//计算51~100的和
class MyCall2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Integer sum = 0;
for(int i =51;i<=100;i++) {
sum = sum + i;
}
return sum;
}
}
扩充
- 同步
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
- 单条执行路径
- 异步
- 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行