1,线程安全

先看上一节程序,我们稍微改动一下:

//线程安全演示
//火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
class Ticket implements Runnable
{
private int tick = 16;//票的张数---16
public void run(){
while(true){
if(tick>0){
//这里的sleep(100)是这次程序要表的关键,只是个模拟而已
try{
Thread.sleep(100);
}catch(Exception e){ }
System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);
}
} }
public static void main(String[] args)
{
Ticket t = new Ticket(); Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t); t1.start();
t2.start();
t3.start();
t4.start();
}
}

其他之处没有改变,只是在判断还有票之后立即睡眠一段时间,这在现实中有可能发生的,线程A刚执行到这里,突然cpu切换到其他线程B中,而B线程也刚好执行到判断语句又被A线程抢夺cpu,此时A线程不用再判断,直接输出,并修改ticket的值,此时有可能使得tickte>0不再满足,但是下一次B线程执行时,也不去判断tickte的值,导致出错。看看上面的程序结果(不为一),数了一下,打印了20张票,还有-1,-2,0的 情况,这就是线程安全问题了。

问题原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完另一个线程就来执行,导致共享数据的错误。

解决办法:对多条操作共享数据的语句,只能让一个线程都执行过程中,其他线程也不能执行。即使其他线程拿到执行权!这就是java提供的同步代码块方案。其格式如下:

synchronized(对象){

需要同步的代码块

}

分析:上面的synchronized后面的对象如同锁,也叫同步监视器,持有锁的线程可以同步执行,没有持有锁的线程即使获取cpu的执行权,也不能进不去同步代码块。线程开始执行同步代码块必须先对同步监视器的锁定。

同步前提:

(1)必须有两个或者两个以上的线程。

(2)必须是多个线程使用同一个锁。

任何时刻只有一个线程获得对同步监视器的锁定,当同步代码块执行结束时,该线程会释放对同步监视器的锁定。

如何看哪些代码需要同步?看看共享数据参与运算的范围。if(tick>0),还有tick—;因此把上面程序改为如下:

//线程安全演示
//火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
class Ticket implements Runnable
{
private int tick = 16;//票的张数---16
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if(tick>0){
//这里的sleep(100)是这次程序要表的关键,只是个模拟而已
try{
Thread.sleep(10);
}catch(Exception e){ }
System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);
}
}
} }
public static void main(String[] args)
{
Ticket t = new Ticket(); Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t); t1.start();
t2.start();
t3.start();
t4.start();
}
}

此时运行就没有出现错误。结果就不展示了。

好处:解决线程安全问题

缺点:多线程需要判断锁,较为消耗资源

2,线程同步

再看下面的例子

/*
需求:银行有一个金库,有两个储户分别存入300元,都是分3次存入
目的:查看该程序是否有安全问题,如果有,如何解决?
如何找问题:
1,明确哪些代码是多线程运行代码
2,明确哪些是共享数据
3,明确哪些代码使用共享数据
*/ class Bank
{
//Object obj = new Object(); ------1
private int sum;
public synchronized void add(int n){ //synchronized可以放在此处作为同步函数
//synchronized(obj){ ------2
sum = sum + n;
try{
Thread.sleep(10);
}catch(Exception e){ }
System.out.println("sum = " + sum);
//} ------3
}
} class Cus implements Runnable
{
private Bank b = new Bank();
public void run(){
for(int x = 0; x < 3; x++){
b.add(100);
}
}
} class BankDemo
{
public static void main(String[] agrs){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
} }

上面代码如果不同步就会出现线程安全问题,但是同步不止刚才那种同步代码块,还有同步函数可以使用,本程序就是个例子。但是对于卖票那个程序来试试

//线程安全演示
//火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
class Ticket implements Runnable
{
private int tick = 160;//票的张数---160
//Object obj = new Object();
public synchronized void run(){
while(true){
//synchronized(obj){
if(tick>0){
//这里的sleep(1000)是这次程序要表的关键,只是个模拟而已
try{
Thread.sleep(100);
}catch(Exception e){ }
System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);
}
//}
} }
public static void main(String[] args)
{
Ticket t = new Ticket(); Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t); t1.start();
t2.start();
t3.start();
t4.start();
}
}

结果

