我们对线程访问同一份资源的多个线程之间,来进行协调的这个东西,就是线程同步。
 
例子1:模拟了多个线程操作同一份资源,可能带来的问题:              
package com.cy.thread;

public class TestSync implements Runnable{
Timer timer = new Timer();
public static void main(String[] args) {
TestSync test = new TestSync();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
} @Override
public void run() {
timer.add(Thread.currentThread().getName());
} } class Timer{
private static int num = 0;
public void add(String name){
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+",你是第"+num+"个使用timer的线程");
}
}
两个线程访问的都是time对象;访问的都是time对象中的add方法;
但是console打印出来是:
 为什么会出现上述问题呢?
解释:
第一个线程访问time对象的add方法,将num++,num变为1了;然后睡眠了;
这时候第二个线程开始执行,访问的仍然是同一份对象timer,肯定也是同一个num,num原来是1,执行num++,num变为2了;然后开始睡眠;
第一个线程醒过来了,num这时候是2,打印:t1,你是第2个使用timer的线程
第二个线程醒过来,打印:t2,你是第2个使用timer的线程
问题出在,这个线程在执行add这个方法的过程之中,被另外一个线程打断了;
num++;和
System.out.println(name+",你是第"+num+"个使用timer的线程");
这句话本来应该作为一个原子性的输出;你不能在中间给我打断了;
例子中写Thread.sleep(1)给另外一个线程执行的机会,方便看到效果;
但是即便是不写sleep(1),你很有可能看见的是正确的结果,但是当这个程序执行起来的过程之中,它还是有可能出问题;
中间还是有可能被打断;
怎么解决呢?
在执行
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+",你是第"+num+"个使用timer的线程");
这句话的过程之中,请你把我当前的对象锁住就行了;

于是代码修改为:

class Timer{
private static int num = 0;
public void add(String name){
synchronized (this) {
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+",你是第"+num+"个使用timer的线程");
}
}
}
synchronized (this):
锁定当前对象;在执行synchronized 后面大括号之间语句的过程之中,一个线程执行的过程之中,不会被另外一个线程打断;
一个线程已经进入到synchronized (this){}这个锁定的区域里面了,你放心,不可能有另外一个线程也在这里边;
既然锁定当前对象time了,它里面的成员变量num跟着也就锁定了;
这就是锁的机制;(互斥锁)

上面的还有一种简便的写法是这样的:

package com.cy.thread;

public class TestSync implements Runnable{
Timer timer = new Timer();
public static void main(String[] args) {
TestSync test = new TestSync();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
} @Override
public void run() {
timer.add(Thread.currentThread().getName());
} } class Timer{
private static int num = 0;
public synchronized void add(String name){
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+",你是第"+num+"个使用timer的线程");
}
}
public synchronized void add(String name)相当于在执行这个方法的过程之中,锁定当前对象,锁定this;
是在执行这个方法的过程之中,当前对象被锁定;
分析现在的执行过程:
t1开始执行,调用add方法,num++,num变为1了,然后sleep,它睡着了没关系,睡着的时候还抱着这把锁呢。
别人进不来,你必须得等它执行完了,你才可以继续执行;
必须等它醒了,把
System.out.println(name+",你是第"+num+"个使用timer的线程");
这句话执行完了,另外一个线程才有执行的机会。 synchronized这个关键字会锁定某一段代码,它的内部的含义是当执行这段代码的过程之中,锁定当前对象;
另外一个人如果也想访问我这对象的话,他只能等着,等我这段代码执行完了,我的锁自然而然也就打开了。
你才能进的来。你才能访问我这对象;

二、当我们讲了锁之后,多线程还会带来其他的问题,一个典型的问题就是死锁。
 死锁的原理:
死锁的原理:
当某一个线程执行的过程之中,需要锁定某个对象A,它已经把这个对象A锁住了;它还需要锁定另外的一个对象B,才能把整个操作执行完。
接下来另外一个线程,他也需要锁定两个对象,他首先锁定的是B;
第一个线程,它锁定了A,然后再拥有B对象的锁,它就能完成了;
第二个线程,他锁定了B,如果再能拥有A对象的锁,他就能完成了;
第一个线程锁定了A,但是执行不下去了,它等待的东西B被其他线程锁住了;
第二线程也执行不下去了,他等的东西A被另外的线程锁住了;
就是死锁了。
用程序来模拟:
package com.cy.thread;

