java线程详解(二)
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线程详解(二)的更多相关文章
- Java多线程详解(二)
评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡 ...
- java线程详解
Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...
- Java线程详解----借鉴
Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...
- 【转】Java线程详解
Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...
- java线程——详解Callable、Future和FutureTask
回顾: 接上篇博客 java线程--三种创建线程的方式,这篇博客主要介绍第三种方式Callable和Future.比较继承Thread类和实现Runnable接口,接口更加灵活,使用更广泛.但这两种方 ...
- java线程详解(三)
java线程间通信 首先看一段代码 class Res { String name; String sex; } class Input implements Runnable { private R ...
- 并发编程 || Java线程详解
通用线程模型 在很多研发当中,实际应用是基于一个理论再进行优化的.所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解. ...
- java线程详解(一)
1,相关概念简介 (1)进程:是一个正在执行的程序.每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元.用于分配空间. (2)线程:就是进程中一个独立的控制单元,线程在控制着 ...
- Java集合详解二
前一篇我们已经讲过了Collectin是存放单值的最大接口, 可以看到Map接口和其主要之类的关系图如下: 快速访问 HashMap HashTable TreeMap Map输出 那Map ...
随机推荐
- windows8.1+centos7双系统(装完centos后无win8引导)
原先系统为windows8.1后来装上centos7后无win8系统引导, 打开电脑进入引导界面按C 进入grub界面 输入“cat (hd0,” 按tab可查看到windows8 地址为“hd0, ...
- aspx中如何绑定llistbox数据列表
DataTable dt = new DataTable(); dt = goodsbll.Get_prodtolist(_fatherpeoid,_sonpeoid).Tables[]; ) { f ...
- Android开发--环境的配置
一 Android开发环境:JDK.eclipse ADT.海马模拟器或者夜神模拟器.配置之前先保证运行内存足够大,不然会导致运行卡. 二 JDK(不用安装) 1.jdk官方下载地址:http://w ...
- UITaleView的基础使用及数据展示操作
UITableView表视图,是实用的数据展示的基础控件,是继承于UIScrollView,所以也可以滚动.但不同于UIScrollView,UITableView只可以上下滚动,而不能左右滚动. 因 ...
- 在HttpClient请求的时候,返回结果解析时出现java.io.IOException: Attempted read from closed stream. 异常,解决
原因是EntityUtils.toString(HttpEntity)方法被使用了多次.所以每个方法内只能使用一次.
- Bootstrap导航点击菜单跳转与点击缩放菜单折叠按钮缓冲效果插件jquery.singlePageNav.min.js
引入步骤: <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></scrip ...
- java如何得到GET和POST请求URL和参数列表(转)
在servlet中GET请求可以通过HttpServletRequest的getRequestURL方法和getQueryString()得到完整的请求路径和请求所有参数列表,POST的需要getPa ...
- PHPRPC jsp发布服务
PHPRPC是面向服务的高性能远程过程调用协议.PHPRPC for java 是此协议的一种实现,详细请到官方看PHPRPC的介绍 ,以其它rpc协议的性能对比请:Java.PHPRPC.Hessi ...
- Android_layout 布局(一)
今天主要学习了Android 的layout布局. Android layout 布局一般分为五种: LinearLayout (线性布局) :子组件按照垂直或者水平方向来布局. RelativeLa ...
- swift 取出中间文本
func stringmid (wholestring:String,front:String,behind:String)->String { if wholestring.isEmpty ...