其实这里面一直是线程0在执行,因为run方法是在同步,当0线程执行时别的线程都不能执行,这是因为同步的代码块范围不正确,只需要把需要同步的代码块封装为一个函数,便可以在run方法中调用这个函数即可。

//线程安全演示
//火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
class Ticket implements Runnable
{
private int tick = 160;//票的张数
public void run(){
while(true){
show();//此处调用show方法
} } public synchronized void show(){//将同步代码块封装起来
if(tick>0){
try{
Thread.sleep(100);
}catch(Exception e){ }
System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);
}
}
public static void main(String[] args)
{
Ticket t = new Ticket(); Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t); t1.start();
t2.start();
t3.start();
t4.start();
}
}

此时可以看到几个线程都启动了。那么有个疑问,同步代码块这种方法有个同步监视器来进行锁住。那么同步方法怎么实现呢?

其实函数都需要对象调用,每个函数都有一个对象引用,就是隐含的this,所以同步函数使用的锁就是this。

通过程序来验证:

使用两个线程来实现卖票,第一个线程在同步函数中,第二个线程在同步代码块中。都在执行卖票动作。

class Ticket implements Runnable
{
private int tick = 16;//票的张数
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag){
while(true){
synchronized(obj){
if(tick>0){
try{
Thread.sleep(100);
}catch(Exception e){ }
System.out.println(Thread.currentThread().getName() + "...code:" + tick--);
}
}
}
}
else{
while(true){
show();
}
}
} public synchronized void show(){
if(tick>0){
try{
Thread.sleep(100);
}catch(Exception e){ }
System.out.println(Thread.currentThread().getName() + "...show:" + tick--);
}
}
public static void main(String[] args)
{
Ticket t = new Ticket(); Thread t1 = new Thread(t);
Thread t2 = new Thread(t); t1.start();
try{
Thread.sleep(100);
}catch(Exception e){ }
t.flag = false;
t2.start(); }
}

结果如下

先来解释一下,这个程序的run()方法中有同步代码块,还有一个同步函数。我们的意思是想让t1先执行,在main函数中让其sleep是因为如果没有sleep的话,主线程可能没有分配给t1的cpu执行权直接执行下面的flag = false这句,导致同步代码块中的代码永远不能执行。加入sleep可以执行同步代码块的代码,也可以执行同步函数中的代码,但是看到结果,打印了0号票,这是不允许的,是什么造成了这种情况?

看看文章最前面的同步的前提,有两个,再次提出:

(1)必须有两个或者两个以上的线程。-----满足

(2)必须是多个线程使用同一个锁。-----不满足-----〉第一个锁是synchronized(obj)中的obj,还有一个同步函数,它的锁是this,就是调用它的对象。

把synchronized(obj)这句换为synchronized(this),就不会出现上述问题,所以这样满足第二个前提,说明同步函数使用的的确是this这个锁。

有个情况,如果同步函数使用static修饰,那么静态方法中的锁是谁呢?肯定不是this,因为静态方法没有this。静态进内存时没有本类对象,但是有本类的字节码对象,该对象的类型是class.因此,对于静态方法,他的同步监视器(也就是锁)就是类名.class。把synchronized(obj)这句换为synchronized(Ticket.class),就不会出现0号票了。

3,死锁

死锁原因:同步中嵌套同步,但是锁却不同步。

先修改上面的程序

class Ticket implements Runnable
{
private int tick = 1000;//票的张数
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag){
while(true){
synchronized(obj){
System.out.println("*****************");
show();
}
}
}
else{
while(true){
System.out.println("------------------");
show();
}
}
} public synchronized void show(){
synchronized(obj){
if(tick>0){
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "...code:" + tick--);
}
}
}
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(100);}catch(Exception e){}
t.flag = false;
t2.start(); }
}

上面的程序出现死锁,原因是同步代码块中锁是obj,且嵌套同步函数(其锁是this),同步函数的锁是this,而其中又嵌套同步代码块,其锁是obj。可以修改锁即可。比如把obj全部换为this。这样代码就不会出错。但上述代码的输出-----和***之前加入一句判断语句if(tick>0)不然票卖完了还在打印。

上面的例子是同步代码块中包含同步函数,同步函数中又包含同步代码块,下面再看一个只在同步代码块中出现的死锁的例子。