public class TestDeadLock implements Runnable{
public int flag = 1;
static Object o1 = new Object(), o2 = new Object(); @Override
public void run() {
System.out.println("flag= " + flag);
if(flag == 1){
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} synchronized (o2) {
System.out.println("1");
}
}
} if(flag == 0){
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} synchronized (o1) {
System.out.println("0");
}
}
}
} public static void main(String[] args) {
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
} }
程序一运行,打印
flag= 0
flag= 1
然后就死在那了...
解决线程死锁的问题:
你最好只锁住一个,不要锁住两,也就是把锁的粒度加粗一些;
锁定当前整个对象不就完了吗,干嘛锁定这个对象下面两个小对象啊。
把锁的粒度加粗一些,是办法之一,还有很多其他的办法...

三、生产者消费者问题                                  

package com.cy.thread;

public class ProducerConsumer {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(c).start();
}
} /**
* 馒头类
* id: 每一个馒头的id
*/
class WoTou{
int id; WoTou(int id){
this.id = id;
} @Override
public String toString() {
return "WoTou : " + id;
}
} /**
* 装馒头的篮子 用栈来模拟,先装进去的,最后拿出来
* 开始的时候装了0个馒头
* 一共能装arrWT.length = 6个馒头
*/
class SyncStack{
int index = 0;
WoTou[] arrWT = new WoTou[6]; /**
* 装馒头
*/
public synchronized void push(WoTou wt){
while(index == arrWT.length){
try {
/**
* 这里说的不是让当前对象wait;wait是指:
* 锁定在我当前对象上的这个线程,停止住。注意:
* wait方法只有你锁定了,锁定了我这个线程之后,才能wait,才有资格wait。 你要锁不住我这个对象就别谈wait这事。
* 如果这个方法不是synchronized的,调用wait立马出错。
* 而且一旦wait了,就死过去了,这个对象的锁就不再归我所有,只有在我醒过来的时候我才再找这把锁。
* (这是wait和sleep的巨大的区别)
*
* 一个线程访问我这个push方法的时候,它已经拿到我这个对象的锁了,
* 拿到对象锁的这个线程在执行的过程之中,它遇到一个事件(已经满了,不能再往里装馒头了)必须阻塞住,必须停止,
* 必须等清洁工把馒头清走了才能继续办事。所以目前只能等着。不能再生产了。
*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} /**
* wait方法和notify方法一般是一一对应的
* notify:叫醒一个现正正在在wait在我这个对象上的线程。
* 谁现在正在我的对象上等待,我就叫醒一个线程,让它继续执行。
* notifyAll:叫醒等待在这个对象上的多个线程
* 例子中只有两个线程,用notify是可以的。
*/
this.notify();
arrWT[index] = wt;
index ++;
} /**
* 吃馒头
*/
public synchronized WoTou pop(){
while(index==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} this.notify();
index --;
return arrWT[index];
}
} /**
* 生产者 一次生产20个馒头
*/
class Producer implements Runnable{
SyncStack ss = null;
Producer(SyncStack ss){
this.ss = ss;
} @Override
public void run() {
for(int i=0; i<20; i++){
WoTou wt = new WoTou(i);
ss.push(wt);
System.out.println("生产了: " + wt);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
} /**
* 消费者 一次吃20个馒头
*/
class Consumer implements Runnable{
SyncStack ss = null;
Consumer(SyncStack ss){
this.ss = ss;
} @Override
public void run() {
for(int i=0; i<20; i++){
WoTou wt = ss.pop();
System.out.println("消费了:" + wt); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} console打印:
生产了: WoTou : 0
消费了:WoTou : 0
生产了: WoTou : 1
消费了:WoTou : 1
生产了: WoTou : 2
消费了:WoTou : 2
生产了: WoTou : 3
消费了:WoTou : 3
生产了: WoTou : 4
消费了:WoTou : 4
生产了: WoTou : 5
消费了:WoTou : 5
生产了: WoTou : 6
消费了:WoTou : 6
消费了:WoTou : 7
生产了: WoTou : 7
生产了: WoTou : 8
消费了:WoTou : 8
生产了: WoTou : 9
消费了:WoTou : 9
生产了: WoTou : 10
消费了:WoTou : 10
生产了: WoTou : 11
消费了:WoTou : 11
生产了: WoTou : 12
消费了:WoTou : 12
消费了:WoTou : 13
生产了: WoTou : 13
生产了: WoTou : 14
消费了:WoTou : 14
生产了: WoTou : 15
消费了:WoTou : 15
生产了: WoTou : 16
消费了:WoTou : 16
生产了: WoTou : 17
消费了:WoTou : 17
生产了: WoTou : 18
消费了:WoTou : 18
消费了:WoTou : 19
生产了: WoTou : 19
 
 
 
 
 
 
 
 --------------------------

java多线程(2) 线程同步的更多相关文章

  1. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  2. Java多线程 3 线程同步

    在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系.可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题.现在就来学习多线程对数据访问的 ...

  3. Java多线程与线程同步

    六.多线程,线程,同步 ①概念: 并行:指两个或多个在时间同一时刻发生(同时发生) 并发:指两个或多个事件在同一时间段内发生 具体概念: 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多 ...

  4. java多线程:线程同步synchronized(不同步的问题、队列与锁),死锁的产生和解决

    0.不同步的问题 并发的线程不安全问题: 多个线程同时操作同一个对象,如果控制不好,就会产生问题,叫做线程不安全. 我们来看三个比较经典的案例来说明线程不安全的问题. 0.1 订票问题 例如前面说过的 ...

  5. Java多线程 | 02 | 线程同步机制

    同步机制简介 ​ 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.Java平台提供的线程同步机制包括: 锁,volatile关键字,final关键字,static关键字,以 ...

  6. Java多线程之线程同步【synchronized、Lock、volatitle】

    线程同步 线程同步:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多. ...

  7. java ->多线程_线程同步、死锁、等待唤醒机制

    线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. l  我们通过一个案例,演示线 ...

  8. java 多线程 day03 线程同步

    package com.czbk.thread; /** * Created by chengtao on 17/12/3. 线程安全问题: 线程安全出现 的根本原因: 1. 存在两个或者两个以上 的 ...

  9. Java多线程之线程的同步

    Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...

  10. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

随机推荐

  1. nodejs之log4js日志记录模块简单配置使用

    在我的一个node express项目中,使用了log4js来生成日志并且保存到文件里,生成的文件如下: 文件名字叫:access.log 如果在配置log4js的时候允许了同时存在多个备份log文件 ...

  2. mysql的sql编程

    sql编程 变量 变量分为两种:系统变量和自定义变量 系统变量 系统定义好的变量:大部分的时候用户根本不需要使用系统变量,系统变量时用来控制服务器的表现的,如:auto_commit. 查看系统变量 ...

  3. Java基础学习-Collection体系结构和迭代测试

    package Collection; import java.util.ArrayList; import java.util.Collection; import java.util.Iterat ...

  4. Oracle 对比两张表不一样 的数据

    闲来无事,更一片博客,前几天有一个项目中有一个需求,用户通过excel导入数据,由于是通用的导入,所以导入的列的类型都为varchar,所以需要建一张中间表,使其列都为varchar类型,然后通过存储 ...

  5. The capacitive screen technology - tadpole

  6. 关于微信中JS-SDK的接口验证过程详细说明

    最近在做微信的企业服务号,刚开始通过个人的测试平台进行开发,使用了自定义菜单,自定义菜单包含两个功能:1.扫一扫,通过扫描我们账单的二维码,绑定账户和账单的关系:2.打开我们系统的账单查询页面,查询账 ...

  7. 【SQL查询】正则表达式匹配字符串

    1. 元字符说明 元字符 含义 ^ 匹配输入字符串的开始位置. $ 匹配输入字符串的结尾位置. * 匹配前面的字符零次或多次. + 匹配前面的字符一次或多次. ? 匹配前面的字符零次或一次. . 匹配 ...

  8. jfreechart在jsp中画图方式

    这个问题一直困扰我好久,今天算是稍微找到一点解决思路了,在网上搜了好多列子,大部分的都是用servlet来实现画图,偶然找到一个列子用的是org.jfree.chart.servlet.Servlet ...

  9. Vim技能修炼教程(14) - 写个ex命令吧

    写个ex命令吧 我们第二节开始就写了语法高亮的插件.这一节,我们学习第二种插件的写法,就是写个我们自己的ex命令. 自定义ex命令的命令是:command,我们在~/.vim/下建立一个plugin目 ...

  10. java入门学习(2)—基本数据类型

    1.变量:定义变量:[数据类型] 变量名 = 赋值(这样定义的变量一般属于局部变量,放置在栈内存中): 2.标识符:可以有字母(可以使任意文字),数字,下划线,$等组成:但是不能以数字开头,不能是保留 ...