什么是同步

  • 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条线程访问,一条线程在执行一个循环的过程中被中断,下一个线程则出现错误
  • 因此,线程任务中可能引起错误的地方应当被一次执行完毕

同步代码块

  • 用同步代码块改写上面的代码
package testpack;

public class Test1  {
public static void main(String[] args){
System.out.println("现在是主线程: "+Thread.currentThread());
System.out.println("下面新建两个线程");
A a=new A(500);
new Thread(a,"线程A").start();
new Thread(a,"线程B").start();
new Thread(a,"线程C").start();
}
}
class A implements Runnable{
private int tickets;
A (int tick){
tickets=tick;
}
private Object obj=new Object(); //同步监视器
public void run() {
synchronized(obj){ //同步代码块
for (;tickets>0;tickets--) {
System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
try{
Thread.sleep(1); //让当前线程暂停1毫秒,其他线程也不能执行该同步代码块
}catch(InterruptedException ex){
ex.printStackTrace();
}
if (tickets==1){
System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
}
}
} }
}
  • 同步监视器,就是一个普通的对象,就像一把锁,只有获得了同步监视器的线程才能执行同步代码块。
  • 同步代码块执行一次完毕后,将会释放锁,接下来是这条线程拿到同步锁,还是其他其他线程,则不一定,根据线程调度而定,但是在同步代码块执行过程中,不会被中断,一个同步任务会被一次执行完毕

同步方法

  • 在同步代码块中,synchonized关键字在run()方法内部,修饰的是一段代码,也可以用来修饰run()方法,也就是同步方法
  • synchronized不只可以修饰run()方法,还可以修饰其他方法,只要是需要一次同步完成的任务,然后再在run()方法中被调用
  • 同步方法中有一个隐式的同步监视器,就是this,也就是调用run()方法(或同步方法)的这个对象
  • 还是上面的实例,用同步方法改写
package testpack;

public class Test1  {
public static void main(String[] args){
System.out.println("现在是主线程: "+Thread.currentThread());
System.out.println("下面新建两个线程");
A a=new A(500);
new Thread(a,"线程A").start();
new Thread(a,"线程B").start();
new Thread(a,"线程C").start();
}
}
class A implements Runnable{
private int tickets;
A (int tick){
tickets=tick;
}
public synchronized void run() { //同步方法
for (;tickets>0;tickets--) {
System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
try{
Thread.sleep(1);
}catch(InterruptedException ex){
ex.printStackTrace();
}
if (tickets==1){
System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
}
} }
}

释放同步监视器

  • 当前线程的同步任务(同步方法、同步代码块)执行完毕
  • 在同步任务中,遇到break、return,终止了同步任务
  • 在同步任务中,出现Error、Exception等,导致同步任务结束
  • 在同步任务中,执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器
  • 不会释放同步监视器的情况:
    • 同步任务中,调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行
    • 同步任务中,其他线程调用了该线程的suspend()方法将该线程挂起

同步锁

  • 除了可以用new Object()和this作同步监视器往外,还可以定义专门的同步锁,且功能更加强
  • Lock接口
    • ReentrantLock实现类
  • ReadWriteLock
    • ReentrantReadWriteLock实现类
    • ReentrantReadWriteLock.ReadLock
    • ReentrantReadWriteLock.WriteLock
  • StampedLock
  • 示例:用ReentrantLock改写上面的代码
package testpack;

import java.util.concurrent.locks.ReentrantLock;

public class Test1  {
public static void main(String[] args){
System.out.println("现在是主线程: "+Thread.currentThread());
System.out.println("下面新建两个线程");
A a=new A(50);
new Thread(a,"线程A").start();
new Thread(a,"线程B").start();
new Thread(a,"线程C").start();
}
}
class A implements Runnable{
private final ReentrantLock lock=new ReentrantLock(); //定义一个同步锁
private int tickets;
A (int tick){
tickets=tick;
}
public void run() {
lock.lock(); //加锁
for (;tickets>0;tickets--) {
System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
if (tickets==1){
System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
}
}
lock.unlock(); //释放锁
}
}

死锁

  • 两个线程各拿一把锁,下一步运行都需要对方手里那把锁,但都拿不到,则造成死锁,程序不能继续执行
package testpack;
public class Test1 {
public static void main(String[] args){
DeadLock dl=new DeadLock();
new Thread(dl).start();
dl.init();
}
}
class DeadLock implements Runnable {
A a=new A();
B b=new B();
public void init(){
a.a1(b);
System.out.println("进入主线程");
}
public void run(){
b.b1(a);
System.out.println("进入子线程");
}
}
class A {
public synchronized void a1(B b){
System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,正在执行a1()");
try{
Thread.sleep(10);
}catch(InterruptedException ex){
ex.printStackTrace();
}
System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,准备调用b2()");
b.b2(); //b2方法是同步方法,调用该方法要对调用的对象b加锁
}
public synchronized void a2(){
System.out.println("这是a2()方法");
}
}
class B{ public synchronized void b1(A a){
System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,正在执行b1()");
try{
Thread.sleep(10);
}catch(InterruptedException ex){
ex.printStackTrace();
}
System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,准备调用a2()");
a.a2(); //a2方法是同步方法,调用该方法要对调用的对象a加锁
} public synchronized void b2(){
System.out.println("这是b2()方法");
}
}
  • 上面在调用a.a2()和b.b2()方法时,分别要对a对象和b对象加锁,但这时,a、b对象的锁都在对方手里,造成两个线程阻塞