class Test implements Runnable
{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.locka){
System.out.println("if a");
synchronized(MyLock.lockb){
System.out.println("if b");
}
}
} else{
synchronized(MyLock.lockb){
System.out.println("else b");
synchronized(MyLock.locka){
System.out.println("else a");
}
}
}
}
} class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
} class DeadLockTest
{
public static void main(String[] agrs){ Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}

结合代码,在run()方法中,if语句中的同步代码块有嵌套,else也一样。他们也是这种情况:同步中嵌套同步,但是锁却不同步。

所以在以后的编程过程中一定要防止此类情况的发生。

注:这些代码和相关结论参见毕向东的java基础视频教程和李刚的《疯狂java讲义》,都非常不错!

java线程详解(二)的更多相关文章

  1. Java多线程详解(二)

    评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡 ...

  2. java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  3. Java线程详解----借鉴

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  4. 【转】Java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  5. java线程——详解Callable、Future和FutureTask

    回顾: 接上篇博客 java线程--三种创建线程的方式,这篇博客主要介绍第三种方式Callable和Future.比较继承Thread类和实现Runnable接口,接口更加灵活,使用更广泛.但这两种方 ...

  6. java线程详解(三)

    java线程间通信 首先看一段代码 class Res { String name; String sex; } class Input implements Runnable { private R ...

  7. 并发编程 || Java线程详解

    通用线程模型 在很多研发当中,实际应用是基于一个理论再进行优化的.所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解. ...

  8. java线程详解(一)

    1,相关概念简介 (1)进程:是一个正在执行的程序.每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元.用于分配空间. (2)线程:就是进程中一个独立的控制单元,线程在控制着 ...

  9. Java集合详解二

    前一篇我们已经讲过了Collectin是存放单值的最大接口, 可以看到Map接口和其主要之类的关系图如下: 快速访问 HashMap    HashTable  TreeMap  Map输出 那Map ...

随机推荐

  1. ios delegate你必须知道的事情

    在我们的class中设计delegate的时候,我们通常会有几个注意事项. 假设我的class叫做MyClass,那我们可能会有定义一个MyClassDelegate这个protocol当作我的del ...

  2. C#全角、半角转换

    全角:指一个字符占用两个标准字符位置:半角:指一字符占用一个标准的字符位置. using System; using System.Collections.Generic; using System. ...

  3. js动画性能提升笔记

    JavaScript动画的性能并不亚于CSS动画.因此,如果使用了现代的动画库,例如Velocity,那么动画引擎的性能将不再是app的瓶颈,构成瓶颈的只有代码. 网络性能相关 动画是浏览器运行中资源 ...

  4. java的InputStream和OutputStream的理解【转】

    1.在java中stream代表一种数据流(源),javaio的底层数据元,---(想像成水龙头)2.任何有能力产生数据流(源)的javaio对象就可以看作是一个InputStream对象既然它能产生 ...

  5. 学习打造自己的DEBUG_NEW

    学习范例http://www.cppblog.com/Robertxiao/archive/2012/11/05/194547.html 在使用MFC库开发程序时,我非常喜欢MFC框架中的内存泄漏诊断 ...

  6. 7.29 H5学习笔记

    常用实体字符小于号  < 大于号  > 和号  & 引号  " 撇号  &apos; 分  ¢ 镑  £ 日元  ¥ 欧元  € 小节  § 版权  © 注册  ...

  7. SQL镜像资料

    使用数据库镜像端点证书 (Transact-SQL):https://msdn.microsoft.com/zh-cn/library/ms191477.aspx 允许数据库镜像端点将证书用于入站连接 ...

  8. oracle 客户端重新安装遇到的问题

    前一阵,因为把ORACLE客户端的密码忘记了,在网上也找了好多方法,试着不重新安装找回密码,可是都行不通,没有办法重新装.安装时遇到一些问题,因为我是WIN7,64位系统,安装的oracle11g64 ...

  9. 英语语法 It all started the summer before second grade when our moving van pulled into her neighborhood

    It all started the summer before second grade when our moving van pulled into herneighborhood It all ...

  10. LR录制https协议报证书错误,导航已阻止

    使用IE浏览器录制https协议报证书错误,导航已阻止,修改如下配置文件: