一、为什么要线程同步
因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写 *** 作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程 *** 作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100取钱成功了,账户余额是0那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
二、不同步时的代码
BankJava
package threadTest;/
@author ww
/
public class Bank {
private int count =0;//账户余额
//存钱
public void addMoney(int money){
count +=money;
Systemoutprintln(SystemcurrentTimeMillis()+"存进:"+money);
}
//取钱
public void subMoney(int money){
if(count-money < 0){
Systemoutprintln("余额不足");
return;
}
count -=money;
Systemoutprintln(+SystemcurrentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
Systemoutprintln("账户余额:"+count);
}
}
SyncThreadTestjava
package threadTest;
public class SyncThreadTest {
public static void main(String args[]){
final Bank bank=new Bank();
Thread tadd=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Threadsleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
eprintStackTrace();
}
bankaddMoney(100);
banklookMoney();
Systemoutprintln("\n");
}
}
});
Thread tsub = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
banksubMoney(100);
banklookMoney();
Systemoutprintln("\n");
try {
Threadsleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
eprintStackTrace();
}
}
}
});
tsubstart();
taddstart();
}
}
余额不足
账户余额:0
余额不足
账户余额:100
1441790503354存进:100
账户余额:100
1441790504354存进:100
账户余额:100
1441790504354取出:100
账户余额:100
1441790505355存进:100
账户余额:100
1441790505355取出:100
账户余额:100
三、使用同步时的代码
(1)同步方法:
即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
修改后的Bankjava
package threadTest;
/
@author ww
/
public class Bank {
private int count =0;//账户余额
//存钱
public synchronized void addMoney(int money){
count +=money;
Systemoutprintln(SystemcurrentTimeMillis()+"存进:"+money);
}
//取钱
public synchronized void subMoney(int money){
if(count-money < 0){
Systemoutprintln("余额不足");
return;
}
count -=money;
Systemoutprintln(+SystemcurrentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
Systemoutprintln("账户余额:"+count);
}
}
再看看运行结果:
余额不足
账户余额:0
余额不足
账户余额:0
1441790837380存进:100
账户余额:100
1441790838380取出:100
账户余额:0
1441790838380存进:100
账户余额:100
1441790839381取出:100
账户余额:0
瞬间感觉可以理解了吧。
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
(2)同步代码块
即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
Bankjava代码如下:
package threadTest;
/
@author ww
/
public class Bank {
private int count =0;//账户余额
//存钱
public void addMoney(int money){
synchronized (this) {
count +=money;
}
Systemoutprintln(SystemcurrentTimeMillis()+"存进:"+money);
}
//取钱
public void subMoney(int money){
synchronized (this) {
if(count-money < 0){
Systemoutprintln("余额不足");
return;
}
count -=money;
}
Systemoutprintln(+SystemcurrentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
Systemoutprintln("账户余额:"+count);
}
}
运行结果如下:
余额不足账户余额:0
1441791806699存进:100
账户余额:100
1441791806700取出:100
账户余额:0
1441791807699存进:100
账户余额:100
效果和方法一差不多。
注:同步是一种高开销的 *** 作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
(3)使用特殊域变量(volatile)实现线程同步
avolatile关键字为域变量的访问提供了一种免锁机制
b使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
c因此每次使用该域就要重新计算,而不是使用寄存器中的值
dvolatile不会提供任何原子 *** 作,它也不能用来修饰final类型的变量
Bankjava代码如下:
package threadTest;
/
@author ww
/
public class Bank {
private volatile int count = 0;// 账户余额
// 存钱
public void addMoney(int money) {
count += money;
Systemoutprintln(SystemcurrentTimeMillis() + "存进:" + money);
}
// 取钱
public void subMoney(int money) {
if (count - money < 0) {
Systemoutprintln("余额不足");
return;
}
count -= money;
Systemoutprintln(+SystemcurrentTimeMillis() + "取出:" + money);
}
// 查询
public void lookMoney() {
Systemoutprintln("账户余额:" + count);
}
}
运行效果怎样呢?
余额不足
账户余额:0
余额不足
账户余额:100
1441792010959存进:100
账户余额:100
1441792011960取出:100
账户余额:0
1441792011961存进:100
账户余额:100
是不是又看不懂了,又乱了。这是为什么呢?就是因为volatile不能保证原子 *** 作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。
(4)使用重入锁实现线程同步
在JavaSE50中新增了一个javautilconcurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
Bankjava代码修改如下:
package threadTest;
import javautilconcurrentlocksLock;
import javautilconcurrentlocksReentrantLock;
/
@author ww
/
public class Bank {
private int count = 0;// 账户余额
//需要声明这个锁
private Lock lock = new ReentrantLock();
// 存钱
public void addMoney(int money) {
locklock();//上锁
try{
count += money;
Systemoutprintln(SystemcurrentTimeMillis() + "存进:" + money);
}finally{
lockunlock();//解锁
}
}
// 取钱
public void subMoney(int money) {
locklock();
try{
if (count - money < 0) {
Systemoutprintln("余额不足");
return;
}
count -= money;
Systemoutprintln(+SystemcurrentTimeMillis() + "取出:" + money);
}finally{
lockunlock();
}
}
// 查询
public void lookMoney() {
Systemoutprintln("账户余额:" + count);
}
}
运行效果怎么样呢?
余额不足
账户余额:0
余额不足
账户余额:0
1441792891934存进:100
账户余额:100
1441792892935存进:100
账户余额:200
1441792892954取出:100
账户余额:100
效果和前两种方法差不多。
如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
(5)使用局部变量实现线程同步
Bankjava代码如下:
package threadTest;
/
@author ww
/
public class Bank {
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
// TODO Auto-generated method stub
return 0;
}
};
// 存钱
public void addMoney(int money) {
countset(countget()+money);
Systemoutprintln(SystemcurrentTimeMillis() + "存进:" + money);
}
// 取钱
public void subMoney(int money) {
if (countget() - money < 0) {
Systemoutprintln("余额不足");
return;
}
countset(countget()- money);
Systemoutprintln(+SystemcurrentTimeMillis() + "取出:" + money);
}
// 查询
public void lookMoney() {
Systemoutprintln("账户余额:" + countget());
}
}
运行效果:
余额不足
账户余额:0
余额不足
账户余额:0
1441794247939存进:100
账户余额:100
余额不足
1441794248940存进:100
账户余额:0
账户余额:200
余额不足
账户余额:0
1441794249941存进:100
账户余额:300
看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。
ThreadLocal与同步机制
aThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题
b前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
源代码如下:
1、线程类
package comyjfutil; import javautilDate; import javautilList; public class GetWebThread extends Thread{ / 线程 / public void run(){ try { while (true) { int day = 0; long time1 = new Date()getTime(); //用来同步抓取线程 synchronized("searchthead"){ Mainthisdaycount++; if(Mainthisdaycount>Maindaycount){ break; } Systemoutprintln("开始查询第"+(Mainthisdaycount)+"天"); Threadsleep(133); day = Mainthisdaycount-1; } //获取抓取的时间 String datetext = TimeUtildateformat(TimeUtiladdDaysForDate(day)); String[] txt =FileUtilgetCityByTxtFile(); for(int t=0;t<txtlength;t++){ String[] way = txt[t]split("\|"); String start = way[0]; String end = way[1]; //抓取到的数据列表 List<DataBean> datalist = MaingetDataList(datetext, start, end); if(datalist!=null){ Mainisadsl = 0; CheckAdsladsllasttime = new Date()getTime(); FileUtiladdDataToFileCsv(datalist); MainlogprintLog("===="+datetext+"="+start+"="+end+"="+t+"=数据总数:"+datalistsize()); }else{ Threadsleep(11); AdslTheadisadsl = true; Threadsleep(11); //判断是否正在拨号 并暂停线程 while (AdslTheadisadsl) { Threadsleep(5000); } t--; } } long time2 = new Date()getTime(); MainlogprintLog(datetext+"==查询完毕=========耗时"+(time2-time1)); } } catch (Exception e) { MainlogprintLog(egetMessage()); eprintStackTrace(); } } } 第二步, 准备线程启动。
控制线程数量。 >
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
2
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
3
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
4
notityAll ():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争
线程同步是多线程编程中重要的概念。它的基本意思就是同步各个线程对资源(比如全局变量、文件)的访问。如果不对资源访问进行线程同步,就会产生资源访问冲突的问题。比如,一个线程正在读取一个全局变量,而读取全局变量的这个语句在C++语言中只是一条语句,但在CPU指令处理这个过程的时候需要用多条指令来处理这个读取变量的过程。如果这一系列指令被另外一个线程打断了,也就是说CPU还没有执行完全部读取变量的所有指令就去执行另外一个线程了,而另外一个线程却要对这个全局变量进行修改,修改完后又返回原先的线程,继续执行读取变量的指令,此时变量的值已经改变了,这样第一个线程的执行结果就不是预料的结果了。线程同步是多线程编程中重要的概念。它的基本意思就是同步各个线程对资源(比如全局变量、文件)的访问。如果不对资源访问进行线程同步,就会产生资源访问冲突的问题。比如,一个线程正在读取一个全局变量,而读取全局变量的这个语句在C++语言中只是一条语句,但在CPU指令处理这个过程的时候需要用多条指令来处理这个读取变量的过程。如果这一系列指令被另外一个线程打断了,也就是说CPU还没有执行完全部读取变量的所有指令就去执行另外一个线程了,而另外一个线程却要对这个全局变量进行修改,修改完后又返回原先的线程,继续执行读取变量的指令,此时变量的值已经改变了,这样第一个线程的执行结果就不是预料的结果了。
你锁错东西了
synchronized add只是锁了add这个方法,
num++之后打印,中间插不进来其他线程,所以结果是1 2
你想要2 2的话要synchronized (num)
就是对num的synchronized 块结束以前其他的线程块不能 *** 作num,等可以 *** 作的时候num已经是2了
就是
class Timer{
private static Integer num = 0;
public synchronized void add(String name){
//synchronized (num) {
num ++;
try {Threadsleep(1);}
catch (InterruptedException e) {}
Systemoutprintln(name+", 你是第"+num+"个使用timer的线程");
//}
}
}
之所以改成Integer是因为int是基础数据类型不能成为锁
没运行,简单看了看,似乎有些问题
1。从说明逻辑来看:
“线程A执行一次,通知线程B利用A的结果执行,再通知线程A取新的值”
这一句完全体现不出来用线程的必要,这和单线程顺序执行ABABAB有什么区别?
应该这样描述:线程A执行通知线程B利用A的结果执行,B确认得到值后就通知A取新值,然后B执行打印
2。同步问题:AB共用一个g_hEvent(AB之间的同步,还是B与B同步,A和B的SetEvent(g_hEvent);到底是谁控制谁?),而且WaitForSingleObject还放在while外面,你的“通知”确定有用?
java中多线程的实现方法有两种:1直接继承thread类;2实现runnable接口;同步的实现方法有五种:1同步方法;2同步代码块;3使用特殊域变量(volatile)实现线程同步;4使用重入锁实现线程同步;5使用局部变量实现线程同步 。
其中多线程实现过程中需注意重写或者覆盖run()方法,而对于同步的实现方法中使用较常使用的是利用synchronized编写同步方法和代码块。
以上就是关于java多线程有几种实现方法线程之间如何同步全部的内容,包括:java多线程有几种实现方法线程之间如何同步、java多线程采集,多线程数据同步的问题。 怎么做同步啊。。、线程同步的方法等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)