其他

  • 可变类的线程安全是以降低程序的运行效率为代价的
  • 不要对线程安全类的所有方法都进行同步,只对那些改变共享资源的方法进行同步
  • 如果一个类有单线程和多线程运行环境,那么应该提供两种版本,就是StringBuilder(单线程)和StringBuffer(多线程)一样

0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁的更多相关文章

  1. 线程同步 synchronized 同步代码块 同步方法 同步锁

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  2. 0023 Java学习笔记-面向对象-初始化代码块

    初始化代码块 在18篇-类的基本要素中说到,类的三大成员:成员变量.构造方法.方法,初始化代码块是类的第4个成员 初始化块用于对类或者对象的初始化, 一个类的初始化块可以有0-多个,按先后顺序执行 跟 ...

  3. Java学习笔记 - 类方法与代码块的执行顺序

    类的初始化顺序 使用一个简单的父子类例子来做示范,代码执行顺序在代码后有标注. class Parent { public static String p_StaticField = "父类 ...

  4. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  5. Java学习笔记-多线程-创建线程的方式

    创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...

  6. 线程的同步机制:同步代码块&同步方法

    解决存在的线程安全问题:打印车票时出现重票,错票 使用同步代码块的解决方案 TestWindow2 package com.aff.thread; /* 使用实现Runnable接口的方式,售票 存在 ...

  7. java学习笔记 --- 多线程(线程安全问题——同步代码块)

    1.导致出现安全问题的原因: A:是否是多线程环境 B:是否有共享数据 C:是否有多条语句操作共享数据 2.解决线程安全问题方法: 同步代码块: synchronized(对象){ 需要同步的代码; ...

  8. 0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题

    调用同步锁的wait().notify().notifyAll()进行线程通信 看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同 ...

  9. JAVA学习笔记 -- 多线程之共享资源

    在多线程程序执行过程中,可能会涉及到两个或者多个线程试图同一时候訪问同一个资源.为了防止这样的情况的发生,必须在线程使用共享资源时给资源"上锁",以阻挡其他线程的訪问. 而这样的机 ...

随机推荐

  1. ABP源码分析十一:Timing

    Timing这个简单实用的功能主要用于以统一的方式表示时间.因为ABP中有大量的module,还支持自定义module,所以将时间统一表示为local时间(默认)或utc时间是必要的. IClockP ...

  2. EMC与地之重新认识地

    记得在Mark的培训中,他手上拿了一个无线鼠标,然后问了一个很有意思的问题:“这个无线鼠标的地在哪里?同样,我们的手机没有和任何大地有接 触,那么这个地又在哪里呢?”这个问题确实很有意思,也确实让人很 ...

  3. [Java面经] 关于面试的二三事.

    今天终于闲下来了, 那么也好总结下这几天面试的经历.四天的时间一共面了七家, 有一家是自己推迟了没有去.声明:如若转载请注明出处:http://www.cnblogs.com/wang-meng/p/ ...

  4. 原生Ajax总结

    HTTP协议 传统的请求和Ajax请求 Ajax定义 Asynchronous JavaScript and XML. Ajax异步的,JavaScript程序希望与服务器直接通信而不需要重新加载页面 ...

  5. Android 开发环境搭建以及工具(不断更新)

    学习android需要学习的编程知识 https://wiki.cyanogenmod.org/w/Doc:_Development_Resources 从http://source.android. ...

  6. ORACLE 11gR2 DG(Physical Standby)日常维护01

    环境:RHEL 6.4 + Oracle 11.2.0.4 一.主备手工切换 1.1 主库,切换成备库并启动到mount 1.2 备库,切换成主库并启动到open 1.3 新的备库启动日志应用 二.重 ...

  7. Python(四)装饰器、迭代器&生成器、re正则表达式、字符串格式化

    本章内容: 装饰器 迭代器 & 生成器 re 正则表达式 字符串格式化 装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解 ...

  8. 嵌入式服务器jetty,让你更快开发web

    概述 jetty是什么? jetty是轻量级的web服务器和servlet引擎. 它的最大特点是:可以很方便的作为嵌入式服务器. 它是eclipse的一个开源项目.不用怀疑,就是你常用的那个eclip ...

  9. domReady的实现

    我们都知道JQ的 $(document).ready(fn) 方法.可以在页面准备就绪后才执行脚本,该方法相比传统的window.onload 事件,它的优势体现于onload事件是需要等到页面中所有 ...

  10. 多线程中的volatile和伪共享

      伪共享 false sharing,顾名思义,“伪共享”就是“其实不是共享”.那什么是“共享”?多CPU同时访问同一块内存区域就是“共享”,就会产生冲突,需要控制协议来协调访问.会引起“共享”的最 ...